Populate html select lists from data in a view model in an ASP.NET MVC 3 application

Posted on June 21st, 2011

Ever feel like the HTML elements.

The current overload methods for System.Web.Mvc.HtmlHelper.DropDownList:

DropDownList Overloads

We can see that all the methods that provide a means for populating the elements work off of an IEnumerable object. The sticker here is the SelectListItem type. In order to pass data into the helper we need to package it up in SelectListItem objects. So our code that handles prepping data for the view will need to know about an MVC specific construct. Not that there is necessarily anything wrong with that, but it can be limiting to your decoupling efforts if you are into that sort of thing (which can be a very good thing to be into).

We can work around this by writing our own extension methods on the HtmlHelper class. Let's begin by writing an extension method in a static class for HtmlHelper.DropDownList. This extension method will allow us to pass in a string to represent the name to use for the HTML select element and a Dictionary collection object for the HTML option elements. The method will handle mapping the dictionary to a System.Web.Mvc.SelectList object that can then be passed to one of the existing DropDownList methods.

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace Website.Models
{
    public static class ExtensionMethods
    {
        public static MvcHtmlString DropDownList(this HtmlHelper helper, 
            string name, Dictionary dictionary)
        {
            var selectListItems = new SelectList(dictionary, "Key", "Value");
            return helper.DropDownList(name, selectListItems);
        }
    }
}

The SelectList constructor supports passing in an IEnumerable object and two strings that represent the property names of the value field and text field to use from the objects in the enumerable collection. This makes it easy for us to pass in the incoming dictionary object and the name of the properties on the objects in the dictionary. Enumerating over a Dictionary object results in KeyValuePair objects that have properties named Key and Value, so those are the string values we would use to tell the SelectList object how to map the data. With the SelectList created, we can call the existing DropDownList method on the HtmlHelper and be on our way.

With the extension method in place we can turn our attention to crafting a view model and loading up data for populating a select list. Imagine we needed a select list for days of the week where the text is the name of the day and the value is an integer representing the day number in the week. If we were to create a class named PageWithSelectList as our view model, we could fill it out with the following:

using System.Collections.Generic;

namespace Website.Models
{
    public class PageWithSelectList
    {
        public Dictionary DaysOfWeek { get; set; }
        public int DayOfWeek { get; set; }

        public PageWithSelectList()
        {
            this.DaysOfWeek = new Dictionary
                                  {
                                      {1, "Sunday"},
                                      {2, "Monday"},
                                      {3, "Tuesday"},
                                      {4, "Wednesday"},
                                      {5, "Thursday"},
                                      {6, "Friday"},
                                      {7, "Saturday"}
                                  };
        }
    }
}

The DaysOfWeek would represent the data for the option elements. The DayOfWeek would represent the selected day key value (if any). The constructor handles loading up the DaysOfWeek with some data.

Moving to the controller, we can initialize an instance of the PageWithSelectList class and pass that to a view. Using a HomeController as an example, the Index action method could look like so:

using System.Web.Mvc;
using Website.Models;

namespace Website.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var model = new PageWithSelectList();
            return View(model);
        }
    }
}

Before we turn our attention to the view file we need to address some details in the way the Razor view engine works. In previous versions of MVC you were able to add namespaces to the pages node in your main Web.config file to make them available to your view files without needing to add using statements within your views. With the Razor view engine there is a new node where these need to go () and they need to go into the Web.config file located in the Views directory.


  
  
    
      
      
      
      
      
    
  

With the default MVC 3 projects in Visual Studio 2010 this node will automatically be added to the Web.config files in the Views directories. In the example above we have added the Website.Models namespace to the list so our views will have access to it and thus be able to call our extension method.

Note
If you use Areas you will need to add namespace nodes to each Web.config in each Areas//Views directory if you want them available. Right now it doesn't look like adding the node to the top level Web.config is supported.

With those minor details handled we can build our view content. We make the view strongly typed to our Website.Models.PageWithSelectList class and add a call to our new extension method, passing in the name of our DayOfWeek property to align the html element id with our view model field and the DaysOfWeek object from our view model for populating the select options.

@model PageWithSelectList
@{
    ViewBag.Title = "Index";
}
@Html.DropDownList("DayOfWeek", Model.DaysOfWeek)

Since we added the Website.Models namespace to the Web.config file in the Views directory we do not need to use the namespace in our @model declaration, nor do we need to include a @using declaration. However, if we didn't add the namespace to the Web.config our view would look like:

@using Website.Models
@model PageWithSelectList
@{
    ViewBag.Title = "Index";
}
@Html.DropDownList("DayOfWeek", Model.DaysOfWeek)

The resulting html that gets rendered from the view looks like so:


With that, we have added some decoupling love to the HtmlHelper.DropDownList method! But the "out of the box" helper is not completely without love. In fact, it is tricked out to make our life extremely easy when it comes to pre-selecting an option in our view. Since our extension method is handling the mapping of our data structure into a structure the existing HtmlHelper.DropDownList method supports we get all the benefits of the existing method. The method will look for an existing property in the view model that matches the name value passed into the DropDownList method and will set the Selected property on the SelectListItem that has a matching value. By adding the DayOfWeek property to our PageWithSelectList view model we can set that property in our HomeController.Index action method and the HtmlHelper.DropDownList method will take care of things from there prior to rendering the html.

Setting the property on our model in the action method:

using System.Web.Mvc;
using Website.Models;

namespace Website.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var model = new PageWithSelectList();
            model.DayOfWeek = 3;
            return View(model);
        }
    }
}

With no changes needed to our view, the resulting html that gets rendered:


Maybe a Dictionary is not ideal for what your application needs. No problem. Simply create extension methods that take in data structures that you need. If they implement IEnumerable you can use the same example code logic we have already set up. If they don't, just handle mapping your data structure manually in your extension method logic.

Don't like the notion of working with "magic strings"? Create an extension method for the HtmlHelper.DropDownListFor method:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace Website.Models
{
    public static class ExtensionMethods
    {
        public static MvcHtmlString DropDownList(this HtmlHelper helper, 
            string name, Dictionary dictionary)
        {
            var selectListItems = new SelectList(dictionary, "Key", "Value");
            return helper.DropDownList(name, selectListItems);
        }

        public static MvcHtmlString DropDownListFor(this HtmlHelper helper,
            Expression> expression, Dictionary dictionary)
        {
            var selectListItems = new SelectList(dictionary, "Key", "Value");
            return helper.DropDownListFor(expression, selectListItems);
        }
    }
}

Then in your view you can use a strongly typed expression to reference the view model property for the html select element name:

@model PageWithSelectList
@{
    ViewBag.Title = "Index";
}
@Html.DropDownListFor(i=>i.DayOfWeek, Model.DaysOfWeek)

Now get back to work and show those html select elements who's boss by bending your MVC code to your will!

Download the code

Discussion

Felix
Felix
22 Jun, 2011 08:29 PM

I was hoping that somebody else comments first, since I want to comment on something the author didn't say; rather something that he said (and the obvious response would be "so why wouldn't you write what you feel is missing" - and my only comeback would be "well, the author's writing skills are clearly much better than mine; and besides, English is my third language"). Anyway...

Dropdown boxes are indeed complicated animals, but simply showing them was never the most complex part. Typically, they are shown on data entry form (after all, if the user can select a value from the list, we most likely want to send this value to the server). So, there are four challenges that the developer needs to solve:

  1. Populate the list of values from the lookup table, and identify which one is selected (or add "Select one" in case of Create View)
  2. Collect the key value
  3. With least pain use it as part of ValueProvider and, in case of success, persist it along with all other object fields
  4. In case of failure make sure that the list of values is re-populated, and - depending on requirements - the newly selected value is retained when the View is re-displayed.

So, it's a combination of MVC, Entity Framework (or other ORM), and good old ASP.NET that creates the challenge. This post addresses just the first one. As my college professor liked to say - "it's a good start" :)

John
John
25 Jun, 2011 12:09 PM

Thanks, it's exactly what I needed. This will be very handy.

25 Jun, 2011 01:12 PM

Nice post....I'm going to updating my mvc3 app soon....

Max Campsell
Max Campsell
26 Jun, 2011 01:53 AM

Classy code. Is it possible to create a Helper that will take a ViewBag with the list data?

var model = new PageWithSelectList();            
model.DayOfWeek = 3;
ViewData["TestData"] = model;

and then in the view;

@Html.DropDownList("DayOfWeek", ViewData["TestData"])

as I wish to populate say three drop down lists. eg; DaysOfWeek, MonthsOfYear, Next10Years

Thanigainathan
Thanigainathan
26 Jun, 2011 02:37 AM

Hei,

MVC is not allowing me to create a empty dropdown . <%=Html.DropdownList("somename",null)>

Here I could not give the data as null. please advise

Felix
Felix
27 Jun, 2011 02:34 PM

@Thanigainathan,

My advise would be not to give the data as null. Don't know why you think it should be possible, but MVC will not allow you to do that. For empty dropdown use empty list (check MSDN documentation for more details)

Hope that helps!

Rais Hussain
28 Jun, 2011 05:01 AM

Nice post. I have multiple DropDownList, like master and child, is it possible to provide an ajax based DropDownList which we can determine that the data will be populated by ajax request or just with a simple non-ajax request?

cyclion
cyclion
02 Aug, 2011 11:34 AM

Thanks a lot. Very helpful.

Dave
Dave
12 Sep, 2011 01:57 PM

I try not to be discouraging, and for the most part I like what you have done. But I kinda think it is an anti-pattern.

With ASP.NET MVC 3, whilst the View engine may change, you're always going to be using the same supporting infrastructure. Extricating the controllers and models from that infrastructure would be more trouble than it is worth, should you decide to go off in another direction.

Decoupling is a great. But not in every context. This seems to be decoupling for the sake of decoupling.

It reminds me of a conversation I once had with another developer about the Web Client Software Factory. The only alternative view that you would ever put on that is ... another version of ASP.NET (Webforms).

Tohid Azizi
Tohid Azizi
29 Sep, 2011 02:41 PM

@Felix

Did you find any solution for "selected item"?

In edit scenario, DropDownListfor(model, selectedList) does not generate selected="selected":


for the current value of the model.

Any solution?

Joe
Joe
03 Oct, 2011 09:46 AM

@Dave- I actually like this pattern much better than Scott Allens solution.

http://odetocode.com/blogs/scott/archive/2010/01/18/drop-down-lists-and-asp-net-mvc.aspx

I looked at Scott's solution, and my first thought was "why do I have to use the mvc namespace for this?" I was very happy to find this alternative, more generic solution.

Maybe you will call it purist, but I like ViewModels to remain as UI agnostic as possible. And allowing the MVC namespace into the castle will only invite more transgressions in the future.

But my biggest reason for think this is: MVVM and Silverlight. For me, I want to get the most out of my ViewModels, and wish to reuse across MVC and Silverlight and whatever else will come down the pike. If I start using MVC in them, that kills that opportunity.

And lastly, This solution is about the same level of complexity than Scotts, so why not get that extra level of flexbility?

Brian
Brian
13 Oct, 2011 09:14 AM

@Html.DropDownListFor(c=>c.CampusId, Model.CampusDictionary)

The code above is rendering then all my options.

Any ideas why? Thanks for your help.

Brian
Brian
13 Oct, 2011 09:16 AM
``

The above code is rendering my select open and close then my options.

Any help is appreciated.

Felix
Felix
07 Nov, 2011 12:25 AM

@Tohid Azizi

I don't have a problem with selected item and therefore I was not looking for solution. In my edit scenario, DropDownListFor(model => model.id, selectedList) generates everything it's supposed to.

Seems that you have a bug in your code and are asking for help without showing the code. For example, how would anybody guess what is in your model, and how would anybody guess that it indeed matches the dropdown list.

In any event, if you need help, Stack Overflow is probably a better place to ask.

Robert
Robert
08 Dec, 2011 04:29 PM

@MAX - you can do that with the ViewData. I too have several dropdowns and this just worked out better for me.

Controller:

var states = new StatesList();
ViewData["States"] = states.States;

View:

@Html.DropDownList("States", ViewData["States"] as Dictionary)

No new comments are allowed on this post.