ASP.NET MVC – Display visual hints for the required fields in your model



Problem

  • You want to display visual hints in the view to signal that certain fields are required.
  • You want the visual hints to be dynamically added when the RequiredAttribute is added to a model property.
  • You want to use a familiar way of displaying the visual hints.

Solution

Let’s analyze each of the requests above:

1. You want to display visual hints in the view to signal that certain fields are required.

The most common way to do this is to add a red star (*) near the field or to color the field label in red.

2. You want the visual hints to be dynamically added when the RequiredAttribute is added to a model property.

The best way will be to create a html helper, read the IsRequired property from ModelMetadata and output a red star (<span style=”color: red;”>*<span> ).

Unfortunately for the default ModelMetadataProvider the IsRequired property is always true for all non-nullable properties with or without the RequiredAttribute applied.

One way to overcome this is to create our own ModelMetadataProvider and set the IsRequired property to true only if the RequiredAttribute is present but there is an alternative solution using reflection. So instead of using the IsRequired property we need to use reflection to see if the RequiredAttribute is present in the custom attributes collection for the current property (see the code bellow).

We will end with something like this: Html.RequiredVisualHintFor( m => m.FirstName). Now we have the visual hint displayed automatically but we need to add a new html helper besides the LabelFor, TextBoxFor (or OtherInputForhelper) and DisplayMessageFor).

3. You want to use a familiar way of displaying the visual hints.

The solution at step 2 solves our problem but introduces a new helper and thus adding more code in our view. We already have the Label html control so why not to “extend” it by adding some overloads?

MVC2 (relevant code only):

internal static MvcHtmlString LabelHelper(
    HtmlHelper html ,
    ModelMetadata metadata ,
    string htmlFieldName ,
    string cssFieldId ,
    string cssClassName )
{
    string labelText = 
        metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split( '.' ).Last( );
    if ( String.IsNullOrEmpty( labelText ) )
    {
        return MvcHtmlString.Empty;
    }

    TagBuilder tag = new TagBuilder( "label" );
    tag.Attributes.Add( "for" , 
        html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId( htmlFieldName ) );
    tag.SetInnerText( labelText );

    //The Required attribute has allow multiple false
    bool isRequired = 
        metadata.ContainerType.GetProperty( metadata.PropertyName )
        .GetCustomAttributes( typeof( RequiredAttribute ) , false )
        .Length == 1;

    if ( isRequired )
    {
        if ( cssFieldId.Equals( "self" , StringComparison.OrdinalIgnoreCase ) )
        {
            tag.AddCssClass( cssClassName );
        }
        else
        {
            if ( cssFieldId.StartsWith( "#" ) )
            {
                cssFieldId = cssFieldId.Remove( 0 , 1 );
            }
            StringBuilder jQueryString = new StringBuilder( )
                .Append( tag.ToString( TagRenderMode.Normal ) )
                .Append( "<script type='text/javascript'>" )
                .Append( "$(document).ready(function () {" )
                .Append( "$('#" ).Append( cssFieldId ).Append( "').addClass('" )
                .Append( cssClassName ).Append( "\')" )
                .Append( "});" )
                .Append( "</script> " );

            return MvcHtmlString.Create( jQueryString.ToString( ) );
        }
    }

    return MvcHtmlString.Create( tag.ToString( TagRenderMode.Normal ) );
}

MVC3 (relevant code only):

internal static MvcHtmlString LabelHelper(
    HtmlHelper html ,
    ModelMetadata metadata ,
    string htmlFieldName ,
    string labelText = null ,
    string cssFieldId = "self" ,
    string cssClassName = "field-required" )
{
    string resolvedLabelText = 
        labelText ?? metadata.DisplayName ?? 
        metadata.PropertyName ?? htmlFieldName.Split( '.' ).Last( );

    if ( String.IsNullOrEmpty( resolvedLabelText ) )
    {
        return MvcHtmlString.Empty;
    }

    TagBuilder tag = new TagBuilder( "label" );
    tag.Attributes.Add(
        "for" ,
        TagBuilder.CreateSanitizedId(
            html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName( htmlFieldName )
        )
    );
    tag.SetInnerText( resolvedLabelText );


    //The Required attribute has allow multiple false
    bool isRequired = 
        metadata.ContainerType.GetProperty( metadata.PropertyName )
            .GetCustomAttributes( typeof( RequiredAttribute ) , false )
            .Length == 1;

    if ( isRequired )
    {
        if ( cssFieldId.Equals( "self" , StringComparison.OrdinalIgnoreCase ) )
        {
            tag.AddCssClass( cssClassName );
        }
        else
        {
            if ( cssFieldId.StartsWith( "#" ) )
            {
                cssFieldId = cssFieldId.Remove( 0 , 1 );
            }
            StringBuilder jQueryString = new StringBuilder( )
                .Append( tag.ToString( TagRenderMode.Normal ) )
                .Append( "<script type='text/javascript'>" )
                .Append( "$(document).ready(function () {" )
                .Append( "$('#" ).Append( cssFieldId ).Append( "').addClass('" )
                .Append( cssClassName ).Append( "\')" )
                .Append( "});" )
                .Append( "</script> " );

            return MvcHtmlString.Create( jQueryString.ToString( ) );
        }
    }

    return MvcHtmlString.Create( tag.ToString( TagRenderMode.Normal ) );
}

How it works

As you can see above the whole magic is in identifying if the RequiredAttribute is present, If it is then we add a class to the tag (if the “self” value is present) or we output a jQuery snippet if we need to add the class to another control. After that is all about adding a CSS style for the respective class.

See it in action

Let’s consider the following model:

    public class Person
    {
        public string Prefix { get; set; }
        [Required( ErrorMessage = "The first name is required" )]
        public string FirstName { get; set; }
        [Required( ErrorMessage = "The middle name is required" )]
        public string MiddleName { get; set; }
        [Required( ErrorMessage = "The last name is required" )]
        public string LastName { get; set; }
        public string PrefferedName { get; set; }
        public string Suffix { get; set; }
        public bool Sex { get; set; }
    }

And the Edit view for it

MVC2 (Edit.aspx)

<% Html.EnableClientValidation(); %>
<% using ( Html.BeginForm( "Edit" , "Person" ) ) 
{%>
    <%: Html.ValidationSummary(true) %>
    <fieldset>
        <legend>Fields</legend>
        <div class="editor-label">
            <%: Html.LabelFor(model => model.Prefix, "self") %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.Prefix) %>
            <%: Html.ValidationMessageFor(model => model.Prefix) %>
        </div>
        <div class="editor-label">
            <%: Html.LabelFor(model => model.FirstName, "self") %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.FirstName) %>
            <%: Html.ValidationMessageFor(model => model.FirstName) %>
        </div>
        <div id="divRequired">
            <div class="editor-label">
                <%: Html.LabelFor( model => model.MiddleName , "#divRequired" , "field-required3" )%>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(model => model.MiddleName) %>
                <%: Html.ValidationMessageFor(model => model.MiddleName) %>
            </div>
        </div>
        <div class="editor-label">
            <%: Html.LabelFor(model => model.LastName, "self", "field-required2") %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.LastName) %>
            <%: Html.ValidationMessageFor(model => model.LastName) %>
        </div>
        <div class="editor-label">
            <%: Html.LabelFor(model => model.PrefferedName) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.PrefferedName) %>
            <%: Html.ValidationMessageFor(model => model.PrefferedName) %>
        </div>
        <div class="editor-label">
            <%: Html.LabelFor(model => model.Suffix) %>
        </div>
        <div class="editor-field">
            <%: Html.TextBoxFor(model => model.Suffix) %>
            <%: Html.ValidationMessageFor(model => model.Suffix) %>
        </div>
        <div class="editor-label">
            <%: Html.LabelFor( model => model.Sex , "self" )%>
        </div>
        <div class="editor-field">
            <%: Html.CheckBoxFor(model => model.Sex) %>
            <%: Html.ValidationMessageFor(model => model.Sex) %>
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
<% } %>

MVC3 (Edit.cshtml)

@using ( Html.BeginForm( ) )
{
    @Html.ValidationSummary( true )
    <fieldset>
        <legend>Person</legend>
        <div class="editor-label">
            @Html.LabelFor( model => model.Prefix , null , null, null )
        </div>
        <div class="editor-field">
            @Html.EditorFor( model => model.Prefix )
            @Html.ValidationMessageFor( model => model.Prefix )
        </div>
        <div class="editor-label">
            @Html.LabelFor( model => model.FirstName , null , null , null )
        </div>
        <div class="editor-field">
            @Html.EditorFor( model => model.FirstName )
            @Html.ValidationMessageFor( model => model.FirstName )
        </div>
        <div id="divRequired">
            <div class="editor-label">
                @Html.LabelFor( model => model.MiddleName , "divRequired" , "field-required3" )
            </div>
            <div class="editor-field">
                @Html.EditorFor( model => model.MiddleName )
                @Html.ValidationMessageFor( model => model.MiddleName )
            </div>
        </div>
        <div class="editor-label">
            @Html.LabelFor( model => model.LastName , "self" , "field-required2" )
        </div>
        <div class="editor-field">
            @Html.EditorFor( model => model.LastName )
            @Html.ValidationMessageFor( model => model.LastName )
        </div>
        <div class="editor-label">
            @Html.LabelFor( model => model.PrefferedName )
        </div>
        <div class="editor-field">
            @Html.EditorFor( model => model.PrefferedName )
            @Html.ValidationMessageFor( model => model.PrefferedName )
        </div>
        <div class="editor-label">
            @Html.LabelFor( model => model.Suffix )
        </div>
        <div class="editor-field">
            @Html.EditorFor( model => model.Suffix )
            @Html.ValidationMessageFor( model => model.Suffix )
        </div>
        <div class="editor-label">
            @Html.LabelFor( model => model.Sex , null , null , null )
        </div>
        <div class="editor-field">
            @Html.EditorFor( model => model.Sex )
            @Html.ValidationMessageFor( model => model.Sex )
        </div>
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

The styles:

/* Styles for editor and display hints
----------------------------------------------------------*/

.field-required:before
{
    color: red;
    content: "*";
}
.field-required2:after
{
    font-style: italic;
    font-weight: bold;
    color: red;
    content: " (Required)";
}
.field-required3
{
    border-color: #666666;
    border-style: dashed;
    width: 220px;
    padding: 5px;
}
.field-required3 label
{
    color: red;
    font-style: italic;
    font-weight: bold;
}

The result:

 MVC2 | MVC3

Download

Download code

 

11 Comments

  • Exactly what I was looking for.
    Nice formatting but what tool did you use?

  • Thanks OsiriO!

    I'm using Windows Live Writer 2011 with the VSPaste plugin (it takes your Visual Studio text formatting and color - but you have to set the background color yourself).

  • I have entity framework poco with the [Required] attribute and this all works when i use that entity in my view. However, if i create a viewmodel class that derives from my ef poco, it doesn't find attributes.

    i tried passing true to GetCustomAttributes which sounds like it should work but didn't for me. I also tried going through the ContainerType.BaseType property.

    //The Required attribute has allow multiple false
    bool isRequired =
    metadata.ContainerType.GetProperty(metadata.PropertyName)
    .GetCustomAttributes(typeof(RequiredAttribute), true)
    .Length == 1;

    if (!isRequired && metadata.ContainerType.BaseType != null)
    {
    isRequired =
    metadata.ContainerType.BaseType.GetProperty(metadata.PropertyName)
    .GetCustomAttributes(typeof(RequiredAttribute), true)
    .Length == 1;
    }


    The TagBuilder is able to find the [DisplayName("Amount")] attribute from by base class so i would think i should be able to find the [Required] some how.

  • Hi Randy,

    I've created a PersonViewModel in the Person.cs (from the sample) that inherits from Person and also has an additional field with the RequiredAttribute applied. I've changed the Person's Edit.cshtml view to use the PersonViewModel instead (also added the new field) and everything works just fine. Can you give me some other hints to reproduce the problem?

  • Turns out that it was working when the attributes were on the base class. It wasn't working becuase i had the attributes on a MetadataType class. I ended up adding this code to additionally check the metadata atrributes.


    if (!isRequired)
    {
    // check the meta data
    var customAttributes = metadata.ContainerType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType().ToArray();
    MetadataTypeAttribute metadataAttribute = customAttributes.FirstOrDefault();
    if (metadataAttribute != null)
    {
    var property = metadataAttribute.MetadataClassType.GetProperties().FirstOrDefault(p=> p.Name == htmlFieldName);
    if (property != null)
    {
    isRequired = Attribute.IsDefined(property, typeof(RequiredAttribute));
    }
    }
    }

  • Thanks for that Randy. I will update the post

  • It's a great post, thanks.

  • I know you say "Relevant code only" and this is part of a series, but for those of us who stumble upon this article when looking for how to do this, it might be helpful to include mention of how we also need to create an override of

    public static MvcHtmlString LabelFor

    I might be the only one, but this had me banging my head for a while.. :)

  • I know you say "Relevant code only" and this is part of a series, but for those of us who stumble upon this article when looking for how to do this, it might be helpful to include mention of how we also need to create a new *overload* of

    public static MvcHtmlString LabelFor

    I might be the only one, but this had me banging my head for a while.. :)

  • How can I change the class only after a postback where the validation fails?

  • @Stuart Dobson: All the overloads of the Label helper are calling the one method I've shown in the post (internal static MvcHtmlString LabelHelper). You can download my code (or even MVC2/MVC3 source code to see how...but is just a method call).

    As for the other question: the whole point is to tell the user that the field is required not to show that after a postback.


Comments have been disabled for this content.