A Property by Any Other Name (Custom ModelMetadataProviders)

Html Helpers go a long way to help reduce some of the redundant code we need to write on a day to day basis but there’s one thing they lack out of the box and that’s user friendly display name rendering.

Take the following example:

public class Person {

   public string FirstName { get; set; }

   public string LastName { get; set; }

}

Now go and use a built-in Html.LabelFor to generate a label for either of these fields and you get lovely labels displaying such names as FirstName and LastName. We could fix these display names by adding [Display] attributes to all of our properties such as:

public class Person {

   [Display(Name="First Name")]
   public string FirstName { get; set; }

   [Display(Name="Last Name")]
   public string LastName { get; set; }

}

but I prefer something a little less involved.

First we must figure out what is responsible for generating these names and with a little searching you’ll find out that we’re looking for a way to hook into the ModelMetadataProvider that is responsible, specifically a DataAnnotationsModelMetadataProvider (boring definition stuff). With a little code we can have all of our Html.LabelFor and Html.DisplayNameFor helpers generating nice display names for all of our fields without the extra data attributes. BONUS these also carry over to model state errors as well!

Ready? Good! We start by creating our new provider:

    public class ModelMetadataConventionProvider : DataAnnotationsModelMetadataProvider
    {
        protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType,
            Func<object> modelAccessor, Type modelType, string propertyName)
        {
            var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

            if (metadata.DisplayName == null) {
                metadata.DisplayName = metadata.PropertyName.ToSeparatedWords();
            }

            return metadata;
        }
    }

The highlighted bit above is where the magic happens. First it checks to see if the current property being referenced has a DisplayName, something it would have if we gave it one using [Display(Name="Field Name")]. If a DisplayName is not defined we use our custom logic and split the PropertyName to separate words based on title casing. The string extension, ToSeparatedWords is included below.

    public static class StringExtensions
    {
        public static string ToSeparatedWords(this string value)
        {
            return value != null ? Regex.Replace(value, "([A-Z][a-z]?)", " $1").Trim() : null;
        }
     }

Nothing special here just some Regex.Replace magic that does the string splitting for us.

Next we register this new provider in our Global.asax:

        protected void Application_Start()
        {
            // other calls removed

            ModelMetadataProviders.Current = new ModelMetadataConventionProvider();
        }

and we are good to go.

All of our Html.LabelFor, Html.DisplayFor helpers and validations should now be showing our nice clean display names.

So what happens when this tweak doesn’t generate the display names we want, why we resort back to the [Display(Name="The Name I Really Want")] attribute to give the field the name we want.

Interested in what else we can do with our new provider? A Google search also returned another use in which you could translate labels on the fly, think multilingual stuff [Stack Overflow].

Leave a Reply

Your email address will not be published. Required fields are marked *