Using an EditorEntryFor
helper makes the code DRYer and opens new possibilities, such as automatic indication of required fields. A small addition in one helper method will mark all required fields in entire MVC web application.
It’s more or less standard to indicate required fields in a form with a red * next to the label. Entering them one by one in the markup is tedious. Adding them to the description text is outright wrong and will look bad if the description is used in other places, such as validation messages.
They shouldn’t need to be handled at all, since the metadata already contains a property indicating if the field is required or not. If all form entries are created in the same way as I showed in the DRYing MVC Forms with an EditorEntryFor Helper post it’s easy to automatically add a required marker whenever a form field is required.
Instead of calling LabelFor
directly, I’ve created an own helper method that checks the IsRequired
flag on the metadata.
First the metadata is looked up based on the expression using standard ASP.NET MVC methods. Then the IsRequired
field is set. It is automatically updated based on data type and if there is a [Required]
attribute. Any non-nullable value types are always required. Nullable value types and reference types are only required if there is a [Required]
attribute. If the field is required, a span with a single * inside it is added right after the label.
private static MvcHtmlString LabelWithRequiredMarkerFor<TModel, TValue>( this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) { var label = html.LabelFor(expression); if (ModelMetadata.FromLambdaExpression(expression, html.ViewData).IsRequired) { label = new MvcHtmlString(label.ToString() + " <span class=\"required-marker\">*</span>"); } return label; } |
To call the new method in the right place, the EditorEntryFor
helper from the previous post is adjusted. The new complete helper class is listed below.
/// <summary> /// Helper methods for form creation /// </summary> public static class FormExtensions { /// <summary> /// Creates an entry in a form, complete with label, input and validation message. /// </summary> /// <typeparam name="TModel">Type of the model.</typeparam> /// <typeparam name="TValue">Type of the field.</typeparam> /// <param name="html">Html helper.</param> /// <param name="expression">An expression that identifies the field of the model to render /// an editor entry for</param> /// <returns>MvchHtmlString</returns> public static MvcHtmlString EditorEntryFor<TModel, TValue>( this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) { return BuildFormEntry(html.LabelWithRequiredMarkerFor(expression), html.EditorFor(expression), html.ValidationMessageFor(expression)); } private static MvcHtmlString LabelWithRequiredMarkerFor<TModel, TValue>( this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) { var label = html.LabelFor(expression); if (ModelMetadata.FromLambdaExpression(expression, html.ViewData).IsRequired) { label = new MvcHtmlString(label.ToString() + " <span class=\"required-marker\">*</span>"); } return label; } private static MvcHtmlString BuildFormEntry( MvcHtmlString label, MvcHtmlString input, MvcHtmlString validation) { return new MvcHtmlString("<div class=\"editor-label\">" + label + "</div>\n" + "<div class=\"editor-field\">" + input + validation + "</div>\n\n"); } } |