Fix for #1722 - FromHeader does not respect default value
This change adds support for our three-valued logic to the default value handling part of the MutableObjectModelBinder. The issue is that we want to look up a default value when a 'greedy' model binder returns true but doesn't find a value. We also don't want to call the property setter unless there is: 1). A value from model binding OR 2). A default value
This commit is contained in:
parent
692a07240c
commit
51e7812e7e
|
|
@ -88,7 +88,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
var parameterType = parameter.ModelType;
|
||||
var modelBindingContext = GetModelBindingContext(parameter, actionContext, operationBindingContext);
|
||||
if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext))
|
||||
if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext) &&
|
||||
modelBindingContext.IsModelSet)
|
||||
{
|
||||
arguments[parameter.PropertyName] = modelBindingContext.Model;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
propertyMetadata);
|
||||
|
||||
// bind and propagate the values
|
||||
// If we can't bind, then leave the result missing (don't add a null).
|
||||
// If we can't bind then leave the result missing (don't add a null).
|
||||
if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
|
||||
{
|
||||
var result = new ComplexModelDtoResult(propertyBindingContext.Model,
|
||||
propertyBindingContext.ValidationNode);
|
||||
var result = ComplexModelDtoResult.FromBindingContext(propertyBindingContext);
|
||||
dto.Results[propertyMetadata] = result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
public sealed class ComplexModelDtoResult
|
||||
{
|
||||
public ComplexModelDtoResult(object model,
|
||||
[NotNull] ModelValidationNode validationNode)
|
||||
public static ComplexModelDtoResult FromBindingContext([NotNull] ModelBindingContext context)
|
||||
{
|
||||
return new ComplexModelDtoResult(context.Model, context.IsModelSet, context.ValidationNode);
|
||||
}
|
||||
|
||||
public ComplexModelDtoResult(
|
||||
object model,
|
||||
bool isModelBound,
|
||||
[NotNull] ModelValidationNode validationNode)
|
||||
{
|
||||
Model = model;
|
||||
IsModelBound = isModelBound;
|
||||
ValidationNode = validationNode;
|
||||
}
|
||||
|
||||
public object Model { get; private set; }
|
||||
public bool IsModelBound { get; }
|
||||
|
||||
public ModelValidationNode ValidationNode { get; private set; }
|
||||
public object Model { get; set; }
|
||||
|
||||
public ModelValidationNode ValidationNode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,10 +291,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
};
|
||||
}
|
||||
|
||||
private static object GetPropertyDefaultValue(PropertyInfo propertyInfo)
|
||||
private static bool TryGetPropertyDefaultValue(PropertyInfo propertyInfo, out object value)
|
||||
{
|
||||
var attr = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();
|
||||
return (attr != null) ? attr.Value : null;
|
||||
var attribute = propertyInfo.GetCustomAttribute<DefaultValueAttribute>();
|
||||
if (attribute == null)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = attribute.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PropertyValidationInfo GetPropertyValidationInfo(ModelBindingContext bindingContext)
|
||||
|
|
@ -345,7 +354,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||
|
||||
// Eliminate provided properties from requiredProperties; leaving just *missing* required properties.
|
||||
validationInfo.RequiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName));
|
||||
var boundProperties = dto.Results.Where(p => p.Value.IsModelBound).Select(p => p.Key.PropertyName);
|
||||
validationInfo.RequiredProperties.ExceptWith(boundProperties);
|
||||
|
||||
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
|
||||
{
|
||||
|
|
@ -407,7 +417,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return;
|
||||
}
|
||||
|
||||
var value = dtoResult.Model ?? GetPropertyDefaultValue(property);
|
||||
object value;
|
||||
var hasDefaultValue = false;
|
||||
if (dtoResult.IsModelBound)
|
||||
{
|
||||
value = dtoResult.Model;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasDefaultValue = TryGetPropertyDefaultValue(property, out value);
|
||||
}
|
||||
|
||||
propertyMetadata.Model = value;
|
||||
|
||||
// 'Required' validators need to run first so that we can provide useful error messages if
|
||||
|
|
@ -429,6 +449,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
if (!dtoResult.IsModelBound && !hasDefaultValue)
|
||||
{
|
||||
// If we don't have a value, don't set it on the model and trounce a pre-initialized
|
||||
// value.
|
||||
return;
|
||||
}
|
||||
|
||||
if (value != null || property.PropertyType.AllowsNullValue())
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -183,6 +183,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
var binder = new Mock<IModelBinder>();
|
||||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Callback<ModelBindingContext>(c =>
|
||||
{
|
||||
// This value won't go into the arguments, because we return false.
|
||||
c.Model = "Hello";
|
||||
})
|
||||
.Returns(Task.FromResult(result: false));
|
||||
|
||||
var actionContext = new ActionContext(
|
||||
|
|
@ -211,6 +216,59 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderDoesNotSetModel()
|
||||
{
|
||||
// Arrange
|
||||
Func<object, int> method = foo => 1;
|
||||
var actionDescriptor = new ControllerActionDescriptor
|
||||
{
|
||||
MethodInfo = method.Method,
|
||||
Parameters = new List<ParameterDescriptor>
|
||||
{
|
||||
new ParameterDescriptor
|
||||
{
|
||||
Name = "foo",
|
||||
ParameterType = typeof(object),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder
|
||||
.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Callback<ModelBindingContext>(c =>
|
||||
{
|
||||
Assert.False(c.IsModelSet);
|
||||
})
|
||||
.Returns(Task.FromResult(result: true));
|
||||
|
||||
var actionContext = new ActionContext(
|
||||
new RouteContext(Mock.Of<HttpContext>()),
|
||||
actionDescriptor)
|
||||
{
|
||||
Controller = Mock.Of<object>(),
|
||||
};
|
||||
|
||||
var actionBindingContext = new ActionBindingContext()
|
||||
{
|
||||
ModelBinder = binder.Object,
|
||||
};
|
||||
|
||||
var inputFormattersProvider = new Mock<IInputFormattersProvider>();
|
||||
inputFormattersProvider
|
||||
.SetupGet(o => o.InputFormatters)
|
||||
.Returns(new List<IInputFormatter>());
|
||||
|
||||
var invoker = new DefaultControllerActionArgumentBinder(new DataAnnotationsModelMetadataProvider());
|
||||
|
||||
// Act
|
||||
var result = await invoker.GetActionArgumentsAsync(actionContext, actionBindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetActionArgumentsAsync_AddsActionArguments_IfBinderReturnsTrue()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,5 +20,5 @@
|
|||
</div>
|
||||
|
||||
<footer>
|
||||
Tracked by
|
||||
Tracked by default-tracking-id
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,36 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Empty(result.ModelStateErrors);
|
||||
}
|
||||
|
||||
// There should be no model state error for a top-level object
|
||||
[Theory]
|
||||
[InlineData("transactionId1234", "1e331f25-0869-4c87-8a94-64e6e40cb5a0")]
|
||||
public async Task FromHeader_BindHeader_ToString_OnParameter_NoValues_DefaultValue(
|
||||
string headerName,
|
||||
string headerValue)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
"http://localhost/Blog/BindToStringParameterDefaultValue");
|
||||
// Intentionally not setting a header value
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||
|
||||
Assert.Equal("default-value", result.HeaderValue);
|
||||
Assert.Null(result.HeaderValues);
|
||||
Assert.Empty(result.ModelStateErrors);
|
||||
}
|
||||
|
||||
// The action that this test hits will echo back the model-bound values
|
||||
[Theory]
|
||||
[InlineData("transactionIds", "1e331f25-0869-4c87-8a94-64e6e40cb5a0")]
|
||||
|
|
@ -187,6 +217,70 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal("Title", error);
|
||||
}
|
||||
|
||||
// This model sets a value for 'Title', and the model binder won't trounce it.
|
||||
//
|
||||
// There's no validation error because we validate the initialized value.
|
||||
[Fact]
|
||||
public async Task FromHeader_BindHeader_ToModel_NoValues_InitializedValue_ValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
"http://localhost/Blog/BindToModelWithInitializedValue?author=Marvin");
|
||||
|
||||
// Intentionally not setting a title or tags
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||
|
||||
Assert.Equal("How to Make Soup", result.HeaderValue);
|
||||
Assert.Equal<string>(new[] { "Cooking" }, result.HeaderValues);
|
||||
|
||||
var error = Assert.Single(result.ModelStateErrors);
|
||||
Assert.Equal("Title", error);
|
||||
}
|
||||
|
||||
// This model uses default value for 'Title'.
|
||||
//
|
||||
// There's no validation error because we validate the default value.
|
||||
[Fact]
|
||||
public async Task FromHeader_BindHeader_ToModel_NoValues_DefaultValue_NoValidationError()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
"http://localhost/Blog/BindToModelWithDefaultValue?author=Marvin");
|
||||
|
||||
// Intentionally not setting a title or tags
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<Result>(body);
|
||||
|
||||
Assert.Equal("How to Make Soup", result.HeaderValue);
|
||||
Assert.Equal<string>(new[] { "Cooking" }, result.HeaderValues);
|
||||
|
||||
var error = Assert.Single(result.ModelStateErrors);
|
||||
Assert.Equal("Title", error);
|
||||
}
|
||||
|
||||
private class Result
|
||||
{
|
||||
public string HeaderValue { get; set; }
|
||||
|
|
|
|||
|
|
@ -14,10 +14,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
|||
var validationNode = GetValidationNode();
|
||||
|
||||
// Act
|
||||
var result = new ComplexModelDtoResult("some string", validationNode);
|
||||
var result = new ComplexModelDtoResult(
|
||||
"some string",
|
||||
isModelBound: true,
|
||||
validationNode: validationNode);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("some string", result.Model);
|
||||
Assert.True(result.IsModelBound);
|
||||
Assert.Equal(validationNode, result.ValidationNode);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -583,7 +583,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
"ValueTypeRequired",
|
||||
"FirstName",
|
||||
"LastName",
|
||||
"PropertyWithDefaultValue"
|
||||
"PropertyWithDefaultValue",
|
||||
"PropertyWithInitializedValue",
|
||||
"PropertyWithInitializedValueAndDefault",
|
||||
};
|
||||
var bindingContext = new ModelBindingContext
|
||||
{
|
||||
|
|
@ -751,7 +753,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||
|
||||
var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
||||
dto.Results[nameProperty] = new ComplexModelDtoResult("John Doe", new ModelValidationNode(nameProperty, ""));
|
||||
dto.Results[nameProperty] = new ComplexModelDtoResult(
|
||||
"John Doe",
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(nameProperty, ""));
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
|
|
@ -805,14 +810,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
var propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
|
||||
dto.Results[propertyMetadata] =
|
||||
new ComplexModelDtoResult("John Doe", new ModelValidationNode(propertyMetadata, "theModel.Name"));
|
||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
||||
"John Doe",
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.Name"));
|
||||
|
||||
// Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this
|
||||
// case because the binding exists.
|
||||
propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Age");
|
||||
dto.Results[propertyMetadata] =
|
||||
new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
||||
null,
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
||||
|
||||
// Act; must also Validate because null-check error handler is late-bound
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
|
|
@ -894,11 +903,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
// Make Age valid and City invalid.
|
||||
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "Age");
|
||||
dto.Results[propertyMetadata] =
|
||||
new ComplexModelDtoResult(23, new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
||||
23,
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.Age"));
|
||||
|
||||
propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "City");
|
||||
dto.Results[propertyMetadata] =
|
||||
new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.City"));
|
||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
||||
null,
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.City"));
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
|
|
@ -963,8 +977,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
// Make ValueTypeRequired invalid.
|
||||
var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "ValueTypeRequired");
|
||||
dto.Results[propertyMetadata] =
|
||||
new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.ValueTypeRequired"));
|
||||
dto.Results[propertyMetadata] = new ComplexModelDtoResult(
|
||||
null,
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(propertyMetadata, "theModel.ValueTypeRequired"));
|
||||
|
||||
// Act
|
||||
testableBinder.ProcessDto(bindingContext, dto);
|
||||
|
|
@ -999,9 +1015,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||
|
||||
var firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
|
||||
dto.Results[firstNameProperty] = new ComplexModelDtoResult("John", new ModelValidationNode(firstNameProperty, ""));
|
||||
dto.Results[firstNameProperty] = new ComplexModelDtoResult(
|
||||
"John",
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(firstNameProperty, ""));
|
||||
|
||||
var lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
|
||||
dto.Results[lastNameProperty] = new ComplexModelDtoResult("Doe", new ModelValidationNode(lastNameProperty, ""));
|
||||
dto.Results[lastNameProperty] = new ComplexModelDtoResult(
|
||||
"Doe",
|
||||
isModelBound: true,
|
||||
validationNode: new ModelValidationNode(lastNameProperty, ""));
|
||||
|
||||
var dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
|
||||
dto.Results[dobProperty] = null;
|
||||
|
||||
|
|
@ -1025,7 +1049,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.First(o => o.PropertyName == "PropertyWithDefaultValue");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
model: null,
|
||||
isModelBound: false,
|
||||
validationNode: validationNode);
|
||||
|
||||
var requiredValidator = bindingContext.OperationBindingContext
|
||||
.ValidatorProvider
|
||||
.GetValidators(propertyMetadata)
|
||||
|
|
@ -1034,7 +1063,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
|
||||
// Assert
|
||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||
|
|
@ -1042,6 +1071,60 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Assert.True(bindingContext.ModelState.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_PropertyIsPreinitialized_NoValue_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(
|
||||
o => o.PropertyName == "PropertyWithInitializedValue");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||
|
||||
// This value won't be used because IsModelBound = false.
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
model: "bad-value",
|
||||
isModelBound: false,
|
||||
validationNode: validationNode);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||
|
||||
// Assert
|
||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||
Assert.Equal("preinitialized", person.PropertyWithInitializedValue);
|
||||
Assert.True(bindingContext.ModelState.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_PropertyIsPreinitialized_WithDefaultValue_NoValue_CallsSetter()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(
|
||||
o => o.PropertyName == "PropertyWithInitializedValueAndDefault");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||
|
||||
// This value won't be used because IsModelBound = false.
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
model: "bad-value",
|
||||
isModelBound: false,
|
||||
validationNode: validationNode);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||
|
||||
// Assert
|
||||
var person = Assert.IsType<Person>(bindingContext.Model);
|
||||
Assert.Equal("default", person.PropertyWithInitializedValueAndDefault);
|
||||
Assert.True(bindingContext.ModelState.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_PropertyIsReadOnly_DoesNothing()
|
||||
{
|
||||
|
|
@ -1049,12 +1132,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
model: null,
|
||||
isModelBound: false,
|
||||
validationNode: validationNode);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||
|
||||
// Assert
|
||||
// If didn't throw, success!
|
||||
|
|
@ -1069,7 +1156,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||
var dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
|
||||
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
new DateTime(2001, 1, 1),
|
||||
isModelBound: true,
|
||||
validationNode: validationNode);
|
||||
|
||||
var requiredValidator = bindingContext.OperationBindingContext
|
||||
.ValidatorProvider
|
||||
.GetValidators(propertyMetadata)
|
||||
|
|
@ -1079,7 +1171,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
|
||||
// Assert
|
||||
validationNode.Validate(validationContext);
|
||||
|
|
@ -1100,12 +1192,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||
var dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode);
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
new DateTime(1800, 1, 1),
|
||||
isModelBound: true,
|
||||
validationNode: validationNode);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Date of death can't be before date of birth." + Environment.NewLine
|
||||
|
|
@ -1118,16 +1213,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
// Arrange
|
||||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
model: null,
|
||||
isModelBound: true,
|
||||
validationNode: validationNode);
|
||||
|
||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
|
||||
// Assert
|
||||
Assert.True(bindingContext.ModelState.IsValid);
|
||||
|
|
@ -1141,15 +1241,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Arrange
|
||||
var bindingContext = CreateContext(GetMetadataForObject(new Person()));
|
||||
bindingContext.ModelName = " foo";
|
||||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired");
|
||||
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
model: null,
|
||||
isModelBound: true,
|
||||
validationNode: validationNode);
|
||||
|
||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.ModelState.IsValid);
|
||||
|
|
@ -1163,15 +1268,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Arrange
|
||||
var bindingContext = CreateContext(GetMetadataForObject(new ModelWhosePropertySetterThrows()));
|
||||
bindingContext.ModelName = "foo";
|
||||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute");
|
||||
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||
var dtoResult = new ComplexModelDtoResult(
|
||||
model: null,
|
||||
isModelBound: true,
|
||||
validationNode: validationNode);
|
||||
|
||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.ModelState.IsValid);
|
||||
|
|
@ -1187,15 +1297,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
// Arrange
|
||||
var bindingContext = CreateContext(GetMetadataForObject(new ModelWhosePropertySetterThrows()));
|
||||
bindingContext.ModelName = "foo";
|
||||
|
||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
|
||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo.Name");
|
||||
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||
var dtoResult = new ComplexModelDtoResult(model: null,
|
||||
isModelBound: true,
|
||||
validationNode: validationNode);
|
||||
|
||||
var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata);
|
||||
|
||||
var testableBinder = new TestableMutableObjectModelBinder();
|
||||
|
||||
// Act
|
||||
testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
|
||||
// Assert
|
||||
Assert.False(bindingContext.ModelState.IsValid);
|
||||
|
|
@ -1290,6 +1404,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
[DefaultValue(typeof(decimal), "123.456")]
|
||||
public decimal PropertyWithDefaultValue { get; set; }
|
||||
|
||||
public string PropertyWithInitializedValue { get; set; } = "preinitialized";
|
||||
|
||||
[DefaultValue("default")]
|
||||
public string PropertyWithInitializedValueAndDefault { get; set; } = "preinitialized";
|
||||
}
|
||||
|
||||
private class PersonWithBindExclusion
|
||||
|
|
@ -1497,20 +1616,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return base.GetMetadataForProperties(bindingContext);
|
||||
}
|
||||
|
||||
public virtual void SetPropertyPublic(ModelBindingContext bindingContext,
|
||||
ModelMetadata propertyMetadata,
|
||||
ComplexModelDtoResult dtoResult,
|
||||
IModelValidator requiredValidator)
|
||||
{
|
||||
base.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
}
|
||||
|
||||
protected override void SetProperty(ModelBindingContext bindingContext,
|
||||
public new void SetProperty(ModelBindingContext bindingContext,
|
||||
ModelMetadata propertyMetadata,
|
||||
ComplexModelDtoResult dtoResult,
|
||||
IModelValidator requiredValidator)
|
||||
{
|
||||
SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
base.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,19 @@ namespace FiltersWebSite
|
|||
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
if (context.ActionArguments["fromGlobalActionFilter"] == null)
|
||||
object obj;
|
||||
List<ContentResult> filters;
|
||||
|
||||
if (context.ActionArguments.TryGetValue("fromGlobalActionFilter", out obj))
|
||||
{
|
||||
context.ActionArguments["fromGlobalActionFilter"] = new List<ContentResult>();
|
||||
filters = (List<ContentResult>)obj;
|
||||
}
|
||||
(context.ActionArguments["fromGlobalActionFilter"] as List<ContentResult>)
|
||||
.Add(Helpers.GetContentResult(context.Result, "Controller override - OnActionExecuting"));
|
||||
{
|
||||
filters = new List<ContentResult>();
|
||||
context.ActionArguments.Add("fromGlobalActionFilter", filters);
|
||||
}
|
||||
|
||||
filters.Add(Helpers.GetContentResult(context.Result, "Controller override - OnActionExecuting"));
|
||||
}
|
||||
|
||||
public override void OnActionExecuted(ActionExecutedContext context)
|
||||
|
|
|
|||
|
|
@ -1,6 +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.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
|
@ -21,6 +22,17 @@ namespace ModelBindingWebSite.Controllers
|
|||
};
|
||||
}
|
||||
|
||||
// Echo back the header value
|
||||
[HttpGet("BindToStringParameterDefaultValue")]
|
||||
public object BindToStringParameterDefaultValue([FromHeader] string transactionId = "default-value")
|
||||
{
|
||||
return new Result()
|
||||
{
|
||||
HeaderValue = transactionId,
|
||||
ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
// Echo back the header values
|
||||
[HttpGet("BindToStringArrayParameter")]
|
||||
public object BindToStringArrayParameter([FromHeader] string[] transactionIds)
|
||||
|
|
@ -53,6 +65,29 @@ namespace ModelBindingWebSite.Controllers
|
|||
};
|
||||
}
|
||||
|
||||
[HttpGet("BindToModelWithInitializedValue")]
|
||||
public object BindToModelWithInitializedValue(BlogPostWithInitializedValue blogPost)
|
||||
{
|
||||
return new Result()
|
||||
{
|
||||
HeaderValue = blogPost.Title,
|
||||
HeaderValues = blogPost.Tags,
|
||||
ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("BindToModelWithDefaultValue")]
|
||||
public object BindToModelWithDefaultValue(BlogPostWithDefaultValue blogPost)
|
||||
{
|
||||
return new Result()
|
||||
{
|
||||
HeaderValue = blogPost.Title,
|
||||
HeaderValues = blogPost.Tags,
|
||||
ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private class Result
|
||||
{
|
||||
public string HeaderValue { get; set; }
|
||||
|
|
@ -73,5 +108,31 @@ namespace ModelBindingWebSite.Controllers
|
|||
|
||||
public string Author { get; set; }
|
||||
}
|
||||
|
||||
public class BlogPostWithInitializedValue
|
||||
{
|
||||
[Required]
|
||||
[FromHeader]
|
||||
public string Title { get; set; } = "How to Make Soup";
|
||||
|
||||
[FromHeader]
|
||||
public string[] Tags { get; set; } = new string[] { "Cooking" };
|
||||
|
||||
public string Author { get; set; }
|
||||
}
|
||||
|
||||
public class BlogPostWithDefaultValue
|
||||
{
|
||||
[Required]
|
||||
[FromHeader]
|
||||
[DefaultValue("How to Make Soup")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[FromHeader]
|
||||
[DefaultValue(new string[] { "Cooking" })]
|
||||
public string[] Tags { get; set; }
|
||||
|
||||
public string Author { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue