Monday, August 24, 2009

Why I left Bing and went back to Google - not a romance

I recently decided to give Bing a chance to impress me and it did. However, today I switch my default search back to Google. The reason is not because it's a better search it's because it's a bigger search.

While developing I am always hitting errors and problems that are usually answered with a quick search on the web. Bing does a good job of this as does Google. However, about half of my search is done on Stack Overflow because that's where the answers to most of my questions lie.

I use the "site:stackoverflow.com" directive with the search term(s) when searching the site. If you use the site:stackoverflow.com directive without search terms then you get an interesting result which is the number of pages that that search engine has indexed on that site.

When I do this with Bing it tells me that it has 29,200 page in its index. When I do this with Google I discover that they have indexed 1,240,000 pages on that same site. That means that Google have 42 pages (if rounded to a whole number) for every 1 page from Bing and if you've read the Hitch Hiker's Guide to the Universe Trilogy (a trilogy in five parts) you will know that 42 is the meaning of life, the universe and everything.

That aside, the chances of me finding that needle is 42 times higher on Google than it is on Bing.

Sorry Bing, you have to go...

Thursday, August 20, 2009

ASP.NET MVC with jQuery DynaTree plugin for Checkboxes

I've just added the jQuery DynaTree plugin to an ASP.NET MVC project that I'm working on and I thought that I'd do a quick write-up on how I did that using an example page.

Here are the Actions from the Controller:

public ActionResult DynaTree()
{
    return View("DynaTree", (object)"1,2,3,4");
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult DynaTree(FormCollection collection)
{
    string selectedItems = Request.Form["SelectedItems"];
    string[] items = selectedItems.Split(',');
    return View("DynaTree", (object)selectedItems);
}

The first Action, the GET verb, simple returns the view with a comma separated list of items that we want pre-selected. Interestingly if the second parameter is a string then the overload of the View() function maps that parameter to a master file so you have to cast it to an object to let it know that you're passing in the Model in this param. I could have also set that string in the ViewData collection.

The POST Action pulls the list out of the <input> tag named SelectedItems.

Here are the contents of the <asp:Content> tag in the view:

<% using(Html.BeginForm()) { %>
    <div id="tree">
        <ul>
            <li id="1">First upper item</li>
            <li id="2">Second upper item</li>
            <li id="3">Third upper item
                <ul>
                    <li id="4">First Lower item in Third layer</li>
                    <li id="5">Second Lower item in Third layer</li>
                </ul>
            </li>
            <li id="6">Fourth upper item
                <ul>
                    <li id="7">First Lower item in Fourth layer</li>
                    <li id="8">Second Lower item in Fourth layer</li>
                </ul>
            </li>
            <li id="9">Fifth upper item</li>
        </ul>
    </div>
    <% =Html.Hidden("SelectedItems", Model) %>
    <input type="button" name="submit" value="Submit" />
<% } %>

<!-- Also needed are references to the jQuery library. This is in the Site.master file -->

<script src="/Scripts/jquery.dynatree.min.js" type="text/javascript"></script>
<script src="/Scripts/TestDynaTree.js" type="text/javascript"></script>

There's nothing special in here.

The JavaScript that I used with this:

$(document).ready(function() {

    $("#tree").dynatree({
        onSelect: function(flag, dtnode) {
            // This will happen each time a check box is selected/deselected
            var selectedNodes = dtnode.tree.getSelectedNodes();
            var selectedKeys = $.map(selectedNodes, function(node) {
                return node.data.key;
            });
            // Set the hidden input field's value to the selected items
            $('#SelectedItems').val(selectedKeys.join(","));
        },
        checkbox: true
    });

    // Remove all of the icons from the tree
    $('#tree .ui-dynatree-icon').remove();

    // Split out the items listed in the SelectedItems hidden input box
    var items = $('#SelectedItems').val().split(',');
    var tree = $("#tree").dynatree("getTree");
    // Mark each of those items as selected in the tree
    for (var i = 0; i < items.length; i++) {
        var node = tree.getNodeByKey(items[i]);
        node.select(true);
        node.activate();
    }
});

The node.activate() function will expand the parent node for any child node that you select.

Wednesday, August 12, 2009

Interface or Abstract Class in C#

I was debating between which would be better for a particular C# class in a project that I'm working on: An Interface or an Abstract Class. So I decided to do some research and highlight the advantages and disadvantages:

  • A class can inherit (implement) multiple Interfaces (+). It can only inherit one Abstract Class (-).
  • Inheritance of an Interface forces the implementation of all parts of the Interface (+).
  • Code might be duplicated using an Interface (-) while it could be written once in an Abstract Class (+).

An Interface is not necessarily better than an Abstract Class. Pick the right tool for the job once you know what the job is.

Wednesday, August 5, 2009

No domain name front running

ICANN have just released the results of a 10 month study into domain name front running by security expert Benjamin Edelman that concludes that front running at leading domain registration and domain availability-checking sites does not happen.

I'd be very surprised if I discovered that a reputable company engaged in that type of activity. They would have to intentionally write some software to do this. I suppose that by definition a company ceases to be reputable when they engage in this so no reputable would do this. Didn't Network Solutions do something like this recently? When you searched for a domain name on their site they registered the name so if you then went to another site to buy the domain you were unable to until the five day tasting period elapsed.

I've heard that Apple registers 400 domains at a time when it's researching a new product that might be released to market to attempt to hide the name or intent of the product among a sea of names.

Tuesday, August 4, 2009

ASP.NET MVC pages are blank

I've come across this problem before and want to try and document some solutions here.

Today's mishap was a blank page from an ASP.NET MVC application when deployed. It turned out that this was because the Authorization system (in this case Active Directory) had not recognized me so the [Authorize(Roles = "Everyone")] attribute that I'd added to the controller was saying that I wasn't a member of the group Everyone. By removing this attribute and redeploying I confirmed that this was the problem.

Another reason for a blank page is if a generic error is happening but you're handling that generic error and suppressing it. There's an answer here with more info on that type of blank page.

Configuring IIS5 for ASP.NET MVC

I was trying to setup an ASP.NET MVC application to run under a virtual directory on IIS 5.1. When I click the properties on that virtual directory and then click the "Configuration..." button nothing happens - i.e. the configuration dialog doesn't pop up as it should. I suspect this is because I've also installed the IIS6 manager on this XP machine.

My objective is to Add an Application Extension Mapping to this virtual directory but I can't get into the configuration page which means I can't reach the Add/Edit Application Extension Mapping dialog either. Ironically I can get to this from the IIS6 manager but when I try and add the ".*" extension to the c:\windows\microsoft.net\framework\v2.0.50727\aspnet_isapi.dll executable I get the message "Wrong extension format." Not being able to add ".*" I added ".mvc" as a placeholder.

The solution is to use the IIS Metabase Explorer. You can install the Internet Information Services (6.0) Resource Kit Tools from here and one of the utilities installed will be the IIS Metabase Explorer.

Once open, navigate the tree in the left panel to LM > W3SVC > 1 > ROOT > AppName. Once selected, in the right panel you'll see a list of records, one of those will be ScriptMaps. Double click ScriptMaps and then scroll down the list until you find the .mvc placeholder. Double click this and change the ".mvc" to ".*" Click Apply and Save

Monday, August 3, 2009

Defeating Spam Bot with email names

I recently changed the registration process on one of my sites' forums. I rewrote it from classic ASP to use ASP.NET MVC in C#. One of the side effects of this rewrite was a complete cessation in registration from spam bots.

At first I thought that this was just because I'd changed to location of the URL where registration took place and had deliberately not forwarded the old location. Most pages when I move them I forward from old to new location but could not think of any reason to do this with a registration page because it's not a page that anyone would ever bookmark and should always be discovered by a human clicking a link. Of course another site could point to it but that is unlikely.

When looking at the logs I discovered that spam registration bots were still finding the registration page but the reason that they were failing is because they search for all <input> tags with email as part of their name and fill in an email address. One of the tags that I have is called AllowEmails which is a drop down. This needs to be assigned a number value as I do a Convert.ToInt32 on the server. When the spam bots filled in this <input> tag's value as an email address instead of a numeric value it was causing an exception on the server and this I was logging with all the form values so I could see what was happening.

This has lead me to the idea that all <input> tags should have names with the word email in them. So FirstNameEmail and LastNameEmail for example. This would cause the spam bots to fill all the values on your form with their email address.

I have also employed the technique of leaving a hidden <input> tag on the page with its name set to email. I remember that this used to work but I see that spam bots are wise to this now and are ignoring the hidden input tags because they know that they are honey pots to trap them with.

Another idea that I've been considering is the naming of each <input> tag with a unique random value and then storing that collection in a Session variable on the server to map the randomly generated names to the real names. I haven't put any effort into that (yet) because I know that if someone took a careful look at this registration page they could just setup a spam bot to iterate through the inputs in order to find the right values to fill in. But then I could start sticking in random <inputs>, or shuffling them around on the screen. What fun I could have... But I won't do that until the bots have got past their current failure...

Sunday, August 2, 2009

LINQ and the outer variable trap

I'd written the following piece of code and was debugging this to try and work out why it wouldn't work. Take a look at it and see if you can figure out my mistake:

foreach (string word in words)
{
    query = query.Where(a => (a.Message).Contains(word));
}

The objective of this code is to build a query with an AND clause between each of the given words.

As a quick aside, while trying to debug this I ended up with a query which ended like this:

WHERE ([t0].[MESSAGE] LIKE @p0) AND ([t0].[MESSAGE] LIKE @p

 

To find the values that p0 and p1 have been set to you need to set the DataContext's .Log property to a TextWriter such as Console.Out or TextWriter tw = new StringWriter();

Back to the quiz, the reason why that code is flawed is because of the outer variable trap. The same variable is being captured in the loop because LINQ uses delayed execution and the Contains(word) part is not evaluated until later. To fix this problem we put a temporary variable inside the loop to force it to be evaluated. This will work:

foreach (string word in words)
{
    string innerWord = word;
    query = query.Where(a => (a.Message).Contains(innerWord));
}