Recently Simon Ince and Stuart Leeks independently started working on solving the same challenge in ASP.NET MVC. Once we realised this we combined our efforts and this blog post is the result. We’ll walk through our approach - and hope you’ll be as pleased as we are with the simplicity of the solution. We’re wondering why we didn’t always do this!        

The challenge that we worked on was how to inject custom attributes into the HTML based on some facet of the View Model when using EditorFor. This requirement came up in a few scenarios, of which Stuart’s post on “Adding aria-required attribute for required fields” is an example.

A Specific Solution

In his post Stuart describes a method for ensuring that an input control for a required field is rendered as follows;

<input aria-required="true" type="text" value="" />

To summarise the post, this results in a customised Razor partial view to override the default String template (and other data types) that checks the Model Metadata to see if the field is required, and adds the attribute if needed.

@{

     var attributes = new RouteValueDictionary

                          {

                             { "class", "text-box single-line" }

                          };

     if (ViewContext.ViewData.ModelMetadata.IsRequired)

     {

         attributes.Add("aria-required", "true");

     }

}

@Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)

This enables a developer to use Html.EditorFor to render a model, with confidence that the aria-required attribute will be rendered when appropriate.

Envisioning a General Solution

This works great, but what if there are other HTML attributes we want to inject? It turns out this is a very common requirement – for example auto complete, wiring up a date picker, or enhancing a web page with JavaScript. Many of these solutions are formed of a few key concepts;

1.       Applying .NET attributes to a View Model to provide metadata about the required behaviour.

2.       Emitting an HTML attribute based on that metadata.

3.       Using the HTML attribute within JavaScript, or relying on browser behaviour to act on it.

In the aria-required example above the attribute is hard-coded. To turn this into a generic solution, the key is to take the Model Metadata and derive a list of custom HTML attributes that should be rendered.  All that remains is some way for a developer to register logic that analyses metadata and returns any HTML attributes that should be applied.

Introducing HtmlAttributeProvider

Both of us solved this problem independently in the same way, so we have some confidence in it resonating with other developers. We created an HtmlAttributeProvider that allows registration of delegates that analyse the metadata. Multiple delegates can be registered and all are evaluated at rendering time.

This means that in Global.asax we can register a delegate to emit the aria-required attribute;

HtmlAttributeProvider.Register(metadata =>metadata.IsRequired, "aria-required", true);

This overload will not emit any attribute if the lambda returns false, but other overloads exist that allow more control. The three overloads are;

static void Register(Func<ModelMetadata, bool> condition, string attributeName, object attributeValue);

static void Register(Func<HtmlHelper, ModelMetadata, IEnumerable<KeyValuePair<string, object>>> provider);

static void Register(Func<ModelMetadata, IEnumerable<KeyValuePair<string, object>>> provider);

Note that one of these also provides a reference to the HtmlHelper, allowing logic access to the ViewContext and routeCollection if needed.

The evaluation of these providers is simple. In every Editor Template (String, Boolean, Decimal, etc) we call to a method that enumerates the registered delegates and aggregates the resulting HTML attributes, before passing them to the Html.TextBox helper. For example, the String.cshtml template is below;

@{

    var attributes = Html.GetAttributes();

    attributes.MergeValue("class", "text-box single-line");

}

@Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)

This simple code coupled with the delegate registered above above means that aria-required will be displayed for Required fields with no additional work on behalf of the developer, as long as they use Html.EditorFor.

Providing Metadata

These provider delegates need some metadata upon which to make the decision about which HTML attributes should be rendered. There are a few ways we can provide that metadata;

·         Rely on built in metadata, such as the Required example above.

·         Apply the DataType, AllowHtml, or other attributes.

·         Use the AdditionalMetadata attribute to inject values into the metadata.AdditionalValues collection, for example;

[AdditionalMetadata("Key", "Value"]

SomeModelField

·         Create an attribute that implements IMetadataAware, that injects values into the AdditionalValues collection. See Stuart’s post on auto complete for an example.

·         Go crazy and create a custom ModelMetadataProvider, although this is rarely needed since ASP.NET MVC 3 added IMetadataAware.

Try it with NuGet!

This simple solution gives us a way to inject custom HTML attributes based on metadata with ease. At this point these attributes are only rendered for Html.EditorFor - we’ve not customised the Display Templates but it would be trivial to do so, enabling Html.DisplayFor to support the same infrastructure.

You can try this approach right now by installing the NuGet package named UkadcHtmlAttributeProvider. We’ve also published the code to the MSDN Code Gallery.