Search code examples
c#asp.net-core-mvctag-helpersasp.net-core-mvc-2.0asp.net-core-mvc-2.1

ASP.net Core 2.1 MVC Validation Tag Helper - output HTML (ie stop HTML Encoding)


I have a registration page, if the user types an email address that's already registered I need to output a link for them to login in the validation message.

However, validation tag helpers HTML encode error messages, so the link is displayed as HTML.

In the view:

@model Site.ViewModels.RegisterEmail
<h1>Register</h1>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Email">
            <div class="form-group">
                <label asp-for="Email" class="control-label"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Continue" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

In the controller:

var LoginLink = $"<a href='{Request.Scheme}://{Request.Host}{Request.PathBase}/Login'>Login</a>";


if (CustomerRepo.CustomerEmailExists(RegisterEmail.Email))
    ModelState.AddModelError("Email", $"Email already registered. {LoginLink} with your email, or register below");

How can I prevent the validator HTML encoding, or otherwise properly display the HTML link in the validation message?

I can't find anything on Google about this.

Thanks.


Solution

  • Extend the validation message tag helper

    The first step is to create the tag helper that extends the existing validation message tag helper. You need to set its attribute to be asp-validation-for so that it would get called.

    You also need to declare a field to indicate whether you want to output raw HTML or not. I called it OutputHtml and denoted the attribute name as asp-validation-output-html.

    using System.IO;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc.TagHelpers;
    using Microsoft.AspNetCore.Mvc.ViewFeatures;
    using Microsoft.AspNetCore.Razor.TagHelpers;
    
    namespace DL.SO.Framework.Mvc.TagHelpers
    {
        [HtmlTargetElement("span", Attributes = ForAttributeName)]
        public class RawHtmlValidationMessageTagHelper : ValidationMessageHelper
        {
            private const string ForAttributeName = "asp-validation-for";
    
            [HtmlAttributeName("asp-validation-output-html")]
            public bool OutputHtml { get; set; }
    
            public RawHtmlValidationMessageTagHelper(IHtmlGenerator generator) : base(generator) {}
    
            public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
            {
                await base.ProcessAsync(context, output);
    
                if (this.OutputHtml)
                {
                    using (var writer = new StringWriter())
                    {
                        output.WriteTo(writer, NullHtmlEncoder.Default);
    
                        output.Content.SetHtmlContent(writer.ToString());
                    }
                }
            }
        }
    }
    

    The trick here is to use NullHtmlEncoder to write tag helper's output, without encoding, to a writer first, and then write the writer's content back to tag helper's Html content.

    On the view

    <form asp-action="Email">
        <div class="form-group">
            <label asp-for="Email" class="control-label"></label>
            <input asp-for="Email" class="form-control" />
            <span 
                asp-validation-for="Email"
                asp-validation-output-html="true"
                class="text-danger">
            </span>
        </div>
        <div class="form-group">
            <input type="submit" value="Continue" class="btn btn-default" />
        </div>
    </form>
    

    Disclaimer: There might be better/smarter ways to do it but here is just my 0.02.