Conventional HTML in ASP.NET MVC: Validators

Other posts in this series:

We’re marching towards our goal of creating conventional components, similar to React classes or Angular directives, but there’s one last piece we need to worry about before we get there: validators.

Validators can be a bit tricky, since they largely depend on the validation framework you’re trying to use. My validation framework of choice is still Fluent Validation, but you can use others as well. Since the data annotation validators are fairly popular, this example will use that as the starting point.

First, we have to figure out what our validation HTML should look like, and how it should affect the rest of our output. But we don’t have a “Validator” convention, we only have “Label”, “Editor”, and “Display” as our possibilities.

Luckily, underneath the covers these Label etc. conventions are only categories of elements, and we can easily create a new category of conventions for our own purposes. The Label/Editor/Display properties are only convenience methods. In our base convention class, let’s create a new category of conventions via a similar property:

public class OverrideHtmlConventions : DefaultHtmlConventions
{
    protected ElementCategoryExpression Validators
    {
        get
        {
            var builderSet = Library
                .For<ElementRequest>()
                .Category("Validator")
                .Defaults;
            return new ElementCategoryExpression(builderSet);
        }
    }

    public OverrideHtmlConventions()
    {
        Editors.Always.AddClass("form-control");
        Labels.Always.AddClass("control-label");
        Labels.Always.AddClass("col-md-2");

This will allow us to append additional conventions to a Validator category of element requests. With a custom builder, we can create a default version of our validator span (without any validation messages):

public class SpanValidatorBuilder : IElementBuilder
{
    public HtmlTag Build(ElementRequest request)
    {
        return new HtmlTag("span")
            .AddClass("field-validation-error")
            .AddClass("text-danger")
            .Data("valmsg-for", request.ElementId);
    }
}

And add it to our default conventions:

public OverrideHtmlConventions()
{
    Validators.Always.BuildBy<SpanValidatorBuilder>();

    Editors.Always.AddClass("form-control");
    Labels.Always.AddClass("control-label");
    Labels.Always.AddClass("col-md-2");

The last part is actually generating the HTML. We need to do two things -

  • Determine if the current field has any validation errors
  • If so, build out the tag with the correct error text

The first part is actually fairly straightforward – ha ha just kidding, it’s awful. Getting validation messages out of ASP.NET MVC isn’t easy, but the source is available so we can just copy what’s there.

public static HtmlTag Validator<T>(this HtmlHelper<T> helper,
    Expression<Func<T, object>> expression) where T : class
{
    // MVC code don't ask me I just copied
    var expressionText = ExpressionHelper.GetExpressionText(expression);
    string fullHtmlFieldName 
        = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expressionText);

    if (!helper.ViewData.ModelState.ContainsKey(fullHtmlFieldName))
        return new NoTag();

    ModelState modelState = helper.ViewData.ModelState[fullHtmlFieldName];
    ModelErrorCollection modelErrorCollection = modelState == null 
        ? null 
        : modelState.Errors;
    ModelError error = modelErrorCollection == null || modelErrorCollection.Count == 0 
        ? null 
        : modelErrorCollection.FirstOrDefault(m => !string.IsNullOrEmpty(m.ErrorMessage)) 
        ?? modelErrorCollection[0];
    if (error == null)
        return new NoTag();
    // End of MVC code

    var tagGeneratorFactory = DependencyResolver.Current.GetService<ITagGeneratorFactory>();
    var tagGenerator = tagGeneratorFactory.GeneratorFor<ElementRequest>();
    var request = new ElementRequest(expression.ToAccessor())
    {
        Model = helper.ViewData.Model
    };

    var tag = tagGenerator.Build(request, "Validator");

    tag.Text(error.ErrorMessage);

    return tag;
}

Ignore what’s going on in the first section – I just grabbed it from the MVC source. The interesting part is at the bottom, where I grab a tag generator factory, create a tag generator, and build an HtmlTag using the Validator category for the given ElementRequest. This is what our Label/Editor/Display methods do underneath the covers, so I’m just emulating their logic. It’s a bit clunkier than I want, but I’ll amend that later.

Finally, after building the base validator tag, we set the inner text to the error message we determined earlier. We only use the first error message – too many and it becomes difficult to read. The validation summary can still be used for multiple errors. Our view is now:

<div class="form-group">
    @Html.Label(m => m.Email)
    <div class="col-md-10">
        @Html.Input(m => m.Email)
        @Html.Validator(m => m.Email)
    </div>
</div>
<div class="form-group">
    @Html.Label(m => m.Password)
    <div class="col-md-10">
        @Html.Input(m => m.Password)
        @Html.Validator(m => m.Password)
    </div>
</div>

Since we know that every validation message will need that “text-danger” class applied, applying it once to our conventions means that we’ll never have to copy-paste that portion around ever again. And much easier to develop against than the MVC templates, which quite honestly, are quite difficult to develop against.

We could go a step further and modify our Label conventions to pick up on the “Required” attribute and show an asterisk or bold required field labels.

Now that we have quite a bit of consistency in our groups of form elements, in the next post we’ll look at tackling grouping multiple tags into a single input/form component.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1659

  • Pingback: Conventional HTML in ASP.NET MVC: Building larger primitives | Jimmy Bogard's Blog

  • Lucas Byers

    As I have been walking through your posts about Fubu and the convention based Html, I have been quite impressed and have begun implementing some of the features. I have run into a snag here on validation as my input does not show up in the ModelState keys in line 8 of Validator.cs listed above. On an Update view, the Id property shows up in the Keys colleciton, but the Name property (a simple text field) does not show up and it should be required. I have been banging my head against the wall reviewing all of my code to see if I can pin down why it is not showing up, but I don’t see anything glaring me in the face as explicitly wrong. Any ideas?

    EDIT: Once the form is posted to the controller action, if I check ModelState, both keys are listed; Id, Name.

    • jbogard

      So is it working now then?

      • Lucas Byers

        No, for some reason when my Validator() logic gets to that line with helper.ViewData.ModelState.ContainsKey(), my “Name” key is simply not there. I was just clarifying that if I go ahead and POST to the controller action, both keys show up in the ModelState. Very strange.