Customize ASP.NET MVC Model Metadata

Using ASP.NET MVC, sometimes you want to customize metadata for model. For example you want to read display name from an XML file, or you want to set some additional metadata for example for rendering.

In this post, I’ll share a few options about customizing ASP.NET MVC model metadata.

There are several options for customizing metadata of the model:

  • Modifying existing attributes by deriving from them.
  • Creating new metadata-aware attributes by implementing IMetadataAware interface.
  • Creating a custom ModelMetadataProvider.

Modifying Existing Attribtes

Sometimes you can derive from existing attributes and change their behavior. For example if you want to change or extend DisplayName property to be able to get display name from a resource file, you can derive from DisplayName attribute and in the constructor of the derived attribute, get the display name from resource based on the name which has been passed to the constructor.

Here is an implementation is adding another constructor which accepts resource type name:

using System;
using System.ComponentModel;
using System.Reflection;

public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
    public DisplayNameLocalizedAttribute(string name) : base(name) { }
    public DisplayNameLocalizedAttribute(string resourceKey, Type resourceType)
        : base(GetDisplayName(resourceKey, resourceType)) { }

    public static string GetDisplayName(string resourceKey, Type resourceType)
    {
        PropertyInfo property = resourceType.GetProperty(resourceKey,
            BindingFlags.Public | BindingFlags.Static);
        return (string)property.GetValue(property.DeclaringType, null);
    }
}

And to use the attribute, assuming you have a public resource file names StringResources.ResX, then it’s enough to decorate model properties like this:

public class Person
{
    [DisplayNameLocalized(FirstName, typeof(StringResources))]
    public string FirstName { get; set; }
    [DisplayNameLocalized(LastName, typeof(StringResources))]
    public string LastName { get; set; }
}

Create new metadata-aware attributes

There is an IMetadataAware interface in ASP.NET MVC. You can create a new attribute by deriving from Attribute class and implementing IMetadataAware.

This interface allows you to create new metadata-aware attributes which contribute to the process of creating model metadata without requiring a custom metadata provider.

The interface is used by the AssociatedMetadataProvider, so all derived metadata providers including DataAnnotationsModelMetadataProvider which is the default metadata provider will use it.

Here is an example which shows how you can implement the interface in a new attribute class:

using System;
using System.Web.Mvc;
public class CustomMetadataAttribure : Attribute, IMetadataAware
{
    public string Key { get; set; }
    public CustomMetadataAttribure(string key) => this.Key = key;
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        //set properties of metadata
    }
}

Creating a custom ModelMetadataProvider

Sometimes you want to change the way that you extract metadata without changing the attributes. For example, let’s say, you have decorated the models with Display attribute and you want to load display names from a database or an xml file instead of getting from a resource files.

You can achive that without any new attribute.

In fact it’s responsibility of ModelMetaDataProvider to get mete data for model, including display text for properties. So as an option, you can keep the Display attribute intact and instead, create a new model metadata provider and return property metadata from a different source. To do so:

  1. Create a new metadata provider by deriving from DataAnnotationsModelMetadataProvider.
  2. Override GetMetadataForProperty and call base, to get metadata. Then change DisplayName based on your custom logic.
  3. Register the new metadata provider as ModelMetadataProviders.Current in App_Start.

Here an example showing the new Model

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Mvc;
public class MyCustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor,
        Type containerType,
        PropertyDescriptor propertyDescriptor)
    {
        var metadata = base.GetMetadataForProperty(modelAccessor, 
            containerType, propertyDescriptor);
        var display = propertyDescriptor.Attributes
            .OfType<DisplayAttribute>().FirstOrDefault();
        if (display != null)
        {
            //Set display name based on your logic:
            //metadata.DisplayName = ....
        }
        return metadata;
    }
}

And then register it in Application_Start:

protected void Application_Start()
{
    //...

    ModelMetadataProviders.Current = new MyCustomModelMetadataProvider();
}

Conclusion

In this post I described a few options for customizing metadata of the model:

  • Modifying existing attributes by deriving from them.
  • Creating new metadata-aware attributes by implementing IMetadataAware interface.
  • Creating a custom ModelMetadataProvider.

The first option is useful when you have an existing attribute and you want to change it’s behavior. Localized DisplayName attribute is a good example for that.

The second option is useful when you want to create a new attribure from scratch and you want to use this attribute to provide some new metadata for model. For example, you want to define some rendering options like back color. In this case you can create a new attribute by implementing IMetadataAware. This way you can set metadata existing properties or add some new values to ModelMetadata.AdditionalValues.

The third option is usefull when you want to run a new metadata provider engine, for example in case you want to load metadata from xml file.

You May Also Like

About the Author: Reza Aghaei

I’ve been a .NET developer since 2004. During these years, as a developer, technical lead and architect, I’ve helped organizations and development teams in design and development of different kind of applications including LOB applications, Web and Windows application frameworks and RAD tools. As a teacher and mentor, I’ve trained tens of developers in C#, ASP.NET MVC and Windows Forms. As an interviewer I’ve helped organizations to assess and hire tens of qualified developers. I really enjoy learning new things, problem solving, knowledge sharing and helping other developers. I'm usually active in .NET related tags in stackoverflow to answer community questions. I also share technical blog posts in my blog as well as sharing sample codes in GitHub.

Leave a Reply

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