// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.ModelBinding; using Xunit; namespace Microsoft.AspNetCore.Mvc.Rendering { public class HtmlHelperValidationSummaryTest { // Message, HTML attributes, tag -> expected result. public static TheoryData ValidValidationSummaryData { get { var attributes = new { @class = "wood smoke", attribute_name = "attribute-value", }; var dictionary = new Dictionary { { "class", "wood smoke" }, { "attribute-name", "attribute-value" }, }; var basicDiv = "
" + "
  • " + Environment.NewLine + "
"; var divWithAttributes = "
    " + "
  • " + Environment.NewLine + "
"; var divWithMessage = "
" + "HtmlEncode[[This is my message]]" + Environment.NewLine + "
  • " + Environment.NewLine + "
"; var divWithH3Message = "
" + "

HtmlEncode[[This is my message]]

" + Environment.NewLine + "
  • " + Environment.NewLine + "
"; var divWithMessageAndAttributes = "
" + "HtmlEncode[[This is my message]]" + Environment.NewLine + "
  • " + Environment.NewLine + "
"; var divWithH3MessageAndAttributes = "
" + "

HtmlEncode[[This is my message]]

" + Environment.NewLine + "
  • " + Environment.NewLine + "
"; return new TheoryData { { null, null, null, basicDiv }, { null, null, "h3", basicDiv }, { null, attributes, null, divWithAttributes }, { null, attributes, "h3", divWithAttributes }, { null, dictionary, null, divWithAttributes }, { null, dictionary, "h3", divWithAttributes }, { "This is my message", null, null, divWithMessage }, { "This is my message", null, "h3", divWithH3Message }, { "This is my message", attributes, null, divWithMessageAndAttributes }, { "This is my message", attributes, "h3", divWithH3MessageAndAttributes }, { "This is my message", dictionary, null, divWithMessageAndAttributes }, { "This is my message", dictionary, "h3", divWithH3MessageAndAttributes }, }; } } // Exclude property errors, client validation enabled -> expected result with model error, with property error. public static TheoryData OneErrorValidationSummaryData { get { var divWithError = "
    " + "
  • HtmlEncode[[This is my validation message]]
  • " + Environment.NewLine + "
"; var divWithErrorAndSummary = "
    " + "
  • HtmlEncode[[This is my validation message]]
  • " + Environment.NewLine + "
"; return new TheoryData { { false, false, divWithError, divWithError }, { false, true, divWithErrorAndSummary, divWithErrorAndSummary }, { true, false, divWithError, string.Empty }, { true, true, divWithError, string.Empty }, }; } } // Exclude property errors, prefix -> expected result public static TheoryData MultipleErrorsValidationSummaryData { get { var divWithRootError = "
    " + "
  • HtmlEncode[[This is an error for the model root.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for the model root.]]
  • " + Environment.NewLine + "
"; var divWithProperty3Error = "
    " + "
  • HtmlEncode[[This is an error for Property3.]]
  • " + Environment.NewLine + "
"; var divWithAllErrors = "
    " + "
  • HtmlEncode[[This is an error for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[The value '' is not valid for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.OrderedProperty3.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.OrderedProperty2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for the model root.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for the model root.]]
  • " + Environment.NewLine + "
"; return new TheoryData { { false, string.Empty, divWithAllErrors }, { false, "Property2", divWithAllErrors }, { false, "some.unrelated.prefix", divWithAllErrors }, { true, string.Empty, divWithRootError }, { true, "Property3", divWithProperty3Error }, { true, "some.unrelated.prefix", string.Empty }, }; } } [Theory] [MemberData(nameof(ValidValidationSummaryData))] public void ValidationSummary_AllValid_ReturnsExpectedDiv( string message, object htmlAttributes, string tag, string expected) { // Arrange var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: message, htmlAttributes: htmlAttributes, tag: tag); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Theory] [MemberData(nameof(ValidValidationSummaryData))] public void ValidationSummary_ExcludePropertyErrorsAllValid_ReturnsEmpty( string message, object htmlAttributes, string tag, string ignored) { // Arrange var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); // Act var result = html.ValidationSummary( excludePropertyErrors: true, message: message, htmlAttributes: htmlAttributes, tag: tag); // Assert Assert.Equal(HtmlString.Empty, result); } [Theory] [MemberData(nameof(ValidValidationSummaryData))] public void ValidationSummary_ClientValidationDisabledAllValid_ReturnsEmpty( string message, object htmlAttributes, string tag, string ignored) { // Arrange var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewContext.ClientValidationEnabled = false; // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: message, htmlAttributes: htmlAttributes, tag: tag); // Assert Assert.Equal(HtmlString.Empty, result); } [Theory] [MemberData(nameof(OneErrorValidationSummaryData))] public void ValidationSummary_InvalidModel_ReturnsExpectedDiv( bool excludePropertyErrors, bool clientValidationEnabled, string expected, string ignored) { // Arrange var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewContext.ClientValidationEnabled = clientValidationEnabled; html.ViewData.ModelState.AddModelError(string.Empty, "This is my validation message"); // Act var result = html.ValidationSummary( excludePropertyErrors: excludePropertyErrors, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Theory] [MemberData(nameof(OneErrorValidationSummaryData))] public void ValidationSummary_InvalidModelWithPrefix_ReturnsExpectedDiv( bool excludePropertyErrors, bool clientValidationEnabled, string expected, string ignored) { // Arrange var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewContext.ClientValidationEnabled = clientValidationEnabled; html.ViewData.TemplateInfo.HtmlFieldPrefix = "this.is.my.prefix"; html.ViewData.ModelState.AddModelError("this.is.my.prefix", "This is my validation message"); // Act var result = html.ValidationSummary( excludePropertyErrors, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Theory] [MemberData(nameof(OneErrorValidationSummaryData))] public void ValidationSummary_OneInvalidProperty_ReturnsExpectedDiv( bool excludePropertyErrors, bool clientValidationEnabled, string ignored, string expected) { // Arrange var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewContext.ClientValidationEnabled = clientValidationEnabled; html.ViewData.ModelState.AddModelError("Property1", "This is my validation message"); // Act var result = html.ValidationSummary( excludePropertyErrors, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Theory] [MemberData(nameof(MultipleErrorsValidationSummaryData))] public void ValidationSummary_MultipleErrors_ReturnsExpectedDiv( bool excludePropertyErrors, string prefix, string expected) { // Arrange var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewData.TemplateInfo.HtmlFieldPrefix = prefix; AddMultipleErrors(html.ViewData.ModelState); // Act var result = html.ValidationSummary( excludePropertyErrors, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ValidationSummary_OrdersCorrectlyWhenElementsAreRemovedFromDictionary() { // Arrange var expected = "
    " + "
  • HtmlEncode[[New error for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.OrderedProperty3.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for the model root.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for the model root.]]
  • " + Environment.NewLine + "
"; var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); AddMultipleErrors(html.ViewData.ModelState); html.ViewData.ModelState.RemoveAll(m => m.Property2); html.ViewData.ModelState.Remove(m => m.Property3); html.ViewData.ModelState.Remove(m => m.Property3.OrderedProperty2); html.ViewData.ModelState.AddModelError("Property2", "New error for Property2."); // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ValidationSummary_IncludesErrorsThatAreNotPartOfMetadata() { // Arrange var expected = "
    " + "
  • HtmlEncode[[This is an error for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[The value '' is not valid for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.OrderedProperty3.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.OrderedProperty2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for the model root.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for the model root.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[non-existent-error1]]
  • " + Environment.NewLine + "
  • HtmlEncode[[non-existent-error2]]
  • " + Environment.NewLine + "
  • HtmlEncode[[non-existent-error3]]
  • " + Environment.NewLine + "
"; var model = new ValidationModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); AddMultipleErrors(html.ViewData.ModelState); html.ViewData.ModelState.AddModelError("non-existent-property1", "non-existent-error1"); html.ViewData.ModelState.AddModelError("non.existent.property2", "non-existent-error2"); html.ViewData.ModelState.AddModelError("non.existent[0].property3", "non-existent-error3"); // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ValidationSummary_IncludesErrorsForCollectionProperties() { // Arrange var expected = "
    " + "
  • HtmlEncode[[Property1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[Property2[0].OrderedProperty1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[Property2[0].Property1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[Property2[2].Property3 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[Property2[10].Property2 error]]
  • " + Environment.NewLine + "
"; var model = new ModelWithCollection(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewData.ModelState.AddModelError("Property1", "Property1 error"); html.ViewData.ModelState.AddModelError("Property2[0].OrderedProperty1", "Property2[0].OrderedProperty1 error"); html.ViewData.ModelState.AddModelError("Property2[10].Property2", "Property2[10].Property2 error"); html.ViewData.ModelState.AddModelError("Property2[2].Property3", "Property2[2].Property3 error"); html.ViewData.ModelState.AddModelError("Property2[0].Property1", "Property2[0].Property1 error"); // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ValidationSummary_IncludesErrorsForTopLevelCollectionProperties() { // Arrange var expected = "
    " + "
  • HtmlEncode[[[0].OrderedProperty2 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[0].OrderedProperty1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[0].Property1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[2].OrderedProperty3 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[2].Property3 error]]
  • " + Environment.NewLine + "
"; var model = new OrderedModel[5]; var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewData.ModelState.AddModelError("[0].OrderedProperty2", "[0].OrderedProperty2 error"); html.ViewData.ModelState.AddModelError("[0].Property1", "[0].Property1 error"); html.ViewData.ModelState.AddModelError("[0].OrderedProperty1", "[0].OrderedProperty1 error"); html.ViewData.ModelState.AddModelError("[2].Property3", "[2].Property3 error"); html.ViewData.ModelState.AddModelError("[2].OrderedProperty3", "[2].OrderedProperty3 error"); // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ValidationSummary_IncludesErrorsForPropertiesOnCollectionTypes() { // Arrange var expected = "
    " + "
  • HtmlEncode[[[0].OrderedProperty2 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[0].OrderedProperty1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[0].Property1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[2].OrderedProperty3 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[[2].Property3 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[OrderedProperty1 error]]
  • " + Environment.NewLine + "
  • HtmlEncode[[OrderedProperty2 error]]
  • " + Environment.NewLine + "
"; var model = new OrderedModel[5]; var html = DefaultTemplatesUtilities.GetHtmlHelper(model); html.ViewData.ModelState.AddModelError("[0].OrderedProperty2", "[0].OrderedProperty2 error"); html.ViewData.ModelState.AddModelError("[0].Property1", "[0].Property1 error"); html.ViewData.ModelState.AddModelError("[0].OrderedProperty1", "[0].OrderedProperty1 error"); html.ViewData.ModelState.AddModelError("[2].Property3", "[2].Property3 error"); html.ViewData.ModelState.AddModelError("[2].OrderedProperty3", "[2].OrderedProperty3 error"); html.ViewData.ModelState.AddModelError("OrderedProperty1", "OrderedProperty1 error"); html.ViewData.ModelState.AddModelError("OrderedProperty2", "OrderedProperty2 error"); // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ValidationSummary_ErrorsInModelUsingOrder_SortsErrorsAsExpected() { // Arrange var expected = "
    " + "
  • HtmlEncode[[This is an error for OrderedProperty3.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for OrderedProperty2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for OrderedProperty2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is yet-another error for OrderedProperty2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for OrderedProperty1.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property3.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property1.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for Property1.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is another error for Property2.]]
  • " + Environment.NewLine + "
  • HtmlEncode[[This is an error for LastProperty.]]
  • " + Environment.NewLine + "
"; var model = new OrderedModel(); var html = DefaultTemplatesUtilities.GetHtmlHelper(model); AddOrderedErrors(html.ViewData.ModelState); // Act var result = html.ValidationSummary( excludePropertyErrors: false, message: null, htmlAttributes: null, tag: null); // Assert Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result)); } [Fact] public void ValidationSummary_UsesValuesFromModelState() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(); // Assert Assert.Equal( "
" + "
  • HtmlEncode[[Error for Property1]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } [Fact] public void ValidationSummary_ExcludesPropertyErrors() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(excludePropertyErrors: true); // Assert Assert.Equal(HtmlString.Empty, validationSummaryResult); } [Fact] public void ValidationSummary_UsesSpecifiedMessage() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(message: "Custom Message"); // Assert Assert.Equal( "
" + "HtmlEncode[[Custom Message]]" + Environment.NewLine + "
  • HtmlEncode[[Error for Property1]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } [Fact] public void ValidationSummary_UsesSpecifiedTag() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(message: "Custom Message", tag: "div"); // Assert Assert.Equal( "
" + "
HtmlEncode[[Custom Message]]
" + Environment.NewLine + "
  • HtmlEncode[[Error for Property1]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } [Fact] public void ValidationSummary_UsesSpecifiedMessageAndExcludesPropertyErrors() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError(string.Empty, "Error for root"); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(excludePropertyErrors: true, message: "Custom Message"); // Assert Assert.Equal( "
HtmlEncode[[Custom Message]]" + Environment.NewLine + "
  • HtmlEncode[[Error for root]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } [Fact] public void ValidationSummary_UsesSpecifiedHtmlAttributes() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(message: "Custom Message", htmlAttributes: new { attr = "value" }); // Assert Assert.Equal( "
" + "HtmlEncode[[Custom Message]]" + Environment.NewLine + "
  • HtmlEncode[[Error for Property1]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } [Fact] public void ValidationSummary_UsesSpecifiedHtmlAttributesAndTag() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(message: "Custom Message", htmlAttributes: new { attr = "value" }, tag: "div"); // Assert Assert.Equal( "
" + "
HtmlEncode[[Custom Message]]
" + Environment.NewLine + "
  • HtmlEncode[[Error for Property1]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } [Fact] public void ValidationSummary_UsesSpecifiedUsesSpecifiedTagAndExcludesPropertyErrors() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError(string.Empty, "Error for root"); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary(excludePropertyErrors: true, message: "Custom Message", tag: "div"); // Assert Assert.Equal( "
HtmlEncode[[Custom Message]]
" + Environment.NewLine + "
  • HtmlEncode[[Error for root]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } [Fact] public void ValidationSummary_UsesSpecifiedUsesSpecifiedHtmlAttributesAndExcludesPropertyErrors() { // Arrange var helper = DefaultTemplatesUtilities.GetHtmlHelper(); helper.ViewData.ModelState.AddModelError(string.Empty, "Error for root"); helper.ViewData.ModelState.AddModelError("Property1", "Error for Property1"); // Act var validationSummaryResult = helper.ValidationSummary( excludePropertyErrors: true, message: "Custom Message", htmlAttributes: new { attr = "value" }); // Assert Assert.Equal( "
HtmlEncode[[Custom Message]]" + Environment.NewLine + "
  • HtmlEncode[[Error for root]]
  • " + Environment.NewLine + "
", HtmlContentUtilities.HtmlContentToString(validationSummaryResult)); } // Adds errors for various parts of the model, including the root. private void AddMultipleErrors(ModelStateDictionary modelState) { modelState.AddModelError("Property3.Property2", "This is an error for Property3.Property2."); modelState.AddModelError("Property3.OrderedProperty3", "This is an error for Property3.OrderedProperty3."); modelState.AddModelError("Property3.OrderedProperty2", "This is an error for Property3.OrderedProperty2."); modelState.SetModelValue("Property3.Empty", rawValue: null, attemptedValue: null); modelState.MarkFieldValid("Property3.Empty"); var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForProperty(typeof(ValidationModel), nameof(ValidationModel.Property3)); modelState.AddModelError("Property3", "This is an error for Property3."); modelState.AddModelError("Property3", new InvalidCastException("Exception will be ignored."), metadata); metadata = provider.GetMetadataForProperty(typeof(ValidationModel), nameof(ValidationModel.Property2)); modelState.AddModelError("Property2", "This is an error for Property2."); modelState.AddModelError("Property2", "This is another error for Property2."); modelState.AddModelError("Property2", new OverflowException("Produces invalid value message"), metadata); metadata = provider.GetMetadataForType(typeof(ValidationModel)); modelState.AddModelError(string.Empty, "This is an error for the model root."); modelState.AddModelError(string.Empty, "This is another error for the model root."); modelState.AddModelError(string.Empty, new InvalidOperationException("Another ignored Exception."), metadata); } // Adds one or more errors for all properties in OrderedModel. But adds errors out of order. private void AddOrderedErrors(ModelStateDictionary modelState) { var provider = new EmptyModelMetadataProvider(); var metadata = provider.GetMetadataForProperty(typeof(OrderedModel), nameof(OrderedModel.Property3)); modelState.AddModelError("Property3", "This is an error for Property3."); modelState.AddModelError("Property3", new InvalidCastException("An ignored Exception."), metadata); modelState.AddModelError("Property2", "This is an error for Property2."); modelState.AddModelError("Property2", "This is another error for Property2."); modelState.AddModelError("OrderedProperty3", "This is an error for OrderedProperty3."); modelState.AddModelError("OrderedProperty2", "This is an error for OrderedProperty2."); modelState.AddModelError("OrderedProperty2", "This is another error for OrderedProperty2."); modelState.AddModelError("LastProperty", "This is an error for LastProperty."); modelState.AddModelError("Property1", "This is an error for Property1."); modelState.AddModelError("Property1", "This is another error for Property1."); modelState.AddModelError("OrderedProperty1", "This is an error for OrderedProperty1."); modelState.AddModelError("OrderedProperty2", "This is yet-another error for OrderedProperty2."); modelState.SetModelValue("Empty", rawValue: null, attemptedValue: null); modelState.MarkFieldValid("Empty"); } private class ValidationModel { public string Property1 { get; set; } public string Property2 { get; set; } public OrderedModel Property3 { get; set; } } private class OrderedModel { [Display(Order = 10001)] public string LastProperty { get; set; } public string Property3 { get; set; } public string Property1 { get; set; } public string Property2 { get; set; } [Display(Order = 23)] public string OrderedProperty3 { get; set; } [Display(Order = 23)] public string OrderedProperty2 { get; set; } [Display(Order = 23)] public string OrderedProperty1 { get; set; } // Exists to ensure #4989 does not regress. Issue specific to case where collection has a ModelStateEntry // but no element does. public byte[] Empty { get; set; } } private class ModelWithCollection { public string Property1 { get; set; } public List Property2 { get; set; } } private class CollectionType : Collection { [Display(Order = 1)] public string OrderedProperty2 { get; set; } [Display(Order = 2)] public string OrderedProperty1 { get; set; } } } }