Sandra
Sandra

Reputation:

ValidationMessage - Handle multiple errors for the same property

I'm using ValidationMessage control in MVC. When validating each property, it may have more than one error message to show, but the ValidationMessage only displays the first error message in the list.

Here is an example:

ModelState["Key"] = new ModelState();
ModelState["Key"].Errors.Add("Error 1");
ModelState["Key"].Errors.Add("Error 2");

and in the html I have: <%= Html.ValidationMessage("Key")%>

which displays: "Error 1"

I want to see all error messages on the page which will be "Error 1 Error 2"

Any idea how to do it?

Upvotes: 9

Views: 8060

Answers (6)

StuartLC
StuartLC

Reputation: 107237

As ModelState follows a dictionary pattern for errors, it seems ultimately we need to concatenate all the errors into the single ModelState key:

   ModelState["Key"].Errors.Add("Error 1. " + "Error 2");

If you use the IValidatableObject convention to perform custom validations, you can convert the validation result failures to ModelState entries as follows:

var resultsGroupedByMembers = validationResults
    .SelectMany(_ => _.MemberNames.Select(
         x => new {MemberName = x ?? "", 
                   Error = _.ErrorMessage}))
    .GroupBy(_ => _.MemberName);

foreach (var member in resultsGroupedByMembers)
{
    ModelState.AddModelError(
        member.Key,
        string.Join(". ", member.Select(_ => _.Error)));
}

The cross join is needed noting there may be more than one MemberName per Validation Result. Unbound results are bound to "" and should be available to the ValidationSummary.

Upvotes: 1

Leo Freitas
Leo Freitas

Reputation: 11

A more straight to the point approach:

Controller:

ModelState.AddModelError("other", "error 1");
ModelState.AddModelError("other", "error 2");
ModelState.AddModelError("other", "error 3");

View:

<ul>
    @foreach (var error in Html.ViewData.ModelState["other"].Errors)
    {
        <li>@error.ErrorMessage</li>
    }
</ul>

Upvotes: 1

Corin
Corin

Reputation: 2457

Based on the solutions presented here and in How to display multiple validation errors with @Html.ValidationMessageFor?, I created my own multiline validation message for a property. It behaves somewhat like ValidationSummary but can be used per field. I use it present a validation message for a collection field of a model. This allows me to present a summary message for the collection and only the collection.

public static MvcHtmlString MultilineValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
    var propertyName = ExpressionHelper.GetExpressionText(expression);
    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 + "<br />");
        }

        // Return standard ValidationMessageFor, overriding the message with our concatenated list of messages.
        var msgSpan = htmlHelper.ValidationMessageFor(expression, "{0}", htmlAttributes as IDictionary<string, object> ?? htmlAttributes);
        var msgDiv = msgSpan.ToHtmlString().Replace("span", "div");

        return new MvcHtmlString(string.Format(msgDiv, msgs.ToString()));
    }

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

Upvotes: 2

Bill Yang
Bill Yang

Reputation: 1413

I had exactly the same problem, so I created an extension method for HtmlHelper as replacement for the MVC ValidationMessage method.

The benefit of this over ValidationSummary method is that it displays error message per field so you can place it right next to each field (same as ValidationMessage method).

public static string AllValidationMessage(this HtmlHelper helper, string modelName)
{
    StringBuilder builder = new StringBuilder();
    TagBuilder ulTag = new TagBuilder("ul");
    ulTag.AddCssClass("u-error-list");

    builder.Append(ulTag.ToString(TagRenderMode.StartTag));
    if (helper.ViewData.ModelState.ContainsKey(modelName) &&
        helper.ViewData.ModelState[modelName].Errors.Count > 0)
    {
        foreach (var err in helper.ViewData.ModelState[modelName].Errors)
        {
            TagBuilder liTag = new TagBuilder("li") { InnerHtml = HttpUtility.HtmlEncode(err.ErrorMessage) };
            liTag.AddCssClass("u-error-item");
            builder.Append(liTag.ToString());
        }
    }
    builder.Append(ulTag.ToString(TagRenderMode.EndTag));

    var msgSpan = helper.ValidationMessage(modelName, "{placeholder}");

    if (msgSpan == null)
        return string.Empty;

    return msgSpan.ToHtmlString().Replace("{placeholder}", builder.ToString());
}

public static string AllValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
    return HtmlHelperExtensions.AllValidationMessage(helper, ExpressionHelper.GetExpressionText(expression));
}

Edit: added AllValidationMessageFor method
Edit: added a null check on msgSpan

Upvotes: 6

Jack Marchetti
Jack Marchetti

Reputation: 15754

Also in your Controller Action you can check the

ModelState.IsValid

and if its false, just return the View and the ValidationSumary will be populated.

Upvotes: 0

Ray Vernagus
Ray Vernagus

Reputation: 6150

With just out-of-the-box MVC, you'll have to add a ValidationSummary:

<%= Html.ValidationSummary() %>

That will show all ModelErrors.

Upvotes: 2

Related Questions