Article Recognized as "Article of The Day" by Microsoft on Microsoft's Community Site "www.asp.net".

Generic Custom Html Helper (Razor) for Dropdown List created by List of Objects

Generally in custom Html Helper you have to pass either list of SelectListItem or list of specific class. But if you have data in list of some specific class (Ex. Customer) then you are bound to first convert it to either in list of SelectListItem or list of a standard class asked as an argument in custom Html Helper. This article explains how to create custom Html Helper for DropdownList without having this boundation. You can pass any type of class to this helper and it will create Dropdown list.

For Example, we need to create Dropdown list for Customer list and Country list. Here are the models for these 2 classes.

Customer.cs
public class Customer
{
    public int Customer_Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
}
Country.cs
public class Country
{
    public int Country_Id { get; set; }
    public string Name { get; set; }
}
VM_Customer.cs

Virtual model having list of Customer class and Country class with corresponding properties in which Dropdown's selected value will be posted.

public class VM_Customer
{
    public List<Customer> CustomerList { get; set; }
    public List<Country> CountryList { get; set; }
    public int CustomerId { get; set; }
    public int CountryId { get; set; }
}

Normal Dropdown List

HtmlHelper class
public static partial class HtmlHelperExtensions
{
    /// <summary>
    /// Creates Dropdown List for specified list of objects
    /// </summary>
    /// <typeparam name="T">Type of object for which Dropdown list needs to be created</typeparam>
    /// <param name="htmlHelper">HtmlHelper class</param>
    /// <param name="name">Name of Dropdown List</param>
    /// <param name="itemList">List of class having data to be bound in Dropdown List</param>
    /// <param name="valueField">Property name of class whose value will be used as value of Dropdown List</param>
    /// <param name="textField">Property name of class whose value will be used as text of Dropdown List</param>
    /// <param name="value">Selected value of Dropdown List</param>
    /// <param name="optionLabel">Option Label of Dropdown List</param>
    /// <param name="cssClass">CSS Class to be applied on Dropdown List control</param>
    /// <returns></returns>
    public static MvcHtmlString CustomDropdownList<T>(this HtmlHelper htmlHelper,
        string name, List<T> itemList, string valueField, string textField, int? value, 
        string optionLabel, string cssClass) where T : new()
    {
        StringBuilder sb = new StringBuilder();
        TagBuilder option;
        TagBuilder select = new TagBuilder("select");
        select.MergeAttribute("name", name, true);
        select.GenerateId(name);
        select.MergeAttribute("class", cssClass);
        select.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name));
        sb.Append(select.ToString(TagRenderMode.StartTag));
        if (!string.IsNullOrEmpty(optionLabel) && optionLabel != "null")
        {
            option = new TagBuilder("option");
            option.SetInnerText(optionLabel);
            option.MergeAttribute("Value", "0");
            if (value == 0)
            {
                option.MergeAttribute("Selected", "true");
            }
            sb.Append(option.ToString(TagRenderMode.Normal));
        }
        foreach (T ddl in itemList)
        {
            Type type = ddl.GetType();
            option = new TagBuilder("option");
            option.SetInnerText(Convert.ToString(type.GetProperty(textField).GetValue(ddl, null)));
            option.MergeAttribute("Value", Convert.ToString(type.GetProperty(valueField).GetValue(ddl, null)));
            if (Convert.ToInt32(type.GetProperty(valueField).GetValue(ddl, null)) == value)
            {
                option.MergeAttribute("Selected", "true");
            }
            sb.Append(option.ToString(TagRenderMode.Normal));
        }
        sb.Append(select.ToString(TagRenderMode.EndTag));
        return MvcHtmlString.Create(sb.ToString());
    }
}

CustomDropdownList method will be used to create custom Html Helper of Dropdown List.

Example.cshtml
@model Test.Models.VM_Customer
@using Test.Helpers;
@using (Html.BeginForm())
{
    <div class="row">
        <div class="col-xs-2">
            <h6>Customer</h6>
        </div>
        <div class="col-xs-4">
            @(Html.CustomDropdownList<Test.Models.Customer>("CustomerId", Model.CustomerList, 
                "Customer_Id", "FirstName", Model.CustomerId, null, "form-control"))
        </div>
        <div class="col-xs-2">
            <h6>Country</h6>
        </div>
        <div class="col-xs-4">
            @(Html.CustomDropdownList<Test.Models.Country>("CountryId", Model.CountryList, 
                "Country_Id", "Name", Model.CountryId, null, "form-control"))
        </div>
    </div>
    <input type="submit" value="Save" />
}

Now after selecting value in Dropdown list, if you will submit your page then selected value will be posted in "CustomerId" and "CountryId" properties of virtual model.

In this method selected value is integer type but there may be a requirement that selected value needs to be string. In that case you just need to change argument type and single line of code in method:

//Use
if (string.IsNullOrEmpty(value))

//in place of
if (value == 0)

Multi-Select Dropdown List

For creating multi-select Dropdown list you need to use this method in custom html helper:

/// <summary>
/// Creates Dropdown List for specified list of objects
/// </summary>
/// <typeparam name="T">Type of object for which Dropdown list needs to be created</typeparam>
/// <param name="htmlHelper">HtmlHelper class</param>
/// <param name="name">Name of Dropdown List</param>
/// <param name="itemList">List of class having data to be bound in Dropdown List</param>
/// <param name="valueField">Property name of class whose value will be used as value of Dropdown List</param>
/// <param name="textField">Property name of class whose value will be used as text of Dropdown List</param>
/// <param name="value">Selected values of Dropdown List</param>
/// <param name="optionLabel">Option Label of Dropdown List</param>
/// <param name="cssClass">CSS Class to be applied on Dropdown List control</param>
/// <returns></returns>
public static MvcHtmlString CustomDropdownList<T>(this HtmlHelper htmlHelper,
    string name, List<T> itemList, string valueField, string textField, int[] value, 
    string optionLabel, string cssClass) where T : new()
{
    StringBuilder sb = new StringBuilder();
    TagBuilder option;
    TagBuilder select = new TagBuilder("select");
    select.MergeAttribute("multiple", "multiple");
    select.MergeAttribute("name", name);
    select.MergeAttribute("id", name);
    select.MergeAttribute("class", cssClass);
    select.AddCssClass("multi");
    select.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name));
    sb.Append(select.ToString(TagRenderMode.StartTag));
    foreach (T ddl in itemList)
    {
        Type type = ddl.GetType();
        option = new TagBuilder("option");
        option.SetInnerText(Convert.ToString(type.GetProperty(textField).GetValue(ddl, null)));
        option.MergeAttribute("Value", Convert.ToString(type.GetProperty(valueField).GetValue(ddl, null)));        
        if (value != null)
        {
            if (value.Contains(Convert.ToInt32(type.GetProperty(valueField).GetValue(ddl, null))))
            {
                option.MergeAttribute("Selected", "true");
            }
        }
        sb.Append(option.ToString(TagRenderMode.Normal));
    }
    sb.Append(select.ToString(TagRenderMode.EndTag));
    return MvcHtmlString.Create(sb.ToString());
}
Example.cshtml
@model Test.Models.VM_Customer
@using Test.Helpers;
@using (Html.BeginForm())
{
    <div class="row">
        <div class="col-xs-2">
            <h6>Customer</h6>
        </div>
        <div class="col-xs-4">
            @(Html.CustomDropdownList<Test.Models.Customer>("CustomerId", Model.CustomerList, 
                "Customer_Id", "FirstName", Model.CustomerIds, null, "form-control"))
        </div>
        <div class="col-xs-2">
            <h6>Country</h6>
        </div>
        <div class="col-xs-4">
            @(Html.CustomDropdownList<Test.Models.Country>("CountryId", Model.CountryList, 
                "Country_Id", "Name", Model.CountryIds, null, "form-control"))
        </div>
    </div>
    <input type="submit" value="Save" />
}

Here CustomerIds and CountryIds are integer array type properties having selected values of customer and country Dropdown lists.


So you do not need do any conversion here. You just need to specify class type and pass list of objects. Html Helper will automatically create Dropdown list as per specified arguments. How it looks? Simple :)