Improving upon ASP.NET MVC's default display annotation convention
Posted on Tuesday, 4th November 2014
ASP.NET MVC is great at taking a lot of the grunt work out of creating forms. However, one area that I’ve always felt was lacking is the way ASP.NET MVC handles outputting form property names.
As it stands, ASP.NET MVC’s default behavior when outputting property names (using @Html.LabelFor() for example) is to output the property name as is. For instance, below highlights how the property name would appear if being output via a LabelFor() HTML helper.
public class ExampleViewModel
{
public string Surname { get; set; } //Output: "Surname"
public string FirstName { get; set; } //Output: "FirstName"
public string age { get; set; } //Output: "age"
}
Whilst the output of the Surname property is fine, given that it’s a single word; you’ll notice that the output of the FirstName property is less than desirable and should instead be displayed as two separate words if it is to be read correctly.
If you wish to update the way the FirstName property is displayed within a view then the common solution is to use the ASP.NET’s Display annotation like so:
public class ExampleViewModel
{
[Display(Name = "Surname")]
public string Surname { get; set; } //Output: "Surname"
[Display(Name = "First Name")]
public string FirstName { get; set; } //Output: "First Name"
[Display(Name = "Age")]
public string age { get; set; } //Output: "Age"
}
Why I’m not a huge fan of Display annotations
Whilst this solution will work, I’m not a huge fan of it as I generally try avoid muddying my objects with annotations if I can; especially annotations that are view specific such as the Display annotation.
Whilst I appreciate this is a view model, containing data that is destined for the view, I would argue that a view model’s purpose is to represent only the data - rather than how it is displayed or formatted within the UI. That’s what HTML is for, marking up our data. If we were to output the view model above via a Restful API or web service then our display annotations suddenly become irrelevant.
Another issue with the overuse of data annotations is they can really start to get messy. Take the following model for instance.
public class LoginModel
{
[Display(Name = "Account User Name")]
[Required(ErrorMessage = "Please fill in your user name.")]
[MaxLen(30)]
public string AccountUserName { get; set; }
[Display(Name = "Account Password")]
[Required(ErrorMessage = "Please fill in your password.")]
[MaxLen(30)]
public string AccountPassword { get; set; }
}
Instead I tend to favor a conventional or configuration approach to such a problem - and thankfully, due to ASP.NET MVC’s extensibility, we can create our own conventional approach to displaying property names without the need to pollute our view model or data transfer objects with UI specific Display annotations.
Harnessing the power of the ModelMetaDataProvider
In order to apply our conventional approach to display properties within ASP.NET MVC we first need to look at where the display data is coming from when using an extension method such as the @Html.LabelFor() extension used in our example above.
If we dig into the LabelFor extension (I use ReSharper which has a built in .NET de-compiler) you’ll see the LabelFor accesses the display property via an implementation of ModelMetadataProvider:
internal static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText, IDictionary<string, object> htmlAttributes, ModelMetadataProvider metadataProvider)
{
return LabelExtensions.LabelHelper((HtmlHelper) html, ModelMetadata.FromLambdaExpression<TModel, TValue>(expression, html.ViewData, metadataProvider), ExpressionHelper.GetExpressionText((LambdaExpression) expression), labelText, htmlAttributes);
}
After a little further digging you’ll find that the ModelMetadataProvider is a base class of DataAnnotationsModelMetadataProvider. If you take a moment to take a look at the DataAnnotationsModelMetadataProvider class and its implementation you’ll clearly see how ASP.NET MVC is accessing the Display attributes value and then returning an instance of ModelMetadata, which eventually ends up within the view helper and gets output in the view.
With this in mind, all we have to do in order to create our own conventional approach is create our own ModelMetadataProvider instance that overrides the current behavior and then register it with ASP.NET MVC.
Creating our own convention based ModelMetadataProvider
In my implementation, whilst I favor conventions over Display attributes, there may be instances where I will want to use a Display attribute, so I’m going to create a new class that extends ASP.NET MVC’s existing DataAnnotationsModelMetadataProvider implementation.
After a little work this is what I ended up with:
public class ConventionalModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
ModelMetadata modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (modelMetadata.DisplayName == null)
{
modelMetadata.DisplayName = modelMetadata.PropertyName.SplitWords();
}
return modelMetadata;
}
}
As you can see, all I’m doing here is calling the base (DataAnnotationsModelMetadataProvider) class’ implementation of the CreateMetadata method and updating the DisplayName property if it’s not set. This means that if the DataAnnotationsModelMetadataProvider class we’re extending does not find a Display data annotation on the property then it will return null. Then in our implementation we’re simply getting the property name from the ModelMetadata class and using an extension method I created (see below) to split the property name into words based on its upper-case letters, turning a string like “AddressLine1” into “Address Line 1”.
public static class StringExtensions
{
public static string SplitWords(this string value)
{
return value != null ? Regex.Replace(value, "([a-z](?=[A-Z0-9])|[A-Z](?=[A-Z][a-z]))", "$1 ").Trim() : null;
}
}
Registering our custom ModelMetadataProvider with ASP.NET MVC
Now all we have to do in order to use our conventional based approach to displaying property names is register our custom ModelMetadataProvider with ASP.NET MVC. This can be done by setting the Metadata provider within our website’s Global.asax.cs class, like so:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
ModelMetadataProviders.Current = new ConventionalModelMetadataProvider();
...
}
}
Once this is done you’ll now be able to remove your Display annotations and have property names output as they are, but split by their uppercase letter, whilst still allowing you to override our conventional behavior using a Display attribute if you need to.
Taking the ModelMetadataProvider further
Custom ModelMetadataProviders are a great way to harness the power of ASP.NET MVC model metadata functionality and opens up an array of possibilities. For instance, you can easily see how you could extend our solution above to query a database for field names, allowing us to have database driven properties - this same direction can be used for multilingual sites!
I hope you’ve found this guide blog post helpful and if you have any questions then please feel free to ask them in the comments section below.
Enjoy this post? Don't be a stranger!
Follow me on Twitter at @_josephwoodward and say Hi! I love to learn in the open, meet others in the community and talk Go, software engineering and distributed systems related topics.