Hardly a week goes by where someone isn’t requesting that we add a .Attributes collection to ModelMetadata in MVC 2.
We don’t want to add this collection, because it gives attributes a “better among equals” status among all the ways that you could express metadata (in addition to attributes, you might choose to get your metadata from your database schema, from data inside a database, from an external configuration source like an XML file, among many other possibilities). If we add .Attributes, we’re declaring one way king – or leaving ourselves open to many other requests for everybody else’s favorite kind of system.
I figure these requests fall into two camps: those who want to write their own Metadata source, and those who want to supplement the existing DataAnnotations metadata source that’s built-in.
It’s not necessary. Here’s how you can supplement ModelMetadata without the .Attributes collection.
If You Don’t Want DataAnnotations:
- Write a new provider.
- Derive it from AssociatedMetadataProvider. This implements the three methods of IModelMetadataProvider for anybody who intends to use attributes as their metadata source.
- Override the abstract CreateMetadata method. Take the attributes you’re given, and construct the ModelMetadata as appropriate. If you want a sample, look at the code for DataAnnotationsModelMetadataProvider. I promise, it’s not very complicated.
- Anything you want that doesn’t have a first-class property for, put into AdditionalValues.
- Register your new provider with ModelMetadataProviders.Current during startup.
- ...
- Profit!
If You Want To Supplement DataAnnotations:
- Write a new provider. (You knew this was coming, suck it up.)
- Derive it from DataAnnotationsModelMetadataProvider.
- Override the virtual CreateMetadata method. Call base.CreateMetadata so that you can get the ModelMetadata that’s filled with values from DataAnnotations, and then just supplement it with values from your own attributes.
- Anything you want that doesn’t have a first-class property for, put into AdditionalValues.
- Register your new provider with ModelMetadataProviders.Current during startup.
- ...
- Profit!
That wasn’t so hard, right? :)
As a bonus, I’m going to show you an example of the latter. This is a metadata provider that uses the new attributes from DataAnnotations in .NET 4 to supplement the older attributes from DataAnnotations in .NET 3.5 SP1.
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web.Mvc; public class DataAnnotations4ModelMetadataProvider : DataAnnotationsModelMetadataProvider { protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { ModelMetadata metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); // Prefer [Display(Name="")] to [DisplayName] DisplayAttribute display = attributes.OfType<DisplayAttribute>().FirstOrDefault(); if (display != null) { string name = display.GetName(); if (name != null) { metadata.DisplayName = name; } // There was no 3.5 way to set these values metadata.Description = display.GetDescription(); metadata.ShortDisplayName = display.GetShortName(); metadata.Watermark = display.GetPrompt(); } // Prefer [Editable] to [ReadOnly] EditableAttribute editable = attributes.OfType<EditableAttribute>().FirstOrDefault(); if (editable != null) { metadata.IsReadOnly = !editable.AllowEdit; } // If [DisplayFormat(HtmlEncode=false)], set a data type name of "Html" // (if they didn't already set a data type) DisplayFormatAttribute displayFormat = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault(); if (displayFormat != null && !displayFormat.HtmlEncode && String.IsNullOrWhiteSpace(metadata.DataTypeName)) { metadata.DataTypeName = DataType.Html.ToString(); } return metadata; } }
Enjoy!