Render HTML tags inside of HTML.ValidationMessageF

2019-04-02 13:18发布

问题:

I'm trying to show a link as part of the validation message for a field. I'm using data attributes with custom error messages to set it:

[Required(ErrorMessage = "Message <a href='#'>link</a>")]
public string Field{ get; set; }

But when it renders the tags are escaped and literally prints:

Message <a href='#'>link</a>

Is it possible to have the link as part of the validation message but render correctly?

In case anyone's interested, here's how I accomplished it

public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
    return ValidationHTMLMessageFor(helper, expression, (object)null);
}
public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
    return ValidationHTMLMessageFor(helper, expression, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
    string propertyName = ExpressionHelper.GetExpressionText(expression);
    string name = helper.AttributeEncode(helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName));

    if (helper.ViewData.ModelState[name] == null ||
        helper.ViewData.ModelState[name].Errors == null ||
        helper.ViewData.ModelState[name].Errors.Count == 0)
    {
        return MvcHtmlString.Empty;
    }

    string errors = "";
    foreach (ModelError error in helper.ViewData.ModelState[name].Errors)
    {
        TagBuilder tag = new TagBuilder("span");
        tag.Attributes.Add("class", HtmlHelper.ValidationMessageCssClassName);
        tag.MergeAttributes(htmlAttributes);
        tag.Attributes.Add("data-valmsg-for", name);
        tag.Attributes.Add("data-valmsg-replace", "true");

        var text = tag.ToString(TagRenderMode.StartTag);
        text += error.ErrorMessage;
        text += tag.ToString(TagRenderMode.EndTag);
        errors += text;
    }

    return MvcHtmlString.Create(errors);

}

Thanks Darin for pointing me in the right direction. I also found this that I used as a template Customize Html.ValidationMessageFor doesn't work in client side.

I'm new to this, so if anyone has any suggestions, please post. Thanks!

回答1:

The code above doesn't work with client-side validation since it doesn't produce the tags required for client-side validation.

Here's an improvement on it that does that:

public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
  string propertyName = ExpressionHelper.GetExpressionText(expression);
  string name = helper.AttributeEncode(helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName));

  TagBuilder tag = new TagBuilder("span");
  tag.Attributes.Add("class", HtmlHelper.ValidationMessageCssClassName);
  tag.MergeAttributes(htmlAttributes);
  tag.Attributes.Add("data-valmsg-for", name);
  tag.Attributes.Add("data-valmsg-replace", "true");
  var returnTag = new StringBuilder(tag.ToString(TagRenderMode.StartTag));

  if (helper.ViewData.ModelState[name] != null &&
      helper.ViewData.ModelState[name].Errors != null &&
      helper.ViewData.ModelState[name].Errors.Count > 0)
  {
    foreach (ModelError error in helper.ViewData.ModelState[name].Errors)
    {
      returnTag.Append(error.ErrorMessage);
    }
  }
  returnTag.Append(tag.ToString(TagRenderMode.EndTag));
  return MvcHtmlString.Create(returnTag.ToString());
}

Thanks for the original post - it was very helpful!



回答2:

Yes, it is possible but not with the standard helpers (ValidationSummary and ValidationMessageFor). You will have to write a custom helper to render those messages if you want to achieve that. You may take a look at the following post for an example of how to write a custom ValidationSummary helper that doesn't HTML encode the error messages as the standard one.



回答3:

I'm using MVC4 so I can't say for sure about MVC3.

The only way I was able to insert a <br> tag (in my case), was to start with Jerode's method, but I also had to put a marker in the message string and replace it in the razor. So the sent message was "Line 1[BR]Line2". The razor was @Html.Raw(Html.ValidationMessageFor(x => Model.MyProperty).ToString().Replace("[BR]", "&lt;br&gt;"))

Hope that helps.



回答4:

Another approach would be to just put the validation item inside of the Html.Raw.

@Html.Raw(Html.ValidationMessageFor(x => Model.MyProperty))

This is not as nice as the approach as the suggestions but should accomplish what you are looking for.



回答5:

Building on the answer from @shycohen:

    public static MvcHtmlString HtmlValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
    {
        string propertyName = ExpressionHelper.GetExpressionText(expression);
        string name = helper.AttributeEncode(helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName));

        TagBuilder tag = new TagBuilder("span");

        tag.Attributes.Add("data-valmsg-for", name);
        tag.Attributes.Add("data-valmsg-replace", "true");

        if (htmlAttributes != null)
        {
            tag.MergeAttributes((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }

        tag.AddCssClass(HtmlHelper.ValidationMessageCssClassName);

        var returnTag = new StringBuilder(tag.ToString(TagRenderMode.StartTag));

        if (helper.ViewData.ModelState[name] != null &&
            helper.ViewData.ModelState[name].Errors != null &&
            helper.ViewData.ModelState[name].Errors.Count > 0)
        {
            foreach (ModelError error in helper.ViewData.ModelState[name].Errors)
            {
                returnTag.Append(error.ErrorMessage);
            }
        }

        returnTag.Append(tag.ToString(TagRenderMode.EndTag));

        return MvcHtmlString.Create(returnTag.ToString());
    }

The htmlAttributes parameter is now optional and passed as an anonymous object to closer match ValidationMessageFor.

The other issue was not being able to add extra classes via htmlAttributes, as TagBuilder.MergeAttributes doesn't merge attribute values. This is solved by using TagBuilder.AddCssClass after merging the attributes to set the validation class.