Change `ValidationSummaryTagHelper`'s property to `ValidationSummary` enum

- #1685
- move `ValidationSummary` type to the `Microsoft.AspNet.Mvc` namespace
- update tests and samples to match
- remove tests for case-insensitivity of `ValidationSummary` property values
This commit is contained in:
Doug Bunting 2015-01-16 13:12:06 -08:00
parent 60fa4a6f45
commit 9b2a9e3976
11 changed files with 125 additions and 110 deletions

View File

@ -14,13 +14,14 @@
<div class="form-horizontal">
@* validation summary tag helper will target just <div/> elements and append the list of errors *@
@* - i.e. this helper, like <select/> helper, has ContentBehavior.Append *@
@* helper does nothing if model is valid and (client-side validation is disabled or asp-validation-summary="ModelOnly") *@
@* helper does nothing if model is valid and (client-side validation is disabled or
asp-validation-summary="ValidationSummary.ModelOnly") *@
@* don't need a bound attribute to match Html.ValidationSummary()'s headerTag parameter; users wrap message as they wish *@
@* initially at least, will not remove the <div/> if list isn't generated *@
@* - should helper remove the <div/> if list isn't generated? *@
@* - (Html.ValidationSummary returns empty string despite non-empty message parameter) *@
@* Acceptable values are: "None", "ModelOnly" and "All" *@
<div asp-validation-summary="All" style="color:blue" id="validation_day" class="form-group">
<div asp-validation-summary="ValidationSummary.All" style="color:blue" id="validation_day" class="form-group">
<span style="color:red">This is my message</span>
</div>

View File

@ -6,7 +6,7 @@
<form>
<div class="form-horizontal">
<div asp-validation-summary="All" />
<div asp-validation-summary="ValidationSummary.All" />
<input type="hidden" asp-for="Id" />
<div class="form-group">

View File

@ -154,6 +154,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
return string.Format(CultureInfo.CurrentCulture, GetString("TagHelpers_NoProvidedMetadata"), p0, p1, p2, p3);
}
/// <summary>
/// The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.
/// </summary>
internal static string InvalidEnumArgument
{
get { return GetString("InvalidEnumArgument"); }
}
/// <summary>
/// The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.
/// </summary>
internal static string FormatInvalidEnumArgument(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("InvalidEnumArgument"), p0, p1, p2);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -144,4 +144,7 @@
<data name="TagHelpers_NoProvidedMetadata" xml:space="preserve">
<value>The {2} was unable to provide metadata about '{1}' expression value '{3}' for {0}.</value>
</data>
<data name="InvalidEnumArgument" xml:space="preserve">
<value>The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.</value>
</data>
</root>

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc.TagHelpers
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Acceptable validation summary rendering modes.

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public class ValidationSummaryTagHelper : TagHelper
{
private const string ValidationSummaryAttributeName = "asp-validation-summary";
private ValidationSummary _validationSummary;
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate]
@ -27,51 +28,60 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
protected internal IHtmlGenerator Generator { get; set; }
/// <summary>
/// If <c>All</c> or <c>ModelOnly</c>, appends a validation summary. Acceptable values are defined by the
/// <see cref="ValidationSummary"/> enum.
/// If <see cref="ValidationSummary.All"/> or <see cref="ValidationSummary.ModelOnly"/>, appends a validation
/// summary. Otherwise (<see cref="ValidationSummary.None"/>, the default), this tag helper does nothing.
/// </summary>
/// <exception cref="ArgumentException">
/// Thrown if setter is called with an undefined <see cref="ValidationSummary"/> value e.g.
/// <c>(ValidationSummary)23</c>.
/// </exception>
[HtmlAttributeName(ValidationSummaryAttributeName)]
public string ValidationSummaryValue { get; set; }
public ValidationSummary ValidationSummary
{
get
{
return _validationSummary;
}
set
{
switch (value)
{
case ValidationSummary.All:
case ValidationSummary.ModelOnly:
case ValidationSummary.None:
_validationSummary = value;
break;
default:
throw new ArgumentException(
message: Resources.FormatInvalidEnumArgument(
nameof(value),
value,
typeof(ValidationSummary).FullName),
paramName: nameof(value));
}
}
}
/// <inheritdoc />
/// <remarks>Does nothing if <see cref="ValidationSummaryValue"/> is <c>null</c>, empty or "None".</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="ValidationSummaryValue"/> is not a valid <see cref="ValidationSummary"/> value.
/// </exception>
/// <remarks>Does nothing if <see cref="ValidationSummary"/> is <see cref="ValidationSummary.None"/>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (!string.IsNullOrEmpty(ValidationSummaryValue))
if (ValidationSummary == ValidationSummary.None)
{
ValidationSummary validationSummaryValue;
if (!Enum.TryParse(ValidationSummaryValue, ignoreCase: true, result: out validationSummaryValue))
{
throw new InvalidOperationException(
Resources.FormatTagHelpers_InvalidValue_ThreeAcceptableValues(
"<div>",
ValidationSummaryAttributeName,
ValidationSummaryValue,
ValidationSummary.All,
ValidationSummary.ModelOnly,
ValidationSummary.None));
}
else if (validationSummaryValue == ValidationSummary.None)
{
return;
}
return;
}
var validationModelErrorsOnly = validationSummaryValue == ValidationSummary.ModelOnly;
var tagBuilder = Generator.GenerateValidationSummary(
ViewContext,
excludePropertyErrors: validationModelErrorsOnly,
message: null,
headerTag: null,
htmlAttributes: null);
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
}
var tagBuilder = Generator.GenerateValidationSummary(
ViewContext,
excludePropertyErrors: ValidationSummary == ValidationSummary.ModelOnly,
message: null,
headerTag: null,
htmlAttributes: null);
if (tagBuilder != null)
{
output.MergeAttributes(tagBuilder);
output.Content += tagBuilder.InnerHtml;
}
}
}

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing;
using Moq;
using Xunit;
@ -17,6 +18,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
public class ValidationSummaryTagHelperTest
{
public void Concstructor_IntializesProperties_AsExpected()
{
// Arrange & Act
var validationSummaryTagHelper = new ValidationSummaryTagHelper();
// Assert
Assert.Null(validationSummaryTagHelper.Generator);
Assert.Null(validationSummaryTagHelper.ViewContext);
Assert.Equal(ValidationSummary.None, validationSummaryTagHelper.ValidationSummary);
}
[Fact]
public async Task ProcessAsync_GeneratesExpectedOutput()
{
@ -25,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
ValidationSummaryValue = "All"
ValidationSummary = ValidationSummary.All,
};
var tagHelperContext = new TagHelperContext(new Dictionary<string, object>(), uniqueId: "test");
@ -57,13 +69,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal(expectedTagName, output.TagName);
}
[Fact]
public async Task ProcessAsync_CallsIntoGenerateValidationSummaryWithExpectedParameters()
[Theory]
[InlineData(ValidationSummary.All, false)]
[InlineData(ValidationSummary.ModelOnly, true)]
public async Task ProcessAsync_CallsIntoGenerateValidationSummaryWithExpectedParameters(
ValidationSummary validationSummary,
bool expectedExcludePropertyErrors)
{
// Arrange
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
ValidationSummaryValue = "ModelOnly",
ValidationSummary = validationSummary,
};
var output = new TagHelperOutput(
"div",
@ -72,7 +88,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var expectedViewContext = CreateViewContext();
var generator = new Mock<IHtmlGenerator>();
generator
.Setup(mock => mock.GenerateValidationSummary(expectedViewContext, true, null, null, null))
.Setup(mock => mock.GenerateValidationSummary(
expectedViewContext,
expectedExcludePropertyErrors,
null, // message
null, // headerTag
null)) // htmlAttributes
.Returns(new TagBuilder("div"))
.Verifiable();
validationSummaryTagHelper.ViewContext = expectedViewContext;
@ -93,7 +114,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
ValidationSummaryValue = "ModelOnly"
ValidationSummary = ValidationSummary.ModelOnly,
};
var output = new TagHelperOutput(
"div",
@ -136,15 +157,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal("Content of validation summaryNew HTML", output.Content);
}
[Theory]
[InlineData("")]
[InlineData(null)]
public async Task ProcessAsync_DoesNothingIfNullOrEmptyValidationSummaryValue(string validationSummaryValue)
[Fact]
public async Task ProcessAsync_DoesNothingIfValidationSummaryNone()
{
// Arrange
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
ValidationSummaryValue = validationSummaryValue
ValidationSummary = ValidationSummary.None,
};
var output = new TagHelperOutput(
"div",
@ -166,16 +185,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
[Theory]
[InlineData("All")]
[InlineData("all")]
[InlineData("ModelOnly")]
[InlineData("modelonly")]
public async Task ProcessAsync_GeneratesValidationSummaryWhenNotNone_IgnoresCase(string validationSummary)
[InlineData(ValidationSummary.All)]
[InlineData(ValidationSummary.ModelOnly)]
public async Task ProcessAsync_GeneratesValidationSummaryWhenNotNone(ValidationSummary validationSummary)
{
// Arrange
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
ValidationSummaryValue = validationSummary
ValidationSummary = validationSummary,
};
var output = new TagHelperOutput(
"div",
@ -211,56 +228,24 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}
[Theory]
[InlineData("None")]
[InlineData("none")]
public async Task ProcessAsync_DoesNotGenerateValidationSummaryWhenNone_IgnoresCase(string validationSummary)
[InlineData((ValidationSummary)(-1))]
[InlineData((ValidationSummary)23)]
[InlineData(ValidationSummary.All | ValidationSummary.ModelOnly)]
[ReplaceCulture]
public void ValidationSummaryProperty_ThrowsWhenSetToInvalidValidationSummaryValue(
ValidationSummary validationSummary)
{
// Arrange
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
ValidationSummaryValue = validationSummary
};
var output = new TagHelperOutput(
"div",
attributes: new Dictionary<string, string>(),
content: "Content of validation message");
var tagBuilder = new TagBuilder("span2")
{
InnerHtml = "New HTML"
};
var validationSummaryTagHelper = new ValidationSummaryTagHelper();
var expectedMessage = string.Format(
@"The value of argument 'value' ({0}) is invalid for Enum type 'Microsoft.AspNet.Mvc.ValidationSummary'.
Parameter name: value",
validationSummary);
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
// Act
await validationSummaryTagHelper.ProcessAsync(context: null, output: output);
// Assert
Assert.Equal("div", output.TagName);
Assert.Empty(output.Attributes);
Assert.Equal("Content of validation message", output.Content);
}
[Fact]
public async Task ProcessAsync_ThrowsWhenInvalidValidationSummaryValue()
{
// Arrange
var validationSummaryTagHelper = new ValidationSummaryTagHelper
{
ValidationSummaryValue = "Hello World"
};
var output = new TagHelperOutput(
"div",
attributes: new Dictionary<string, string>(),
content: "Content of validation message");
var expectedViewContext = CreateViewContext();
var expectedMessage = "Cannot parse 'asp-validation-summary' value 'Hello World' for <div>. Acceptable " +
"values are 'All', 'ModelOnly' and 'None'.";
// Act
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => validationSummaryTagHelper.ProcessAsync(context: null, output: output));
// Assert
// Act & Assert
var ex = Assert.Throws<ArgumentException>(
"value",
() => { validationSummaryTagHelper.ValidationSummary = validationSummary; });
Assert.Equal(expectedMessage, ex.Message);
}

View File

@ -20,7 +20,7 @@
<select asp-for="Product" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" />
<span asp-validation-for="Product" />
</div>
<div asp-validation-summary="All" class="warehouse" />
<div asp-validation-summary="ValidationSummary.All" class="warehouse" />
<input type="submit" />
}
</body>

View File

@ -75,7 +75,7 @@
@Html.EditorFor(model => model.Customer.Gender)
<span asp-validation-for="Customer.Gender" />
</div>
<div asp-validation-summary="All" class="order" />
<div asp-validation-summary="ValidationSummary.All" class="order" />
<input type="hidden" asp-for="Customer.Key" />
<input type="submit" />
</form>

View File

@ -38,8 +38,8 @@
<input asp-for="Gender" type="radio" value="Female" /> Female
<span asp-validation-for="Gender" />
</div>
<div asp-validation-summary="All" class="order" />
<div asp-validation-summary="ModelOnly" class="order" />
<div asp-validation-summary="ValidationSummary.All" class="order" />
<div asp-validation-summary="ValidationSummary.ModelOnly" class="order" />
<input type="submit" />
</form>
</body>

View File

@ -22,7 +22,7 @@
<div class="form-horizontal">
<h4>Employee</h4>
<hr />
<div asp-validation-summary="All" class="text-danger">
<div asp-validation-summary="ValidationSummary.All" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Age" class="control-label col-md-2" />