MVCContrib FluentHTML Select Lists and NHibernate

When using ASP.NET MVC you will eventually want to do a select (drop down) or even a multiple select list, and your first though might be to use <%= Html.DropDownList %>.  Unfortunately you will soon notice that ASP.NET MVC always looks for a match between the name of the dropdown and a property on the model, and if it finds a match, it OVERRIDES the selected value(s) of the select list.  Now of course not being able to reliably set the selected value(s) is a major problem – if you Google this you will get a ton of results and most people solve the issue by just changing the name of the Html.DropDownList(“name”) to something that doesn’t match a model property.

Of course we can put this into the “hack” or “workaround” category, and I don’t like doing that on my nice shiny MVC project.  However, the biggest problem is if you are doing advanced model binding (I have my own impl, but as an example look at SharpArchitecture), changing the name of the drop down will break model binding and model state errors.

So – how do we use drop down lists and maintain our control over naming and selected items?  Enter MVCContrib’s FluentHTML:

MVCContrib FluentHTML Select and MultiSelect

Once you copy the MVCContrib.FluentHTML DLL into your project and resolve the namespace, you get a whole bunch of nice Html helpers right on the base View Data Container.  We want to look at the Select and MultiSelect options, which you would use as follows:

<%= this.MultiSelect("User.Projects").Options(Model.Projects) %>

Now moving past the default case, you’ll want to choose which properties to bind and maybe which values are selected.  The following is how I attempted to do this at first:

<%= this.MultiSelect("User.Projects")
.Options(new MultiSelectList(Model.Projects, "ID", "Name",
Model.User.Projects.Select(a=>a.ID)))%>

There is also another option which involves calling .Selected() instead of putting the selected values directly in the MultiSelectList ctor.
 
<%= this.MultiSelect("User.Projects")
.Options(new MultiSelectList(Model.Projects, "ID", "Name"))
.Selected(Model.User.Projects.Select(a=>a.ID)) %>

The Problem

When using NHibernate, or likely any other ORM, you run into a subtle problem.  Occasionally you will get the following errors when binding MultiSelect (note this problem also happens using the Select):

Object does not match target type.

Exception Details: System.Reflection.TargetException: Object does not match target type.

It takes a lot of debugging, but you eventually discover that this only occurs when binding to proxied instances.  So for this example, I had NHibernate get me a list of all Projects and since lazy-load was set to true (the default), some of these came back as proxies.  When trying to get the ID value of these proxies, you get the exception thrown from within System.Reflection.
 

The Solution

The solution here is to use an overload of Options() to put the valueField and textField into the method call and NOT into the constructor of a SelectList/MultiSelectList.  You can either use strings of selectors, as you can see below:

<%= this.MultiSelect("User.Projects")
.Options(Model.Projects, "ID", "Name")
.Selected(Model.User.Projects.Select(a=>a.ID)) %>
 
OR

<%= this.MultiSelect("User.Projects")
.Options(Model.Projects, a=>a.ID, a=>a.Name)
.Selected(Model.User.Projects.Select(a=>a.ID)) %>
 
Now, there is no reflection errors.  Enjoy!

4 Comments

  • I am new to MVC and have a dropdownlist created from the following code;

    this.Select("PaymentApplication.System").Options(((SelectList)ViewData[ControllerEnums.SelectListName.RecoverySystemList.ToString()]))

    How do I create for when an item is selected??

  • @dennismachel,
    There is a .Selected method that you can use to control which item is selected.

  • Many thanks, this got me where i needed to be.

    I was having problems with your example though and in the end had to use: this.Select(model => model.NatureId).Options((SelectList)Model.NatureList, "Value", "Text")

    with my select list being passed into my viewmodel like so: new SelectList(this.Natures.GetAll(), "Id", "Name");

    Thanks again.

  • Man, I've tried that but still doesn't work to me. But that makes me think that, if the trouble was the NH proxies, a DTO could solve my problem and that really fix it.

    Congrats for put me on the way. Cheers!

Comments have been disabled for this content.