I really like ASP.NET MVC, but understanding the usage of the DropDownListFor HTML helper method is not trivial. Therefore I decided to simplify it by introducing the following HTML extension methods for rendering a DropDownList. The idea is that the data for a dropdownlist is encapsulated in an IDictionary object.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.Mvc; using System.Linq.Expressions; using System.Diagnostics.CodeAnalysis; using System.Web.Routing; using System.Web.Mvc.Html; namespace My.Helpers.HTML { public static class CuSelectExtensions { /// <summary> /// returns an HTML select element for an IDictionary /// </summary> /// <typeparam name="TModel"></typeparam> /// <typeparam name="TKey"></typeparam> /// <typeparam name="TValue"></typeparam> /// <param name="htmlHelper"></param> /// <param name="funcForDict">a function that returns an IDictionary</param> /// <param name="expForSelectedKey">a expression which returns the selected Key of the IDictionary</param> /// <returns></returns> public static MvcHtmlString DropDownListFor<TModel, TKey, TValue>(this HtmlHelper<TModel> htmlHelper, Func<TModel, IDictionary<TKey, TValue>> funcForDict, Expression<Func<TModel, TKey>> expForSelectedKey) { return htmlHelper.DropDownListFor(expForSelectedKey, funcForDict.Invoke(htmlHelper.ViewData.Model).ToSelectList(expForSelectedKey.Compile().Invoke(htmlHelper.ViewData.Model))); } /// <summary> /// Converts a dictionary to a SelectList /// </summary> /// <typeparam name="TKey"></typeparam> /// <typeparam name="TValue"></typeparam> /// <param name="dictionary"></param> /// <returns></returns> public static SelectList ToSelectList<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) { //call with default value return dictionary.ToSelectList(default(TKey)); } /// <summary> /// Converts a dictionary to SelectList /// </summary> /// <typeparam name="TKey"></typeparam> /// <typeparam name="TValue"></typeparam> /// <param name="dictionary"></param> /// <param name="selectedItem"></param> /// <returns></returns> public static SelectList ToSelectList<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey selectedItem) { return new SelectList(dictionary, "Key", "Value", selectedItem); } } }
Emphasizing the importance to divide view model from data model is fundamental for creating a good MVC project. Keeping in mind this aspect you will notice the importance and the goodness of the extension methods I introduced above. Let’s suppose we have the following data model and we want to render a form for collecting this kind of data.
public class Address { public virtual int? ID { get; set; } public virtual State State { get; set; } public virtual string Province { get; set; } public virtual string City { get; set; } public virtual string Street { get; set; } public virtual string PostCode { get; set; } } public class State { public virtual int? ID { get; set; } public virtual string Desc { get; set; } }
Then our view model will look like this:
public class AddressView { public IDictionary<int, string> AvailableStates { get; set; } public int SelectedState { get; set; } public string Province { get; set; } public string City { get; set; } public string Street { get; set; } public string PostCode { get; set; } }
In the following controller, which access the Business-Logic (BL) for retrieving the Address object and which again is built on top of the DataAccess-Logic (DAL), we convert an Address object into an AddressView object and we pass it to a View for rendering it. For simplifying this example I am going to hide the implementation of the BL and DAL.
public ActionResult Edit(int id) { Address ad = AddressBL.GetByID(id); IList<State> states = StateBL.GetAll(); //Convert datamodel to viewmodel AddressView av = new AddressView(); av.AvailableStates = states.ToDictionary(x => x.ID, x => x.Desc); dv.selectedState = ad.State.ID; av.Province = ad.Province // . . . return View(av); }
Finally, using our proposed implementation for DropDownlistFor in a view is very easy. It is enough to specify an IDictionary object and a Key for the selected element. Here is an example:
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" Inherits="ViewPage<AddressView>" %> <%@ Import Namespace="My.Helpers.HTML" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <%: Html.DropDownListFor(x => x.States, x => x.AvailableStates)%> </asp:Content>