Patrick Desjardins Blog
Patrick Desjardins picture from a conference

How to add custom Data Annotation attribute to your property in Asp.Net MVC

Posted on: 2013-02-22

In many scenarios the ViewModel will need to provide to the View some additional information could be useful. The case of having an UIHint attribut with the conjunction of this additional information is probably the situation where most people will face at some point.

[DoSomething] 
public MyClass MyProperty { get; set; } 

Let say you have a template that display multiple information from your view model (in the example above "MyClass"). If this one require to display something more (or less) depending of who use it, and that the view model know this information, the only way to pass this information is by data annotation.

Let's base the theory on the following example.

[DisableShape("Heavy")] 
[UIHint("ShapeSelector")] 
public Shape TopShape { get; set; } 

[UIHint("ShapeSelector")] 
public Shape BottomShape { get; set; } 

The view model class has two properties which define 2 shapes. One must be selected from a list of shape but only accept light shape while the other require to have strong shape. We could create two templates but we could also create a simple template and allow to disable some shape. This is the case of the property "TopShape" where we will disable all heavy shape but keep everything selectable for the "BottomShape".

The first thing to keep in mind is that we will change "ModelMetadataProviders" of MVC to use a custom one when we will be able to add multiple attribute for the whole application. Since we do not want to bind a single attribute we will create a custom meta data provider.

public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider { 
  protected override ModelMetadata CreateMetadata( IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { 
    var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); 
    attributes.OfType<MetadataAttribute>().ToList().ForEach(x => x.Process(modelMetadata)); return modelMetadata; 
  } 
} 

This code lets you have multiple attributes of type "MetadataAttribute".

public abstract class MetadataAttribute : Attribute { 
  public abstract void Process(ModelMetadata modelMetaData);  
} 

As you can see, it's simply an abstract class that lets you define a Process method which is executed in the CreateMetadata method.

From there, you can define what parameter you want for your custom attribute.

public class DisableShapeAttribute : MetadataAttribute { 
  public bool TypeShape { get; set; } 
  public DisableShapeAttribute(string type) { TypeShape = type; }

  public DisableShapeAttribute() { TypeShape = ""; }

  public override void Process(ModelMetadata modelMetaData) { 
    modelMetaData.AdditionalValues.Add("DisableShape", TypeShape); 
  } 
} 

The last step is to register the CustomModelMetadataProvider that loop every MetadataAttribute. This is done via the Global.asax.cs, inside the Application_Start method.

 ModelMetadataProviders.Current = new CustomModelMetadataProvider(); 

From here you can read the attribute from your view or template.

 var attr = ViewData.ModelMetadata.AdditionalValues.SingleOrDefault(x => x.Key == "DisableShape").Value; 
 var attrValue = attr != null && (string)attr;