Tim Scott's Blog

November 30, 2009

A Question Of Astonishment

Filed under: FluentHtml, Programming — Tim Scott @ 5:26 pm

Steve Michelotti recently found a very subtle bug in MvcContrib.FluentHtml.   It provides us with a rather interesting case of applying the Principle Of Least Astonishment (a.k.a. Rule Of Least Surprise).

It turns out that FluentHtml’s select helper lets the user express his intent vaguely.  Consider whether this test should pass:

[Test]
public void when_determining_selected_values_select_should_ignore_selected_method_if_any_options_are_marked_selected()
{
    var list = new List<SelectListItem>
    {
        new SelectListItem { Text = "Acura", Value = "1", Selected = true },
        new SelectListItem { Text = "BMW", Value = "2" },
    };

    var select = new Select("foo.Bar").Selected(2).Options(list);

    var optionNodes select.ToString().ShouldHaveHtmlNode("foo_Bar").ShouldHaveChildNodesCount(2);
    optionNodes[0].ShouldBeSelectedOption(1, "Acura");
    optionNodes[1].ShouldBeUnSelectedOption(2, "BMW");
}

See what we have done here? We have allowed the user to express which options should be selected in two, possibly contradictory ways. In the call to Options, the user has specified that Acura should be selected. In the call to Selected, she has specified that BMW should be selected. How should we interpret what she really wants?

The first path to consider is to change the API to remove the chance for vaugeness. In most cases this is highly preferable. In this case, however, the cat is already out of the bag so to speak. That is, changing the API in this way would be a big breaking change.  Furthermore, it is deliberate and I believe very useful that the API offers a lot of ways to specify select options.

That leaves us to invoke the Rule Of Least Surprise. When a user specifies vague intent, in the manner that we have described, I can see five reasonable outcomes:

  1. Selected items are only those expressed via Selected
  2. Selected items are only those expressed via Options
  3. Selected items are a union of those expressed via Selected and Options
  4. Selected items are only those expressed via Options or Selected, whichever is called last
  5. Selected items are only those expressed via Options unless none, then those expressed via Selected

I will reject #4 out of hand.  FluentHtml helpers are essentially builders.  And with builders, order should never be important.  #1 and #2 are the next to go.  I cannot think of a better argument for either, and so the choice would be arbitrary.  It’s a rather close call between #3 and #5, but I will choose #5.  Here is the thinking.   If the user has specified selected items within the options themselves, then calling Selected is clearly a mistake on his part and he should expect it to be ignored.  On the other hand, if he did not specify selected items within the options list, then he could reasonably expect the call to Selected to be honored.

Would you make a different choice?

November 5, 2009

MVC 2 Areas Gotchas

Filed under: MS MVC — Tim Scott @ 12:24 am

I am adding ASP.NET MVC to a legacy WebForms application. Hallelujah! 

The process has gone very well.  I am here to report, do not fear the hybrid application!  At first we put the MVC parts directly into the legacy app.  It only required a bit of fiddling around to create a custom view engine. Later we decided to move the MVC parts into an “area project” using the new MVC 2.  This MSDN walkthrough was our guide.  The purpose of this post is to report a couple tricky gotchas that we encountered along the way.

GOTCHA #1:  By default, the area folder is named after the assembly, not the area name.

The walkthrough shows code for an AreaRegistration class:

public class Routes : AreaRegistration
{
    public override string AreaName
    {
        get { return "Store"; }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            "Store_Default",
            "Store/{controller}/{action}/{id}",
            new { controller = "Products", action = "List", id = "" }
        );
    }
}

Notice that the Name property is “Store” which matches the first node of the route.  It might seem that this is significant, but as far as I can tell it is not.  What is significant is that the project name “Store” matches the first node of the route.  So route needs to start with your assembly name.  

Okay, but what if my porject is named “Foo.Bar.Store”?  I don’t want my route to be “Foo.Bar.Store/{controller}/{action}/{id}”.  Fortuantely you can change this.  Here’s how.

The walkthrough gives instructions to modify the project files, which includes adding the following AfterBuild task to your “area project:”

<CreateAreaManifest AreaName=”$(AssemblyName)” AreaType=”ChildAreaPath=”$(ProjectDir)” ManifestPath=”$(AreasManifestDir)” ContentFiles=”@(Content)” />

Change it to this:

<CreateAreaManifest AreaName=”StoreAreaType=”ChildAreaPath=”$(ProjectDir)” ManifestPath=”$(AreasManifestDir)” ContentFiles=”@(Content)” />

What this does is tells MSBuild to copy the view folders from the “area project” into the parent project to a location named “Views/Areas/Store” rather than “Views/Areas/Foo.Bar.Store”.

GOTCHA #2: Manifest files are not recreated with every build.

The CreateAreaManifest task does not actually tell MS Build where to copy the view files and folders.  Rather it tells MSBuild how to create a manifest file.  This file contains the details that MSBuild will later use to copy the view files and folders.  But there is a gotcha.  Once a manifest file has been created, subsequent builds add to it, rather than to trash it and create a fresh one.  Therefore, if you change the CreateManifestArea task in the project file, you will need to manually delete the corresponding manifest file. This is a real pain, and maybe there’s some way around it, but not that I know about.

Blog at WordPress.com.