Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET MVC: apply templates to enum properties

0.00/5 (No votes)
9 Apr 2014 1  
How to use templates in ASP.NET MVC to edit enum fields

Introduction

In almost every application I write there is some class with enum properties that should be displayed or edited on the UI. In MVC I use templates and custom metadata providers to manage enum based properties with particular attention to localization issues.

Enum values decoration

The first thing to manage is the label of each enum value.

For labeling I use DisplayAttribute from data annotation library. This attribute let me set a name for the value and also allow me to use resources, when needed, to resolve localization requirements.

As an example I can define a enum for car types:

public enum CarTypes
    {
    [Display(ResourceType = typeof(StringResources), Name = "Hatchback")]
    Hatchback,
    [Display(ResourceType = typeof(StringResources), Name = "Sedan")]
    Sedan,
    [Display(ResourceType = typeof(StringResources), Name = "StationWagon")]
    StationWagon,
    [Display(ResourceType = typeof(StringResources), Name = "SportsCar")]
    SportsCar,
    [Display(ResourceType = typeof(StringResources), Name = "Convertible")]
    Convertible,
    [Display(ResourceType = typeof(StringResources), Name = "OffRoader")]
    OffRoader,
    [Display(ResourceType = typeof(StringResources), Name = "MPV")]
    MPV,
    [Display(ResourceType = typeof(StringResources), Name = "Van")]
    Van
}

Templates

Next I define a display and a editor template that works for every enum type.

In display template I retrieve the display attribute (if exists) for the current value (Model property of template page) and print is as is. If display attribute is not defined for the current value I simply print the Model.

@using System.ComponentModel.DataAnnotations

@{
    var type = (Type) Model.GetType();
    var field = type.GetField(Model.ToString());
    if (field != null)
    {
        var display = ((DisplayAttribute[])field.GetCustomAttributes(typeof(DisplayAttribute), false)).FirstOrDefault();
        if (display != null)
        {
            @display.GetName()
        } else {
            @Model
        }
    }
}

For editor template I retrieve the enum of the model and then I analyze each value of enum (using Enum.GetValues) to retrieve a display attribute if provided.
Then I use HtmlHelper.DropDownList extension to show a dropdown list for the field.

Note that calling DropDownList with a empty string for name property automatically set id of html select tag to name of the original property.

@using System.ComponentModel.DataAnnotations

@{
    var selectList = new List<SelectListItem>();
    string optionLabel = null;
    object htmlAttributes = null;
    var enumType = Model.GetType();
    foreach( var value in Enum.GetValues(enumType) )
    {
        var field = enumType.GetField(value.ToString());
        var option = new SelectListItem {Value = value.ToString() };        
        var display = ((DisplayAttribute[])field.GetCustomAttributes(typeof(DisplayAttribute), false)).FirstOrDefault();
        if (display != null)
        {
            option.Text = display.GetName();
        } else {
            option.Text = value.ToString();
        }
        option.Selected = value == Model;
        selectList.Add(option);
    }
}

@Html.DropDownList("", selectList, optionLabel, htmlAttributes).

Custom Metadata Provider

At this point I can use the templates simply adding a UIHintAttribute to each property of business objects that use an enum type.

public class Car
    {
        public int Id { get; set; }

        [UIHint("Enum")]
        public CarTypes Type { get; set; }

        public string Brand { get; set; }

But this approach requires to add an attribute for each property in each object.
In addition the UIHintAttribute is, in my opinion, strongly connected to UI layers so i dislike to add it to object defined in other layers of the application.

To solve this problem I override CreateMetadata method in a metadata provider derived from default DataAnnotationsModelMetadataProvider.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;

namespace EnumTemplates
{
    /// <summary>
    /// Metadata provider derived from DataAnnotationsModelMetadataProvider that 
    /// add Enum TemplateHint for enumeration types.
    /// To use this provider set ModelMetadataProviders.Current in Application_Start
    /// 
    public class CustomMetadataProvider : DataAnnotationsModelMetadataProvider
    {
        protected override ModelMetadata CreateMetadata(IEnumerable<attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
        {
            var meta = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
            if (modelType.IsEnum && string.IsNullOrEmpty(meta.TemplateHint))
                meta.TemplateHint = "Enum";
            return meta;
        }
    }
}</attribute>

As you can see I set the TemplateHint property only if it wasn't already defined, so it's possible to use other templates for specific properties.

Next I set the new provider in Application_Start method of global.asax.

ModelMetadataProviders.Current = new CustomMetadataProvider();

Use provided templates

At this point using provided templates is as simple as use HtmlHelper.DisplayFor and HtmlHelper.EditorFor functions

Using it in details and edit pages automatically display correct labels and select controls in pages.

Sample project

Attached to this article there is a small ASP.NET MVC project that use the described approach.

You can download it here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here