Ordering fields in ASP.NET MVC 2 templated helpers

Edit on GitHub

Ever worked with the templated helpers provided by ASP.NET MVC 2? Templated helpers provide a way to automatically build UI based on a data model that is marked with attributes defined in the System.ComponentModel.DataAnnotations namespace. For example, a property in the model can be decorated with the attribute [DisplayFormat(DataFormatString = "{0:c}")], and the templated helpers will always render this field formatted as currency.

If you have worked with templated helpers, you must agree: they can be useful! There’s one thing which is impossible in the current version: ordering fields.

kick it on DotNetKicks.com

Take the following class and the rendered form using templated helpers:

ASP.NET MVC EditorForModel()

[code:c#]

public class Person
{
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

[/code]

Nice, but I would rather have the field “Email” displayed third. It would be nice if the field order could be applied using the same approach as with the System.ComponentModel.DataAnnotations namespace: let’s build us an attribute for it!

Building the OrderAttribute

Assuming you have already built an attribute once in your life, let’s go over this quickly:

[code:c#]

[global::System.AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class OrderAttribute : Attribute
{
    readonly int order;

    public OrderAttribute(int order)
    {
        this.order = order;
    }

    public int Order
    {
        get { return order; }
    }
}

[/code]

The OrderAttribute can be applied to any property of a model, and needs exactly one parameter: order. This order will be used to sort the fields being rendered. Here’s how our Person class may look like after applying the OrderAttribute:

[code:c#]

public class Person
{
    [Order(3)]
    public string Email { get; set; }

    [Order(1)]
    public string FirstName { get; set; }

    [Order(2)]
    public string LastName { get; set; }
}

[/code]

Speaks for itself, no? Now, before you stop reading: this will not work yet. The reason is that the default ModelMetadataProvider from the ASP.NET MVC framework, which provides the templated helpers all information they need about the model, does not know about this OrderAttribute. Let’s see what we can do about that…

Building the OrderedDataAnnotationsModelMetadataProvider

In order for the ASP.NET MVC framework to know and use the OrderAttribute created previously, we’re going to extend the default DataAnnotationsModelMetadataProvider provided with ASP.NET MVC 2. Here’s the code:

[code:c#]

public class OrderedDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    public override IEnumerable<ModelMetadata> GetMetadataForProperties(
        object container, Type containerType)
    {
        SortedDictionary<int, ModelMetadata> returnValue =
            new SortedDictionary<int, ModelMetadata>();

        int key = 20000; // sort order for "unordered" keys

        IEnumerable<ModelMetadata> metadataForProperties =
            base.GetMetadataForProperties(container, containerType);

        foreach (ModelMetadata metadataForProperty in metadataForProperties)
        {
            PropertyInfo property = metadataForProperty.ContainerType.GetProperty(
                metadataForProperty.PropertyName);

            object[] propertyAttributes = property.GetCustomAttributes(
                typeof(OrderAttribute), true);

            if (propertyAttributes.Length > 0)
            {
                OrderAttribute orderAttribute = propertyAttributes[0] as OrderAttribute;
                returnValue.Add(orderAttribute.Order, metadataForProperty);
            }
            else
            {
                returnValue.Add(key++, metadataForProperty);
            }
        }

        return returnValue.Values.AsEnumerable();
    }
}

[/code]

By overriding the GetMetadataForProperties, we’re hooking into the DataAnnotationsModelMetadataProvider’s moment of truth, the method where all properties of the model are returned as ModelMetadata. First of all, we’re using the ModelMetadata the base class provdes. Next, we use a little bit of reflection to get to the OrderAttribute (if specified) and use it to build a SortedDictionary of ModelMetadata. Easy!

One small caveat: non-decorated properties will always come last in the rendered output.

One thing left…

One thing left: registering the OrderedDataAnnotationsModelMetadataProvider with the ModelMetadataProviders infrastructure offered by ASP.NET MVC. Here’s how:

[code:c#]

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterRoutes(RouteTable.Routes);

    ModelMetadataProviders.Current = new OrderedDataAnnotationsModelMetadataProvider();
}

[/code]

I guess you know this one goes into Global.asax.cs. If all works according to plan, your rendered view should now look like the following, with the e-mail field third:

image

kick it on DotNetKicks.com

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

7 responses

  1. Avatar for Sniffer
    Sniffer January 11th, 2010

    Rather than annotating each field with an attribute, wouldn't it be easier just order your Person object correctly in the first place?

  2. Avatar for maartenba
    maartenba January 11th, 2010

    True, but what will happen afterwards. Or when you have 3 people working on an application and doing a merge in source control, totally ignoring field order?

  3. Avatar for Vijay santhanam
    Vijay santhanam January 12th, 2010

    nice! this is a great demo of adding custom view meta data. to simplify view creation.

  4. Avatar for Khalid Abuhakmeh
    Khalid Abuhakmeh January 12th, 2010

    So why would I use this over modifying the view directly? It's not like my view cares about the order of the properties on my object.

  5. Avatar for maartenba
    maartenba January 12th, 2010

    It's useful when working with the Html.EditorForModel() templated helpers in MVC2, where you don't have to create the view yourself.

  6. Avatar for Vijay santhanam
    Vijay santhanam January 12th, 2010

    decorating your Models (or more appropriately, ViewModels) with presentation metadata (like attributes) means you can create more generic CRUD (Create, Read,Update, Delete) aspx/ascx view templates without having to create views for each model type.

  7. Avatar for Arnis L.
    Arnis L. January 13th, 2010

    Don't care about sorting issues at the moment, but this post is a great tutorial about creating custom provider. Just what i needed.