Allow HTML in MVC’s Html.ValidationMessageFor or Html.ValidationSummary

Have you ever wanted to put HTML in the validation message for a property or in the validation summary? You can’t do this out of the box, since any message that gets run through either of these methods gets put into the element’s InnerText. But thankfully, we can create simple extensions to allow HTML in both of these validation methods.

First thing is to create a ValidationExtensions.cs file in your solution to store these extensions. That could look like this:

public static class ValidationExtensions
{
    public static IHtmlString HtmlValidationSummary(this HtmlHelper htmlHelper)
    {
        //code here
    }
}

ValidationSummary

Here is a ValidationSummary extension method I found from Darin Dimitrov over at Stack Overflow, which allows us to use liBuilder.InnerHtml, instead of liBuilder.InnerText:

public static IHtmlString HtmlValidationSummary(this HtmlHelper htmlHelper)
{
    var formContextForClientValidation = htmlHelper.ViewContext.ClientValidationEnabled ? htmlHelper.ViewContext.FormContext : null;
    if (htmlHelper.ViewData.ModelState.IsValid)
    {
        if (formContextForClientValidation == null)
        {
            return null;
        }
        if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
        {
            return null;
        }
    }

    var stringBuilder = new StringBuilder();
    var ulBuilder = new TagBuilder("ul");

    ModelState modelState;
    if (htmlHelper.ViewData.ModelState.TryGetValue(htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix, out modelState))
    {
        foreach (ModelError error in modelState.Errors)
        {
            string userErrorMessageOrDefault = error.ErrorMessage;
            if (!string.IsNullOrEmpty(userErrorMessageOrDefault))
            {
                var liBuilder = new TagBuilder("li");
                liBuilder.InnerHtml = userErrorMessageOrDefault;
                stringBuilder.AppendLine(liBuilder.ToString(TagRenderMode.Normal));
            }
        }
    }

    if (stringBuilder.Length == 0)
    {
        stringBuilder.AppendLine("<li style=\"display:none\"></li>");
    }
    ulBuilder.InnerHtml = stringBuilder.ToString();

    TagBuilder divBuilder = new TagBuilder("div");
    divBuilder.AddCssClass(htmlHelper.ViewData.ModelState.IsValid ? HtmlHelper.ValidationSummaryValidCssClassName : HtmlHelper.ValidationSummaryCssClassName);
    divBuilder.InnerHtml = ulBuilder.ToString(TagRenderMode.Normal);
    if (formContextForClientValidation != null)
    {
        if (!htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)
        {
            divBuilder.GenerateId("validationSummary");
            formContextForClientValidation.ValidationSummaryId = divBuilder.Attributes["id"];
            formContextForClientValidation.ReplaceValidationSummary = false;
        }
    }
    return new HtmlString(divBuilder.ToString(TagRenderMode.Normal));
}

Usage:

@Html.HtmlValidationSummary()

ValidationMessageFor

Next, let’s create an extension to allow HTML in ValidationMessageFor:

public static MvcHtmlString HtmlValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string validationMessage = "", IDictionary<string, object> htmlAttributes = null)
{
    string html = htmlHelper.ValidationMessageFor(expression, validationMessage, htmlAttributes).ToString();
    html = WebUtility.HtmlDecode(html);
    return MvcHtmlString.Create(html);
}

Usage:

@* at the top of your page *@
@using YourSolution.Web.Extensions; @* should point to your ValidationExtensions namespace *@

@* a property of your ViewModel *@
@Html.LabelFor(model => model.Email)
@Html.TextBoxFor(model => model.Email)
@Html.HtmlValidationMessageFor(model => model.Email)

Then, in your Controller, you can add a ModelState error and include HTML:

ModelState.AddModelError("Email", "That email is already in use. If this is unexpected, please <a href='/Contact'>contact support</a>.");