Rendering and Binding Drop Down Lists using ASP.NET MVC 2 EditorFor

Published on Author nickriggs37 Comments

I’m a fan of view models. I’m also a big fan of the new display and editor templates in ASP.NET MVC. However I found there isn’t a default EditorFor implementation for rendering drop down lists. I ended up having to create my own editor template and model binder.

First, I decided I wanted my view model property to hold both the list of potential items and the selected item. The SelectListItem object that ships with MVC made this easy. Here is the view model:

public class BestPictureViewModel
{
    public IEnumerable<SelectListItem> Movies { get; private set; }

    public BestPictureViewModel()
    {
        //Fill the list will all the potential selections.
        Movies = new[] {
            new SelectListItem() { Value = "1", Text = "Slumdog Millionaire" },
            new SelectListItem() { Value = "2", Text = "The Curious Case of Benjamin Button" },
            new SelectListItem() { Value = "3", Text = "Frost/Nixon" },
            new SelectListItem() { Value = "4", Text = "Milk" },
            new SelectListItem() { Value = "5", Text = "The Reader" },
        };
    }
}

Let’s quickly create a controller and action that wires this view model up to a view:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new BestPictureViewModel());
    }
}

And now the very simple view:

<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <%= Html.EditorFor(m => m) %>
</asp:Content>

The only property in this ViewModel is a list of movies which we intend on rendering using the EditorFor html helper. However EditorFor doesn’t have a default rendering for IEnumerable<SelectListItem> – so the results are rather lack luster:

Lack-Luster

So let’s teach EditorFor how to render our list. First, we create a new partial view for our editor template: List.ascx

List-Editor-Template

The view should be strongly typed to IEnumerable<SelectListItem> and we use the Html DropDownListFor helper to do the actual rendering.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %>
<%= Html.DropDownListFor(m => m, Model) %>

That still isn’t enough. We need to give the EditorFor helper a hint to render our list property using the new List partial view. We do that using the aptly named UIHint attribute on the view model property:

[UIHint("List")]
public IEnumerable<SelectListItem> Movies { get; private set; }

The results are much better now!

Better-Results

There is still a problem. Usually you want to post a view model back to the server and optimally we use the view model in the action’s signature instead of relying on the form collection:

public ActionResult Save(BestPictureViewModel viewmodel)
{
    return View();
}

Let’s update our view to call this action:

<% using (Html.BeginForm("Save", "Home"))
   { %>
<%= Html.EditorFor(m => m)%>
<input type="submit" value="Save" />
<% } %>

When the site runs we have a form with a submit button. The submit button posts the form and our Save action gets invoked. The viewmodel parameter has our view model with all the items, however the item that was selected (I selected Benjamin Button) isn’t marked as such in the view model:

Not-Selected

When the form posted, it sent only the value of “2” in the request for the variable “Movies”. The MVC default binder doesn’t know how to bind that to our property of type IEnumerable<SelectListItem>. So we have to teach it.

Create a new model binder that inherits from the DefaultModelBinder:

public class MyModelBinder : DefaultModelBinder
{
    protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
    {
        if (propertyDescriptor.PropertyType.IsAssignableFrom(typeof(IEnumerable<SelectListItem>)))
        {
            var valueKey = string.IsNullOrEmpty(bindingContext.ModelName)
                ? propertyDescriptor.Name
                : string.Format("{0}.{1}", bindingContext.ModelName, propertyDescriptor.Name);

            bindingContext.ModelState[valueKey].Errors.Clear();

            var listItemValue = bindingContext.ValueProvider.GetValue(valueKey).AttemptedValue;
            var items = propertyDescriptor.GetValue(bindingContext.Model) as IEnumerable<SelectListItem>;

            items.Where(i => i.Value == listItemValue).First().Selected = true;

            return;
        }

        base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
    }
}

Our new model binder looks for properties that are being bound who’s type is IEnumerable<SelectListItem>. When it finds one, it extracts the posted value, finds it in the list items, and marks it as selected.

Next, in the Global.asax replace the default model binder with your own:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ModelBinders.Binders.DefaultBinder = new MyModelBinder();
}

Now when we run the application, choose Mr. Button and post the form again. Our view model will reflect our selection:

Selected

Download Source Code

37 Responses to Rendering and Binding Drop Down Lists using ASP.NET MVC 2 EditorFor

    • Yeah, there is a lot of boiler plate code involved to get it working. But once you have it in place, drop downs are just decorating your properties with [UIHint(“List”)];

  1. @Dave. I use the Required attribute the same way I would on any other property in my view model:

    [Required]
    [UIHint(&quot;List&quot;)]
    public IEnumerable&lt;SelectListItem&gt; Movies { get; private set; }
    

    Just make sure that the default “not-selected” option in the list has a value of “”.

  2. Nick,

    Thanks for the tutorial.

    In your example you are using an unchanging list of movies. I have a case where the class has a different set of SelectListItems in it depending on what has been passed in. This means that you can’t use your model binder to find the selected value because the collection of selectlistitems is blank.

    Can you think of a good way to solve this issue. I’ve had to go back to using the old ways for the time being but I’m keen on coming up with a good solution for using these new techniques when possible.

  3. That’s a pretty interesting scenario. When you say “depending what has been passed in”, is it something in the Route or Request data? If it is, you could potential create a custom model binder for your type that that identifies and initializes the encapsulated list when the model is first instantiated.

  4. I’ve been working on your solution for three hours now, until I realized it’s completely USELESS.

    You’re filling the ViewModel Movies property the hard way through the constructor, which is just a no go in 99% of cases.

    DropDownList values are normally filled from a DataSource that the ViewModel should not be aware of.

    So when you submit the form, there’s just no SelectListItem values to be selected from. It’s either null or Count == 0 depending where you try to override the DefaultModelBinder.

    I really feel you made me waste my time…

    • I welcome feedback to anything I post – as long as it’s constructive and polite. However, your post is neither.

      Anti-Constructive: Here is a concept. Start a dialog about a different approach instead of just shooting off at the mouth. For example, when you say something like “which is just a no go in 99% of cases”. Perhaps you could explain why. That would make it a constructive comment and we could have one of those dialog things. Who knows – you could change my mind and I could update this 9 month old post.

      Anti-Polite: “completely USELESS” and “I really feel you made me waste my time”. Really? Do you think I get paid to share this stuff? I do it because I hope I can help folks the same way countless other blogs and open source projects have helped me. I’m well aware that I don’t bat 1.00. I would love to read your blog or see your open source commits. I really would – send me a link… but we both know that isn’t going to happen. You didn’t even have the guts to leave your real name or email on a flame comment.

      So, even though I would usually delete a comment like yours. I’m going to leave it. I think it’s important that others know exactly how not to leave a comment on a blog.

  5. Thanks Nick.

    Great to see an extension of the DefaultModelBinder to get a feel for how it works.

    Most Lookups/Select Lists are static and loaded from the database. I’m finding that your method works well for me when I pull the lookup from the db/entitycontext in the parameterless constructor of the view model. Actually, a private method that loads the lookups for all constructors.

  6. Hi Nick,
    great post, thx.
    After ages of testing, I downloaded your source, opened it in VS2010. It doesn’t work as expected.
    The partial view list does not get called, so nothing is rendered except for the submit button. Any idea why?

  7. Thanks for the heads up Michael. There was a typo in the example source. Html.EditorFor(m => m) should be Html.EditorFor(m => m.Movies). I’ve updated the example.

  8. Nick,

    Thanks for posting this!!! I have been searching this topic for days and your solution works great! Maybe MS can get a clue and post some detailed information about MVC up on MSDN! Thanks again!!

    Mark

  9. Hi,
    I tried to implement this solution.
    But I have an issue. The Editfor helpers doesn’t want rendering the dropdown. If I IEnumerable by int.. the template List.asx is called. if I keep IEnumerable and I fill the items nothing happen I cannot reach my break point into the template and the dropdown isn’t render. Do you have an idea what I<m stuck ? There is something special to look at for rendering the dpd?

  10. Okay, I have more details.
    All the properties of my model are generated properly if they are type of (string, int, guid, bool). Only the IEnumerable property ” public IEnumerable Companies { get; private set; }” is not rendered. Well I think we can’t use EditorFor helper with simple Model.
    If I want using it with the IEnumerable I should split the Model. Just for testing Instead of just using Editfor my Whole model, I also added a second line
    Model.UserDataModel)%>
    Model.UserDataModel.Companies)%>

    Now my dropdown using the Editor template appears.

    There is a way better way to generate the form in one shot like if my IEnumarable was like a string?

  11. Well, this example is working fine if I do not use the Editfor to generate completly the form. In fact I try to understand why I can’t use a list EditorTemplate when the model properties associate with this dropdown is included in a model not only dedicated to this dropdown. In others words My dropdown is include in a forms where I want using Model.MyModel)%> to generate this form and the Model’s properties will have to be associate with a dropdown for one of them and normal textbox for the others

  12. I would like to know how to load data into the first combobox(Is covered by the above tutorial) and based on the data in first combobox, load some other data into a second combobox.

  13. Unfortunately this is not working if i do the Movie assignment on the controller before passing the model to the view.
    If i make something like :
    public ActionResult Index()
    {
    var bestPictureViewModel = new BestPictureViewModel();
    bestPictureViewModel.Movies = new[] {
    new SelectListItem() { Value = “1”, Text = “Slumdog Millionaire” },
    new SelectListItem() { Value = “2”, Text = “The Curious Case of Benjamin Button” },
    new SelectListItem() { Value = “3”, Text = “Frost/Nixon” },
    new SelectListItem() { Value = “4”, Text = “Milk” },
    new SelectListItem() { Value = “5”, Text = “The Reader” },
    };

    return View(bestPictureViewModel);
    }

    Then I have items=null on line 22.
    Do you have any Ideea how to fix this ?

    Thank you.

  14. The items must have the SelectListItem collection which the browser will not post. It will only include the value selected. It is quite all right because I just needed the selected item and not the whole collection.

    I am not really interested in the selection list so what I did was to create a SelectionListItem with item selected.

    IEnumerable items = new[] { new SelectListItem { Value = listItemValue, Text = “” , Selected = true } };

    There you go. The value is passed back to the controller with the same type as you passed it to the form.

  15. I considered using EditorFor.. but it’s just simpler to use DropDownListFor, and this allows for tweaking at runtime and doesn’t require recompilation.

    See example (where Model.ProductGroups is simply IEnumerable):


    @Html.DropDownListFor(model => model.Product.ProductGroup.Id, new SelectList(Model.ProductGroups, "Id", "Name"), "-- Select --")

  16. You could definitely see your skills in the work you write. The arena hopes for more passionate writers like you who are not afraid to mention how they believe. At all times go after your heart. “Experience is a good school, but the fees are high.” by Heinrich Heine.

  17. Dear Nick,

    Thanks very much for your most interesting article. This is very much appreciated although I must say that I am experiencing problems which in all likely hood is just a function of my stupidity.

    If I were to zip up my project would you be as so kind as to have a quick look if and when you have the time. I am a relative “newbie” to MVC so you will be helping me to keep what little hair I have on my head intact.

    Once again thank you for your time and for this post as I would certainly love to include this as part of my project

    Yours Sincerely

    David J Ledgett

    On

  18. Sorry to trouble you (I am not a programmer by profession), but I am just trying to figure out how to “pass” the selection to another view.

    For example, if I have a @Html.TextBoxFor(m => m.FirstName), on a confirmation page I can pass @Model.FirstName.

    Any ideas on how I could, for example, pass “The Curious Case of Benjamin Button” rather than passing the value “2” onto the view? I have tried (using my own example) @Model.MyProperty and @Model.MyPropertyList. The former passes the value (“2”), the latter passes “System.Collections.Generic.List`1[System.Web.Mvc.SelectListItem]”.

    There is nothing wrong with the code for my DropDownList, I get sent to the confirmation page with the above.

    I am using a wizard with @Hmtl.Hidden and MvcSerializer.

    Thanks in advance for any help.

  19. I drop a comment whenever I like a article on a site or
    if I have something to add to the discussion. It is caused by the passion communicated in the article I read.
    And after this post Rendering and Binding Drop Down Lists using ASP.NET MVC 2 EditorFor
    – Nick Riggs, Web Developer. I was actually excited enough to drop a thought 😉 I do have a couple of questions for
    you if it’s okay. Could it be simply me or does it look like like some of the comments appear like left by brain dead
    people? 😛 And, if you are writing at additional social sites, I would like to keep up with you.

    Could you make a list all of all your social sites like your Facebook
    page, twitter feed, or linkedin profile?

  20. Hi webmaster i see you put a lot of work in your website, i know how
    to make your blogging easier, do you know that you can copy any content from any page, make it 100% unique and pass
    copyscape test? For more details , just type in google – rewriter creates an unique
    article in a minute

  21. Hey I am so glad I found your site, I really found you
    by mistake, while I was searching on Bing for something else, Nonetheless I am here now and would just like to
    say cheers for a incredible post and a all round interesting blog (I also
    love the theme/design), I don’t have time to look over it all at the minute but I
    have bookmarked it and also included your RSS feeds, so when I have time I will be
    back to read a great deal more, Please do keep up the great work.

Leave a Reply

Your email address will not be published. Required fields are marked *