ASP.NET MVC 3 DropDownListFor strange behaviour for determining selected option

So a strange behaviour in the implementation of the HtmlHelper.DropDownListFor extension method was recently brought to my attention where the order of ‘looking for’ the selected value of the list seemed incorrect.

I would assume that the selected value would always be the selected value from the strongly typed model property (as I have clearly stated by providing a lambda which results in returning that property) but it seems the selected value will not always be the selected value from the strongly typed model as I would expect.

For example:

@model TestModel

@{
    Model.Title = "Hello";
    ViewBag.Title = "Tim";
    Layout = null;
}

@using (Html.BeginForm())
{
    @Html.DropDownListFor(p => p.Title, new SelectList(new[] { "Other", "Hello", "Brent","Tim","Smith" }, "Smith"))
}

Given the above code what would the value of the drop down list be?
a) “Smith”
b) “Tim”
c) “Hello”

Well I would expect the SelectList constructor to create a list with a selected value of “Smith” then I would expect the DropDownListFor extension method to set the selected value to be “Hello” as provided by my model.

In fact what happens is that the value from the ViewBag is taken in priority over any others!
The answer to above is actually b).

To be honest using a strongly typed view I don’t think I would ever want the extension method to take the value from the ViewBag implicitly.

Now if I remove the Title parameter in the ViewBag as done so below, what would you expect?

@model TestModel

@{
    Model.Title = "Hello";
    //ViewBag.Title = "Tim";
    Layout = null;
}

@using (Html.BeginForm())
{
    @Html.DropDownListFor(p => p.Title, new SelectList(new[] { "Other", "Hello", "Brent","Tim","Smith" }, "Smith"))
}

Using the above logic the value would be “Hello” which is correct. However this doesn’t give us an option to override the selected value as the SelectList’s selected overload does the work before handing the object off to the DropDownListFor extension method which it then ignores.

Finally if the ViewBag property is null AND my Model’s property is null then the SelectList’s selected value will be unchanged and the result will be “Smith”.

Now I looked into TextBoxFor and EditorFor, etc and they work as expected. It is only the ListBoxFor and DropDownListFor extension methods which are first checking for the value in the ViewBag before the Model.

Just something everyone needs to be aware of. I would consider this a bug. Be sure to check your naming as we had a page title in the viewbag called “ViewBag.Title” and a “Person.Title” property on our strongly typed Person model. The select list tried to set the selected value of the list to be the page’s title!.

Advertisements

ASP.NET MVC JQuery EventAggregator (Decoupled Event Handling)

The concept of Event Aggregation was first introduced to me as I read through the PRISM toolkit (Composite Application Guidance) from the Microsoft Patterns and Practises team.

It allows you to modularise your code and reduce dependencies between modules, I love it.
An example of how it benefits web applications would be:

Consider a simple 2 column web page layout where I have a div with content on the left (LeftBarRegion) and a div for the more content (MainContentRegion) on the right. Lets say that these two regions are defined in a Layout page (or Master Page) and the inner content of the MainContentRegion is changed by a JQuery ajax call. I have a LeftView and RightView which would fill the appropriate regions in my layout.

Now how do I get the view in the LeftBarRegion to talk to the view in the MainContentRegion and vice versa without knowing anything about the implementation details of each view? What if I also want to notify the parent layout page from either of the inner containers?

Welcome to the power of EventAggregator. We could have any content we like in either of the regions above and they can still talk to each other in a de-coupled fashion.

Enough talking lets watch it in action.


// Very simple - JQuery awesomeness
var EventAggregator = {

    // Ensure you are using the jquery data from the parent document so that everything goes
    // through the same EventAggregator.
    publish: function (event, args) {
        window.top.document.$(window.top.document).trigger(event, args);
    },

    subscribe: function (event, delegate) {
        window.top.document.$(window.top.document).bind(event, delegate)
    }
}

Now I can do this:


// In left content page
EventAggregator.publish(Events.SomeEventThatNeedsToBeHandled, { ID = 1 });

// In content page
EventAggregator.subscribe(Events.SomeEventThatNeedsToBeHandled, function (args) {
    // Do stuff in the main content page when this event occurs
});

// In layout page
EventAggregator.subscribe(Events.SomeEventThatNeedsToBeHandled, function (args) {
    // Do stuff in the layout page when this event occurs.
});