HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Implementing a generic DropDownList attribute and templating solution

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
generictemplatingdropdownlistattributeandsolutionimplementing

Problem

My goal was to be able to render my view models using a standard Object.cshtml editor and display template. In order to do so I needed to be able to always call Html.Editor(propertyName) or Html.Display(propertyName) in order to render the HTML element.

There were plenty of resources for creating custom attributes and custom editor templates. However I ran into a wall when creating the DropDownListAttribute and template since I needed to obtain the values for the select list as well as set the selected item based on the current instance of the view model.

Here is the reference I used to create the custom ModelMetadataProvider and custom attributes.

Here is the problem I ran into with getting the current instance of the view model.

It turns out using reflection here to access the view model is not safe. Depending on how you iterate over the property metadata the view model can sometimes be null.

Here is my implementation using the resource referenced above. This is a simple version that only uses a List for the DropDownList. Using this example it would be trivial to use dictionaries and set the text/value properties:

View Model:

public class PhoneNumberViewModel
{
    // ... normal properties and attributes
    [DropDownList("ContactTypes")]  //  ContactTypes
    {
        get
        {
            return new List()
            {
                "Home", "Work", "Mobile"
            }
        }
    }
}


DropDownListAttribute Implementation

public class DropDownListAttribute : MetadataAttribute
{
    private string _items;

    public DropDownListAttribute(string items)
    {
        _items = items;
    }

    public override void Process(System.Web.Mvc.ModelMetadata modelMetaData)
    {
        modelMetaData.AdditionalValues.Add("Items", _items);
        modelMetaData.TemplateHint = TemplateHints.DropDownList; // Just a const string
    }
}


Custom HTML Helper used to render elements that need access to the view model instance

``

Solution

Why not just do this?

public class PhoneNumberViewModel
{
    // ... normal properties and attributes
    [DropDownList("ContactTypes")]  //  ContactTypes
    {
        get { return new[] { "Home", "Work", "Mobile" }.ToSelectList(); }
    }
}

public static class ExtensionsForSelectListItem
{
    public static IEnumerable ToSelectList(this IEnumerable items)
    {
        return items.Select(x => new SelectListItem { Value = x });
    }

    public static MvcHtmlString ContextualEditor(this HtmlHelper html, string expression)
    {

        var model = html.ViewData.Model;

        var propertyMetadata = ModelMetadata.FromStringExpression(expression, html.ViewData);

        switch (propertyMetadata.TemplateHint)
        {
            case TemplateHints.DropDownList:
                var items = (IEnumerable)model.GetType().GetProperty((string)propertyMetadata.AdditionalValues["Items"]).GetValue(model, null);
                return html.DropDownList(expression, items);
            default:
                return System.Web.Mvc.Html.EditorExtensions.Editor(html, expression);
        }
    }

}


I didn't test the code to get the items. But I'm assuming that you had that part working.

I also authored my own HTML Helper Library if you want check out some other examples.

https://bitbucket.org/grcodemonkey/buildmvc

Code Snippets

public class PhoneNumberViewModel
{
    // ... normal properties and attributes
    [DropDownList("ContactTypes")]  // <-- This is the important one!
    public string ContactType { get; set; }

    public static IEnumerable<SelectListItem> ContactTypes
    {
        get { return new[] { "Home", "Work", "Mobile" }.ToSelectList(); }
    }
}

public static class ExtensionsForSelectListItem
{
    public static IEnumerable<SelectListItem> ToSelectList(this IEnumerable<string> items)
    {
        return items.Select(x => new SelectListItem { Value = x });
    }

    public static MvcHtmlString ContextualEditor(this HtmlHelper html, string expression)
    {

        var model = html.ViewData.Model;

        var propertyMetadata = ModelMetadata.FromStringExpression(expression, html.ViewData);


        switch (propertyMetadata.TemplateHint)
        {
            case TemplateHints.DropDownList:
                var items = (IEnumerable<SelectListItem>)model.GetType().GetProperty((string)propertyMetadata.AdditionalValues["Items"]).GetValue(model, null);
                return html.DropDownList(expression, items);
            default:
                return System.Web.Mvc.Html.EditorExtensions.Editor(html, expression);
        }
    }


}

Context

StackExchange Code Review Q#4899, answer score: 3

Revisions (0)

No revisions yet.