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>