15

I can see that my ModelState.Values[x].Errors is correctly populated with the two validation errors for a single property.

If I use a @Html.ValidationSummary() in my view, it correctly displays both errors.... albeit at the top of the page, and not next to the offending input.

Using @Html.ValidationMessageFor(model => model.MyProperty) displays the first error only for that property!

How do I show both errors, next to the appropriate input?

Merenzo
  • 5,326
  • 4
  • 31
  • 46

2 Answers2

13

One solution is to implement your own extension method on HtmlHelper that does something different to the default ValidationMessageFor behavior. The sample @Html.ValidationMessagesFor method below will concatenate multiple error messages that have been added to the ModelState during server-side validation (only).

public static MvcHtmlString ValidationMessagesFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
    var propertyName = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).PropertyName;
    var modelState = htmlHelper.ViewData.ModelState;

    // If we have multiple (server-side) validation errors, collect and present them.
    if (modelState.ContainsKey(propertyName) && modelState[propertyName].Errors.Count > 1)
    {
        var msgs = new StringBuilder();
        foreach (ModelError error in modelState[propertyName].Errors)
        {
            msgs.AppendLine(error.ErrorMessage);
        }

        // Return standard ValidationMessageFor, overriding the message with our concatenated list of messages.
        return htmlHelper.ValidationMessageFor(expression, msgs.ToString(), htmlAttributes as IDictionary<string, object> ?? htmlAttributes);
    }

    // Revert to default behaviour.
    return htmlHelper.ValidationMessageFor(expression, null, htmlAttributes as IDictionary<string, object> ?? htmlAttributes);
}

This is useful if you have custom business validation you're applying across a collection belonging to your model (for example, cross-checking totals), or checking the model as a whole.

An example using this, where a collection of Order.LineItems are validated server-side:

@using MyHtmlHelperExtensions    // namespace containing ValidationMessagesFor
@using (Html.BeginForm())
{
    @Html.LabelFor(m => m.LineItems)
    <ul>
        @Html.EditorFor(m => m.LineItems)
    </ul>
    @Html.ValidationMessagesFor(m => m.LineItems)
}
Dave A-W
  • 609
  • 7
  • 13
  • Nice solution. Does this work with nested views with viewmodels? I had to add the following: `var relativePropertyName = ExpressionHelper.GetExpressionText(expression);` `var propertyName = String.Format("{0}{2}{1}", htmlHelper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix, relativePropertyName, (String.IsNullOrWhiteSpace(relativePropertyName) ? String.Empty : "."));` – Fiddles May 14 '14 at 03:31
  • Simpler way to get it to work with nested views is: propertyName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName) – Mark Shapiro Jun 01 '15 at 19:21
  • 1
    Excellent answer, and it works. Except that both my errors are on the same line. I need each error message to be on it's own line. With a
    tag in between for instance
    – theB3RV Apr 19 '16 at 20:18
  • 1
    @theB3RV or you can use the `white-space: pre-wrap;` css. – Frank Sebastià Jun 16 '16 at 06:16
5

The answer Dave A-W provided is essentially correct but will not work in all instances. To get the correct property name the source code of MVC 3 uses:

var propertyName = ExpressionHelper.GetExpressionText(expression);

This takes care of any prefixes in the form data

Jasper
  • 86
  • 1
  • 1