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:
- Create a new metadata provider by deriving from
DataAnnotationsModelMetadataProvider
. - Override
GetMetadataForProperty
and call base, to get metadata. Then changeDisplayName
based on your custom logic. - Register the new metadata provider as
ModelMetadataProviders.Current
inApp_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.