37

I am having a slight issue with the use of ValidationSummary(true) to display model level errors. If the ModelState does not contain model errors (i.e. ModelState.AddModelError("", "Error Description")) but contains property errors (added using data annotations) it displays the validation summary with no error information (when you view the source). My css is therefore displaying an empty red box like so:

enter image description here

If there are no property errors then no validation summary is displayed. With ValidationSummary(true) I would expect it to only display validation errors if there are model errors. What have I misunderstood?

I have a basic project as follows:

Controller:

public class HomeController : Controller
{
    public ViewResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Index(IndexViewModel model)
    {
        return View();
    }
}

Model:

public class IndexViewModel
{
    [Required]
    public string Name { get; set; }
}

View:

@model IndexViewModel

@Html.ValidationSummary(true)

@using(@Html.BeginForm())
{
    @Html.TextBoxFor(m => m.Name)
    <input type="submit" value="submit" />
}
Dangerous
  • 4,818
  • 3
  • 33
  • 48
  • Also be aware of this http://stackoverflow.com/questions/2818219/asp-net-mvc-html-validationsummarytrue-does-not-display-model-errors – maulik13 Dec 01 '14 at 12:08

12 Answers12

37
@if (ViewContext.ViewData.ModelState.Where(x => x.Key == "").Any())
{
    @Html.ValidationSummary(true, null, new { @class = "ui-state-error" })
}

This checks if there are any model wide errors and only renders the summary when there are some.

Dmitri Trofimov
  • 1,061
  • 10
  • 5
  • 1
    I think a custom helper (as in the accept answer) but using this condition check would be better - for now I've just used this inline. To save a bit of typing you can just use the following: @if(ViewData.ModelState.Any(_ => _.Key == string.Empty)) { ... }. I always prefer to use string.Empty instead of "" as it shows explicit intention due to the extra effort in typing it, whereas "" could just be someone got side tracked and forgot to come back and fill it in. – Peter Nov 13 '13 at 09:48
  • 6
    Rather than enumerating the keys with `Any`, you can directly use `ContainsKey`: `if (ViewContext.ViewData.ModelState.ContainsKey(string.Empty))` – Kevin Gosse Nov 15 '13 at 15:20
28

I think there is something wrong with the ValidationSummary helper method. You could easily create a custom helper method that wraps the built-in ValidationSummary.

public static MvcHtmlString CustomValidationSummary(this HtmlHelper htmlHelper, bool excludePropertyErrors)
{
  var htmlString = htmlHelper.ValidationSummary(excludePropertyErrors);

  if (htmlString != null)
  {
    XElement xEl = XElement.Parse(htmlString.ToHtmlString());

    var lis = xEl.Element("ul").Elements("li");

    if (lis.Count() == 1 && lis.First().Value == "")
      return null;
  }

  return htmlString;
}

Then from your view,

@Html.CustomValidationSummary(true)
freedomn-m
  • 27,664
  • 8
  • 35
  • 57
VJAI
  • 32,167
  • 23
  • 102
  • 164
  • 3
    It seems this is also an issue with the current MVC4 that I have? I decided to go with this solution in the end which works fine. Thanks for your help. – Dangerous Aug 28 '12 at 11:01
22

Check this question too.

You could hide the summary with CSS:

.validation-summary-valid { display:none; }

Also, you could put the validation summary before the Html.BeginForm().

Community
  • 1
  • 1
eKek0
  • 23,005
  • 25
  • 91
  • 119
6

Another way of doing it is to check if there are any li elements, if not just hide validation-summary-errors

<script type="text/javascript">
     $(document).ready(function () {
          if ($(".validation-summary-errors li:visible").length === 0) {
               $(".validation-summary-errors").hide();
          }
     });
</script>
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
2

I know a workaround has been found but I had a similar problem with an easier solution.

Is your validation summary rendering with css class "validation-summary-valid" or "validation-summary-errors"? The first class is applied with the validation summary is empty, the second is applied when it's populated.

I've noticed that a placeholder div is rendered if the validation summary contains no errors so client side validation can display it if any fields fail validation.

In the default MVC stylesheet 'Site.css' they just suppress displaying the empty validation summary with 'display:none;' e.g. .validation-summary-valid { display: none; }

Mat Brunt
  • 21
  • 1
  • 3
    This is not correct. If there are any errors (whether at model-level or property-level) the `validation-summary-errors` class is always applied. The `validation-summary-valid` is only applied when there are NO errors. So if you are using a `Html.ValidationSummary(true)` and empty `div` with class `validation-summary-errors` is added, even when there are only property-level errors and no model-level errors. (We have the same situation with a login form, where we use *either* model-level or property-level error messages, not both. – Chris Feb 21 '13 at 16:07
1

Another variation of the fix with Bootstrap classes is:

public static class ValidationSummaryExtensions
{
    public static MvcHtmlString CleanValidationSummary(this HtmlHelper htmlHelper, bool excludePropertyErrors, string message = null)
    {
        if(htmlHelper == null) throw new ArgumentNullException("htmlHelper");

        MvcHtmlString validationSummary = null;
        if (htmlHelper.ViewData.ModelState.ContainsKey(string.Empty))
        {
            var htmlAttributes = new { @class = "alert alert-danger" };
            validationSummary = htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes);
        }

        return validationSummary;
    }
}
Vladislav Kostenko
  • 1,155
  • 11
  • 18
1

I was having this empty validation summary issue. I just set excludePropertyErrors to false - and it added the errors to the validation summary.

@Html.ValidationSummary(false)

I realise this isn't necessarily what is being asked here - although this does solve the empty validation summary issue - and is an option if you're having this problem.

niico
  • 11,206
  • 23
  • 78
  • 161
1

@if (ViewContext.ViewData.ModelState.Count > 0)

{

//Your content

}

Would work like charm.

Vijay Patel
  • 163
  • 2
  • 14
1

ValidationSummary accepts an optional message parameter. If you set this parameter, then the box doesn't look as silly.

@Html.ValidationSummary(true, "Sorry, that didn't work. Please check the details submitted and try again.")

enter image description here

thelem
  • 2,642
  • 1
  • 24
  • 34
0

I know this topic is old, but the behavior still exists even in MVC 5. It's definitely not the behavior most of us expect. When we want a "non-property" ModelState error, we do this:

ModelState.AddModelError(string.Empty, ex.Message);

Since we only want to display the summary if the empty key exists, this HtmlHelper does the trick and is cleaner than the accepted answer IMO:

public static MvcHtmlString CustomValidationSummary(this HtmlHelper htmlHelper,
    bool excludePropertyErrors, string message = null)
{
    // Don't render summary if there are no empty keys when excluding property errors
    if (excludePropertyErrors && !htmlHelper.ViewData.ModelState.ContainsKey(string.Empty))
        return null;

    // Use default
    return htmlHelper.ValidationSummary(excludePropertyErrors, message);
}

When excluding property errors, it's likely that you don't need the summary DIV available for client-side validation, so not rendering it at all is fine. I hope this helps some of you.

Jason Butera
  • 2,376
  • 3
  • 29
  • 46
0

Update for .net core Here's a .net core equivalent for VJAI's answer:

[HtmlTargetElement("div", Attributes = "asp-errors-only-validation-summary")]
public class ErrorsOnlyValidationSummaryTagHelper : ValidationSummaryTagHelper
{
    public ErrorsOnlyValidationSummaryTagHelper(IHtmlGenerator generator)
        : base(generator)
    {
        base.ValidationSummary = ValidationSummary.All;
    }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));
        if (output == null)
            throw new ArgumentNullException(nameof(output));

        if (this.ValidationSummary == ValidationSummary.None)
            return;

        TagBuilder validationSummary = this.Generator.GenerateValidationSummary(this.ViewContext, this.ValidationSummary == ValidationSummary.ModelOnly, (string)null, (string)null, (object)null);
        if (validationSummary == null)
        {
            output.SuppressOutput();
        }
        else
        {
            if (!validationSummary.HasInnerHtml)
                return;

            var xel = XElement.Parse(ToHtmlString(validationSummary));

            var lis = xel.Element("ul")?.Elements("li");
            if (lis != null && lis.Count() == 1 && lis.First().Value == string.Empty)
                return;

            output.MergeAttributes(validationSummary);
            output.PostContent.AppendHtml((IHtmlContent)validationSummary.InnerHtml);
        }
    }

    public string ToHtmlString(IHtmlContent content)
    {
        if (content is HtmlString htmlString)
        {
            return htmlString.Value ?? string.Empty;
        }

        using (var writer = new StringWriter())
        {
            content.WriteTo(writer, HtmlEncoder.Default);
            return writer.ToString();
        }
    }
}

Usage:

<div asp-errors-only-validation-summary></div>
freedomn-m
  • 27,664
  • 8
  • 35
  • 57
-1

It's the validation scripts doing this.

Change the following the web.config

<add key="ClientValidationEnabled" value="false" />

It should be false

gg.
  • 658
  • 3
  • 14
  • Won't this disable the client side validation scripts for the entire website? What if I want to use the client side validation scripts? – Dangerous Jun 19 '15 at 14:00
  • This is PRECISELY what the problem is. Html.ValidationSummary is actually rendering the error messages... they're present in the HTTP response stream, but they not in the final HTML of the page. A client-side SCRIPT is deleting them, and this is indeed it. What a screwed up configuration. – Triynko Dec 14 '16 at 17:11