Custom formatting of validation summary and errors

Here are some extension points that you can consider to provide custom rendering for validation summary and field validation errors:

  • Customize existing validation tag helpers (Register new IHtmlGenerator)
  • Create new validation tag helpers (Register new Tag Helpers)

Customize existing validation tag helpers

asp-validation-summary and asp-validation-for tag helpers use GenerateValidationSummary and GenerateValidationMessage methods of the registered implementation of IHtmlGenerator service which is DefaultHtmlGenerator by default.

You can provide your custom implementation deriving DefaultHtmlGenerator and overriding those methods, then register the service at startup. This way those tag helpers will use your custom implementation.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddTransient<IHtmlGenerator, MyHtmlGenerator>();
}

Here is the link to source code of DefaultHtmlGenerator to help you to customize the implementation.

Example - Creating a new implementation IHtmlGenerator

Here is just a simple example to show required namespaces and methods and simply what can goes into your custom implementation. After you provided custom implementation, don't forget to register it in ConfigureServices like what I did above.

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;

namespace ValidationSampleWebApplication
{
    public class MyHtmlGenerator : DefaultHtmlGenerator
    {
        public MyHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor, IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder, ValidationHtmlAttributeProvider validationAttributeProvider) 
            : base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, validationAttributeProvider)
        {
        }
        public override TagBuilder GenerateValidationMessage(ViewContext viewContext, ModelExplorer modelExplorer, string expression, string message, string tag, object htmlAttributes)
        {
            return base.GenerateValidationMessage(viewContext, modelExplorer, expression, message, tag, htmlAttributes);
        }
        public override TagBuilder GenerateValidationSummary(ViewContext viewContext, bool excludePropertyErrors, string message, string headerTag, object htmlAttributes)
        {
            return base.GenerateValidationSummary(viewContext, excludePropertyErrors, message, headerTag, htmlAttributes);
        }
    }
}

Create new validation tag helpers

You also can author your custom tag helpers. To do so, it's enough to derive from TagHelper and override Process methods.

Then you can simply register created tag helpers in the view or globally in _ViewImports.cshtml:

@using ValidationSampleWebApplication
@using ValidationSampleWebApplication.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, ValidationSampleWebApplication

Also when creating the custom tag helpers for validation you can consider:

  • Creating the validation tag helper from scratch
  • Drive from existing tag-helper classes

Example - Adding hasError class to a form-group div

In this example, I've created a asp-myvalidation-for which can be applied on div elements this way <div class="form-group" asp-myvalidation-for="LastName"> and will add hasError class to div if the specified field has validation error. Don't forget to register it in _ViewImports.cshtml like what I did above.

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace ValidationSampleWebApplication
{
    [HtmlTargetElement("div", Attributes = MyValidationForAttributeName)]
    public class MyValidationTagHelper : TagHelper
    {
        private const string MyValidationForAttributeName = "asp-myvalidation-for";

        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }

        [HtmlAttributeName(MyValidationForAttributeName)]
        public ModelExpression For { get; set; }
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            base.Process(context, output);
            ModelStateEntry entry;
            ViewContext.ViewData.ModelState.TryGetValue(For.Name, out entry);
            if (entry != null && entry.Errors.Count > 0)
            {
                var builder = new TagBuilder("div");
                builder.AddCssClass("hasError");
                output.MergeAttributes(builder);   
            }
        }
    }
}

Example - Adding field-validation-error class to a form-group div

In the following example, I've added div support to the standard asp-validation-for tag helper. The existing tag helper just supports div element. Here I've added div support to the asp-validation-for tag helper and in case of error, it will add field-validation-error otherwise, in valid cases the div will have field-validation-valid class.

The default behavior of the tag is in a way that it doesn't make any change in content of the tag if the tag has contents. So for adding the tag helper to an empty span will add validation error to span, but for a div having some contents, it just changes the class of div. Don't forget to register it in _ViewImports.cshtml like what I did above.

using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.TagHelpers;

namespace ValidationSampleWebApplication
{
    [HtmlTargetElement("div", Attributes = ValidationForAttributeName)]
    public class MytValidationMessageTagHelper : ValidationMessageTagHelper
    {
        private const string ValidationForAttributeName = "asp-validation-for";
        public MytValidationMessageTagHelper(IHtmlGenerator generator) : base(generator)
        {
        }
    }
}