How to include a link in AddModelError message?
Asked Answered
M

4

11

I want to add a ModelState error, like so:

ModelState.AddModelError("", "Some message, <a href="/controller/action">click here</a>)

However, the link doesn't get encoded, and so is displayed like text. I tried using

<%= Html.ValidationSummary(true, "Some message")

instead of

<%: Html.ValidationSummary(true, "Some message")

But no luck.

Anyone have any idea how to get this working?

Cheers

Microgroove answered 9/7, 2011 at 22:59 Comment(0)
I
26

The simpliest way (works also with MVC 4):

In controller:

ModelState.AddModelError("", "Please click <a href=\"http://stackoverflow.com\">here</a>");

In view:

if (ViewData.ModelState.Any(x => x.Value.Errors.Any()))
{
@Html.Raw(HttpUtility.HtmlDecode(Html.ValidationSummary().ToHtmlString()))
}
Idaline answered 24/2, 2015 at 13:44 Comment(2)
This should be the accepted answer as it's clean, concise, and works well. ThanksCarmody
my scenario was to pass &nbsp; . It didn't work for that.Wherewith
P
6
<div class="validation-summary-errors">
    <ul>
    <% foreach(var error in ViewData.ModelState.Where(s => s.Value.Errors.Count!=0).SelectMany(s => s.Value.Errors)) { %>
        <li><%= error.ErrorMessage %></li>
    <% } %>
    </ul>
</div>

or in razor:

<div class="validation-summary-errors">
    <ul>
    @foreach(var error in ViewData.ModelState.Where(s => s.Value.Errors.Count!=0).SelectMany(s => s.Value.Errors)) {
        <li>@Html.Raw(error.ErrorMessage)</li>
    }
    </ul>
</div>
Parley answered 29/2, 2012 at 17:56 Comment(0)
S
5

The ValidationSummary helper automatically HTML encodes all messages. One possible workaround is to write a custom validation summary helper which doesn't HTML encode the messages:

public static class HtmlExtensions
{
    public static MvcHtmlString MyValidationSummary(this HtmlHelper htmlHelper, bool excludePropertyErrors, string message)
    {
        var formContext = htmlHelper.ViewContext.ClientValidationEnabled ?  htmlHelper.ViewContext.FormContext : null;
        if (formContext == null && htmlHelper.ViewData.ModelState.IsValid)
        {
            return null;
        }

        string messageSpan;
        if (!string.IsNullOrEmpty(message))
        {
            TagBuilder spanTag = new TagBuilder("span");
            spanTag.SetInnerText(message);
            messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
        }
        else
        {
            messageSpan = null;
        }

        var htmlSummary = new StringBuilder();
        TagBuilder unorderedList = new TagBuilder("ul");

        IEnumerable<ModelState> modelStates = null;
        if (excludePropertyErrors)
        {
            ModelState ms;
            htmlHelper.ViewData.ModelState.TryGetValue(htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix, out ms);
            if (ms != null)
            {
                modelStates = new ModelState[] { ms };
            }
        }
        else
        {
            modelStates = htmlHelper.ViewData.ModelState.Values;
        }

        if (modelStates != null)
        {
            foreach (ModelState modelState in modelStates)
            {
                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.InnerHtml = errorText;
                        htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
                    }
                }
            }
        }

        if (htmlSummary.Length == 0)
        {
            htmlSummary.AppendLine(@"<li style=""display:none""></li>");
        }

        unorderedList.InnerHtml = htmlSummary.ToString();

        TagBuilder divBuilder = new TagBuilder("div");
        divBuilder.AddCssClass((htmlHelper.ViewData.ModelState.IsValid) ? HtmlHelper.ValidationSummaryValidCssClassName : HtmlHelper.ValidationSummaryCssClassName);
        divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);

        if (formContext != null)
        {
            // client val summaries need an ID
            divBuilder.GenerateId("validationSummary");
            formContext.ValidationSummaryId = divBuilder.Attributes["id"];
            formContext.ReplaceValidationSummary = !excludePropertyErrors;
        }
        return MvcHtmlString.Create(divBuilder.ToString());
    }

    private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error, ModelState modelState)
    {
        if (!String.IsNullOrEmpty(error.ErrorMessage))
        {
            return error.ErrorMessage;
        }
        if (modelState == null)
        {
            return null;
        }

        string attemptedValue = (modelState.Value != null) ? modelState.Value.AttemptedValue : null;
        return String.Format(CultureInfo.CurrentCulture, "The value {0} is invalid.", attemptedValue);
    }
}

and then:

<%= Html.MyValidationSummary(true, "Some message") %>

Of course by doing this you should be careful as what text you are putting into those error messages as now they will not be HTML encoded. This means that if you ever wanted to use some special characters such as <, >, & into your message you will need to HTML encode it yourself or the markup will break.

Schweiz answered 10/7, 2011 at 9:23 Comment(0)
E
3

Using HttpUtility.HtmlDecode or @Html.Raw as suggested in other answers introduces a reflected XSS issue because user input is reflected as part of the error message.

The ASP.NET Framework by default will block HTML and return a validation error which doesn't reflect the original value i.e. HTML not allowed for ParameterName

However it only does this for String properties.

For none String data types the AttemptedValue is encoded before being reflected; applying HttpUtility.HtmlDecode or otherwise injecting your own HTML into validation messages and writing custom code to render HTML in any validation message, introduces a reflected XSS bug if you have any none string parameters with default validation.

Rather that disabling built in behaviour, given that you don't necessarily know where the message will be set in the future, you should write a custom ValidationSummary helper which aggregates ModelState errors and a collection of custom validation errors which you know contain HTML, and crucially that you know don't contain any user input.

Engram answered 22/9, 2020 at 15:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.