Diving into ASP.NET MVC 3 Model Metadata Providers

Posted by: Raj Aththanayake , on 6/3/2011, in Category ASP.NET MVC
Views: 103936
Abstract: ASP.NET MVC provides a great support for DataAnnotation attributes which can be used to enable rich metadata behavior within MVC models. ASP.NET MVC 3 introduced the support for new DataAnnotation DisplayAttribute. ModelMetadataProvider can be used to create ModelMetadata based on the model’s attributes. In this article, we will explore how ModelMetadataProvider is extensible and we could easily create our own MetadataProvider to support any custom behavior. ASP.NET MVC 3 also introduced new registration point for IOC users to support registration of custom ModelMetadataProviders.

Storing metadata about your model is a great feature that ASP.NET MVC 2 introduced. These metadata are described by the DataAnnotation attributes which is built into .NET Framework. Metadata also enable a great way to specify Validation attributes, so the MVC Validation system can use those DataAnnotation Metadata attributes to provide Validation. In this article, we will discuss the DataAnnotation’s DisplayAttribute and how ModelMetadataProviders operate within ASP.NET MVC framework.

The difference between DisplayNameAttribute and MVC 3 new DisplayAttribute

You may already know that ASP.NET MVC 2 introduced the DisplayNameAttribute which is part of the System.ComponentModel namespace. This attribute allowed us to display the name of the customer as the “Customer Name” instead of the actual property name: “Name”.

 

mvc display name attribute

ASP.NET MVC 3 now supports DisplayAttribute in System.ComponentModel.DataAnnotation namespace. DisplayAttribute is new in .NET 4.

mvc display attribute

So what is the real difference between the “DisplayAttribute”, and the “DisplayNameAttribute”? They serve the same purpose, which is displaying a custom string, however the key difference is in the overloads they provide.

“DisplayAttribute” supports more overloads than the DisplayNameAttribute”.

display attribute param overload

DisplayNameAttribute” only supports a string which is the DisplayName.

display-name-attribute-param

Even though it doesn’t make sense to do so, what happens if we were to specify both attributes?

mvc display & display name attribute

In this situation, the new DisplayAttribute takes precedence over the DisplayNameAttribute. Instead of the “Customer name 2” we should see the “Customer name 1” displayed to the user.

Handing Resources

In some cases, we need the DisplayAttribute to be resource sensitive. For an example, instead of using non-localized resources, we might want to use the standard .NET resource provider to retrieve the localized resources. Please refer to MSDN documentation for more information on ASP.NET localization.

display-attribute

Below is a simple example on how to configure the resources so they can be displayed based on the localization.

resource-handling

image

As we did for the above DisplayAttribute, it is not straightforward to make DisplayNameAttribute to be resource sensitive. DisplayNameAttribute does not support a parameter for ResourceType. In that case, we need to subclass the DisplayNameAttribute and provide our own implementation similar to below.

image

Below is the usage of new sub classed attribute:

image

Annotating on MVC Model’s Metadata

So far we have discussed the System.ComponentModel.DataAnnotation.DisplayAttribute and how it is different to System.ComponentModel.DisplayNameAttribute.

Each metadata attribute provided by the System.ComponentModel.DataAnnotation has a specific purpose. Please refer to MSDN documentation for more information on each DataAnnotation attribute.

In this section, we will discuss how all DataAnnotation attributes are consumed within ASP.NET MVC 3 framework.

aspnet-mvc-provider-diagram

ModelMetatdataProviders

ModelMetadataProviders provide the currently configured ModelMetadataProvider. MVC uses a built-in ModelMetadataProvider which is called DataAnnotationsModelMetadataProvider. MVC ModelMetadataProvider can be extended to create our own ModelMetadataProvider. Below is an example on how we could implement our own custom ModelMetadataProvider (MyModelMetadataProvider) to consume a custom metadata attribute called MyMetaDataAttribute.

image

The custom ModelMetadataProvider needs to be registered so it can be used within the MVC framework.

We could use static registration to register a custom ModelMetadataProvider.

ModelMetadataProviders.Current = new MyModelMetadataProvider();

In ASP.NET MVC 3, we could also use our favourite IOC container to register a custom ModelMetadataProvider. For example, if the IOC container is the Unity container, we could register our own ModelMetadataProvider as below.

mvc model metadata provider

This also assumes that we have already configured the DependecyResolver as shown below.

DependencyResolver.SetResolver(new UnityDependecyResolver(unityContainer));

Once the DependecyResolver is configured, MVC uses the implementation of an IResolver<T> interface, which is SingleServiceResolver<TService> to resolve the custom implementation of a ModelMetadataProvider.

Within the implementation of SingleServiceResolver<TService>, there is a call to “DependencyResolver.Current” to retrieve the static instance of a currently configured DependencyResolver i.e UnityDependencyResolver. As specified in the MVC 3 ModelMetadataProviders.cs class, ModelMetadataProviders uses the resolver to discover the configured ModelMetadataProvider.

Model MetaData Provider Resolver

If the ModelMetaDataProvider is not resolvable via the configured UnityDependecyResolver, MVC falls back to the static registration point of the ModelMetadataProvider. If the same custom ModelMetadataProvider is configured via both IOC and via the static registration, MVC throws the following exception.

“An instance of ModelMetadataProvider was found in the resolver as well as a custom registered provider in ModelMetadataProviders.Current. Please set only one or the other.

DataAnnotationsModelMetadataProvider

mvc-data-annotations

DataAnnotationsModelMetadataProvider is the class which is ultimately responsible for creating Metadata based on the attributes defined by the System.ComponentModel.DataAnnotation namespace. We have discussed the DisplayAttribute as an example to provide certain metadata conditions such as displaying a custom name.

Whenever the system requests metadata associated for a certain property, MVC uses the currently configured ModelMetadataProvider to request metadata associated for the model’s property.

ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor, containerType, propertyName);

Since DataAnnotationsModelMetadataProvider does not override the GetMetadataForProperty method, MVC uses the AssociatedMetadataProvider’s GetMetadataForProperty method to discover the model’s metadata by passing the propertyName. In turns, AssociatedMetadataProvider calls CreateMetadata method which is overridden by the DataAnnotationsModelMetadataProvider’s CreateMetadata() to create model’s metadata.

image

Based on the property’s attribute(s), the above CreateMetadata() method creates a DataAnnotationsModelMetadata object for the associated property.

mvc-model-metadata

As we see in the above diagram, DataAnnotationsModelMetadata is derived from the ModelMetadata. This object is returned to the client i.e to an Html helper method and extracts the data associated for the property name. MVC constructs Html based on the returned object and send html to the browser.

An example would be if the Html helper is used to render a label, the metadata DisplayName can be extracted as below.

mvc label helper

Summary

ASP.NET MVC provides a great support for DataAnnotation attributes which can be used to enable rich metadata behaviour within MVC models. ASP.NET MVC 3 introduced the support for new DataAnnotation DisplayAttribute. ModelMetadataProvider can be used to create ModelMetadata based on the model’s attributes.

ModelMetadataProvider is extensible and we could easily create our own MetadataProvider to support any custom behaviour. ASP.NET MVC 3 also introduced new registration point for IOC users to support registration of custom ModelMetadataProviders.

The entire source code of this article can be downloaded over here

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Raj Aththanayake is a Microsoft ASP.NET Web Developer specializing in Agile Development practices such Test Driven Development (TDD) and Unit Testing. He is also passionate in technologies such as ASP.NET MVC. He regularly presents at community user groups and conferences. Raj also writes articles in his blog http://blog.rajsoftware.com. You can follow Raj on twitter @raj_kba


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Luko on Saturday, June 4, 2011 9:40 AM
Thanks for this one. I plan to use it in my app to extract metadata from an XML file.
Comment posted by derekthornton on Tuesday, June 7, 2011 1:23 PM
This is kind of pointless without the source code to try it out with.
Comment posted by derekthornton on Tuesday, June 7, 2011 1:29 PM
The sourcecode for the MyMetadataAttribute, rather. You left out the base part of the code that is needed to understand what in the world you're actually pulling data from.
Comment posted by Raj Aththanayake on Thursday, June 9, 2011 6:00 PM
Hi derekthornton - Source code soon to be published. However if you are waiting, below is an overview..
Just create your own attribute (in my case it is 'MyMetadataAttribute' - you may directly derive from an Attribute, or derived from an existing attribute i.e abstract Attribute class), and implement/override (if derived) the Process method. Process method takes ModelMetadata as an argument but ONLY execute if the attribute type is type of MyMetadataAttribute (remember that the propertyName passed into this method must have the MyMetadataAttribute decorated.)
What Process method does is adding custom metadata to ModelMetadata.AdditionalValues collection. For an example modelMetaData.AdditionalValues.Add("maxlength", Maxlength); This is in highlevel, but you can do many other things with a specialised attribute.
At the end of the execution of MyMetadataAttribute’s Process method, you should have all additional metadata ready to consume within ModelMetadata instance. ModelMetadata’s CreateMetadata returns the metadata (with the additionalValues) to framework and later accessible via ViewData. For example
ViewData.ModelMetadata.AdditionalValues[key]

Depending on the Html Helper, you can consume these additional metadata the way you want to specialise your behaviour.
Comment posted by Carol on Friday, June 10, 2011 9:04 PM
The source code has been added to the article. Thanks for the feedback!
Comment posted by Mike Griando on Monday, June 13, 2011 1:38 AM
5/5. I love the way MVC falls back to the registration point of the ModelMetadataProvider if the UnityDependecyResolver does not work.Great article!
Comment posted by derekthornton on Tuesday, June 14, 2011 3:11 PM
Awesome, thanks. Sorry if I seemed a bit snarky with my comment about the source code - it's just that I had spent hours looking for this EXACT thing, and what this article covered, and I hit that roadblock and just wanted to punch my machine. The one piece I needed was the one missing. Thanks a ton for publishing it!
Comment posted by derekthornton on Tuesday, June 14, 2011 3:18 PM
Also, I think it is utterly [explitive deleted] brilliant that you did this without using the View Model data. Absolutely astounding. This is so useful I cannot even express it. So many MVC helpers are based on the Controller context they come from, and cannot exist for any model except the strongly typed one they are invoked on. This however seems to work independent of the controller/view.
Comment posted by supecode on Monday, December 10, 2012 8:08 PM
[Display(Name = "LabelName", ResourceType = typeof(MyResourse))]
that's it
Comment posted by Antonio Petricca on Wednesday, October 30, 2013 5:29 AM
Hi, you have to add the binding flag "NonPublic" to get property infos for INTERNAL resource files.

Thank you for your article!

Antonio

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel