Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Add null check for ModelStateEntry.Children
Browse files Browse the repository at this point in the history
  • Loading branch information
dougbu committed Nov 3, 2016
1 parent 6a9a753 commit f8f9bbc
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
Expand Down Expand Up @@ -90,7 +89,7 @@ private static void Visit(
ModelMetadata metadata,
List<ModelStateEntry> orderedModelStateEntries)
{
if (metadata.ElementMetadata != null)
if (metadata.ElementMetadata != null && modelStateEntry.Children != null)
{
foreach (var indexEntry in modelStateEntry.Children)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,21 @@ public static ViewContext GetViewContext(
IHtmlGenerator htmlGenerator,
IModelMetadataProvider metadataProvider)
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary(metadataProvider)
return GetViewContext(model, htmlGenerator, metadataProvider, modelState: new ModelStateDictionary());
}

public static ViewContext GetViewContext(
object model,
IHtmlGenerator htmlGenerator,
IModelMetadataProvider metadataProvider,
ModelStateDictionary modelState)
{
var actionContext = new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new ActionDescriptor(),
modelState);
var viewData = new ViewDataDictionary(metadataProvider, modelState)
{
Model = model,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,149 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
{
public class ValidationSummaryTagHelperTest
{
public static TheoryData<ModelStateDictionary> ProcessAsync_GeneratesExpectedOutput_WithNoErrorsData
{
get
{
var emptyModelState = new ModelStateDictionary();

var modelState = new ModelStateDictionary();
SetValidModelState(modelState);

return new TheoryData<ModelStateDictionary>
{
emptyModelState,
modelState,
};
}
}

[Theory]
[MemberData(nameof(ProcessAsync_GeneratesExpectedOutput_WithNoErrorsData))]
public async Task ProcessAsync_GeneratesExpectedOutput_WithNoErrors(
ModelStateDictionary modelState)
{
// Arrange
var expectedTagName = "not-div";
var metadataProvider = new TestModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);

var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var tagHelperContext = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
attributes: new TagHelperAttributeList
{
{ "class", "form-control" }
},
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent("Custom Content");

var model = new Model();
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider, modelState);
var validationSummaryTagHelper = new ValidationSummaryTagHelper(htmlGenerator)
{
ValidationSummary = ValidationSummary.All,
ViewContext = viewContext,
};

// Act
await validationSummaryTagHelper.ProcessAsync(tagHelperContext, output);

// Assert
Assert.Equal(2, output.Attributes.Count);
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("class"));
Assert.Equal("form-control validation-summary-valid", attribute.Value);
attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("data-valmsg-summary"));
Assert.Equal("true", attribute.Value);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(
$"Custom Content<ul><li style=\"display:none\"></li>{Environment.NewLine}</ul>",
output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}

[Theory]
[InlineData(ValidationSummary.All)]
[InlineData(ValidationSummary.ModelOnly)]
public async Task ProcessAsync_GeneratesExpectedOutput_WithModelError(ValidationSummary validationSummary)
{
// Arrange
var expectedError = "I am an error.";
var expectedTagName = "not-div";
var metadataProvider = new TestModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);

var validationSummaryTagHelper = new ValidationSummaryTagHelper(htmlGenerator)
{
ValidationSummary = validationSummary,
};

var expectedPreContent = "original pre-content";
var expectedContent = "original content";
var tagHelperContext = new TagHelperContext(
allAttributes: new TagHelperAttributeList(
Enumerable.Empty<TagHelperAttribute>()),
items: new Dictionary<object, object>(),
uniqueId: "test");
var output = new TagHelperOutput(
expectedTagName,
attributes: new TagHelperAttributeList
{
{ "class", "form-control" }
},
getChildContentAsync: (useCachedResult, encoder) =>
{
var tagHelperContent = new DefaultTagHelperContent();
tagHelperContent.SetContent("Something");
return Task.FromResult<TagHelperContent>(tagHelperContent);
});
output.PreContent.SetContent(expectedPreContent);
output.Content.SetContent(expectedContent);
output.PostContent.SetContent("Custom Content");

var model = new Model();
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
validationSummaryTagHelper.ViewContext = viewContext;

var modelState = viewContext.ModelState;
SetValidModelState(modelState);
modelState.AddModelError(string.Empty, expectedError);

// Act
await validationSummaryTagHelper.ProcessAsync(tagHelperContext, output);

// Assert
Assert.InRange(output.Attributes.Count, low: 1, high: 2);
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("class"));
Assert.Equal("form-control validation-summary-errors", attribute.Value);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal(
$"Custom Content<ul><li>{expectedError}</li>{Environment.NewLine}</ul>",
output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}

[Fact]
public async Task ProcessAsync_GeneratesExpectedOutput()
public async Task ProcessAsync_GeneratesExpectedOutput_WithPropertyErrors()
{
// Arrange
var expectedError0 = "I am an error.";
var expectedError2 = "I am also an error.";
var expectedTagName = "not-div";
var metadataProvider = new TestModelMetadataProvider();
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
Expand Down Expand Up @@ -59,23 +198,30 @@ public async Task ProcessAsync_GeneratesExpectedOutput()
output.Content.SetContent(expectedContent);
output.PostContent.SetContent("Custom Content");

Model model = null;
var model = new Model();
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
validationSummaryTagHelper.ViewContext = viewContext;

var modelState = viewContext.ModelState;
SetValidModelState(modelState);
modelState.AddModelError(key: $"{nameof(Model.Strings)}[0]", errorMessage: expectedError0);
modelState.AddModelError(key: $"{nameof(Model.Strings)}[2]", errorMessage: expectedError2);

// Act
await validationSummaryTagHelper.ProcessAsync(tagHelperContext, output);

// Assert
Assert.Equal(2, output.Attributes.Count);
var attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("class"));
Assert.Equal("form-control validation-summary-valid", attribute.Value);
Assert.Equal("form-control validation-summary-errors", attribute.Value);
attribute = Assert.Single(output.Attributes, attr => attr.Name.Equals("data-valmsg-summary"));
Assert.Equal("true", attribute.Value);
Assert.Equal(expectedPreContent, output.PreContent.GetContent());
Assert.Equal(expectedContent, output.Content.GetContent());
Assert.Equal("Custom Content<ul><li style=\"display:none\"></li>" + Environment.NewLine + "</ul>",
output.PostContent.GetContent());
Assert.Equal(
$"Custom Content<ul><li>{expectedError0}</li>{Environment.NewLine}" +
$"<li>{expectedError2}</li>{Environment.NewLine}</ul>",
output.PostContent.GetContent());
Assert.Equal(expectedTagName, output.TagName);
}

Expand Down Expand Up @@ -339,9 +485,29 @@ private static ViewContext CreateViewContext()
new HtmlHelperOptions());
}

private static void SetValidModelState(ModelStateDictionary modelState)
{
modelState.SetModelValue(key: nameof(Model.Empty), rawValue: null, attemptedValue: null);
modelState.SetModelValue(key: $"{nameof(Model.Strings)}[0]", rawValue: null, attemptedValue: null);
modelState.SetModelValue(key: $"{nameof(Model.Strings)}[1]", rawValue: null, attemptedValue: null);
modelState.SetModelValue(key: $"{nameof(Model.Strings)}[2]", rawValue: null, attemptedValue: null);
modelState.SetModelValue(key: nameof(Model.Text), rawValue: null, attemptedValue: null);

foreach (var key in modelState.Keys)
{
modelState.MarkFieldValid(key);
}
}

private class Model
{
public string Text { get; set; }

public string[] Strings { 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; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,8 @@ 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));
Expand Down Expand Up @@ -712,6 +714,9 @@ private void AddOrderedErrors(ModelStateDictionary modelState)

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
Expand All @@ -738,6 +743,10 @@ private class OrderedModel
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
Expand Down

0 comments on commit f8f9bbc

Please sign in to comment.