How to display html elements like links in errors

2019-02-18 23:07发布

问题:

One of my error message renders a link. However, Html.ValidationSummary() encodes it and therefore it displays as follow:

An account with the mobile or email you have specified already exists. If you have forgotten your password, please <a href="/account/Reset">Reset</a> it.

Instead, it should render as:

An account with the mobile or email you have specified already exists. If you have forgotten your password, please Reset it.

The error is added to the ModelState inside view as follows:

if (...)
{
    ViewData.ModelState.AddModelError(string.Empty, string.Format("An account with the mobile or email you have specified already exists. If you have forgotten your password, please {0} it.", Html.ActionLink("Reset", "Reset")));
}

In short, how should I prevent Html.ValidationSummarry() to selectively/entirely encoding html in errors.

回答1:

The current HTML helpers for displaying error messages do not support this. However, you could write your own HTML helpers that display the error message without HTML escaping it, i.e. they would treat the error message as raw HTML.

As a starting point, you could use the ASP.NET MVC source code from Codeplex, specifically the ValidationSummary method of the ValidationExtensions class:

    public static string ValidationSummary(this HtmlHelper htmlHelper, string message, IDictionary<string, object> htmlAttributes) {
        // Nothing to do if there aren't any errors
        if (htmlHelper.ViewData.ModelState.IsValid) {
            return null;
        }

        string messageSpan;
        if (!String.IsNullOrEmpty(message)) {
            TagBuilder spanTag = new TagBuilder("span");
            spanTag.MergeAttributes(htmlAttributes);
            spanTag.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);
            spanTag.SetInnerText(message);
            messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
        }
        else {
            messageSpan = null;
        }

        StringBuilder htmlSummary = new StringBuilder();
        TagBuilder unorderedList = new TagBuilder("ul");
        unorderedList.MergeAttributes(htmlAttributes);
        unorderedList.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);

        foreach (ModelState modelState in htmlHelper.ViewData.ModelState.Values) {
            foreach (ModelError modelError in modelState.Errors) {
                string errorText = GetUserErrorMessageOrDefault(htmlHelper.ViewContext.HttpContext, modelError, null /* modelState */);
                if (!String.IsNullOrEmpty(errorText)) {
                    TagBuilder listItem = new TagBuilder("li");
                    listItem.SetInnerText(errorText);
                    htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
                }
            }
        }

        unorderedList.InnerHtml = htmlSummary.ToString();

        return messageSpan + unorderedList.ToString(TagRenderMode.Normal);
    }

You can then change this method to treat the error message as raw HTML.

Two warnings though:

  1. You're changing the meaning of certain properties of the ModelState class. While you get away with using your own HTML helpers now, a future version of ASP.NET MVC might introduce changes that no longer work with this approach.

  2. Be very careful about not using error messages that aren't properly escaped so you don't expose your web app to XSS attacks. Certain standard validation annotation might not work any longer since they don't HTML escape the error message.