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:
Ryan Nowak 2015-01-15 18:02:41 -08:00
parent 692a07240c
commit 51e7812e7e
11 changed files with 431 additions and 59 deletions

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

@ -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

View File

@ -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()
{

View File

@ -20,5 +20,5 @@
</div>
<footer>
Tracked by
Tracked by default-tracking-id
</footer>

View File

@ -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; }

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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; }
}
}
}