diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
index c617f4817f..2dc1125d70 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
+using System.Linq.Expressions;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
@@ -654,26 +655,28 @@ namespace Microsoft.AspNet.Mvc
}
///
- /// Updates the specified model instance using values from the controller's current value provider.
+ /// Updates the specified instance using values from the controller's current
+ /// .
///
/// The type of the model object.
/// The model instance to update.
- /// true if the update is successful; otherwise, false.
+ /// A that on completion returns true if the update is successful
[NonAction]
public virtual Task TryUpdateModelAsync([NotNull] TModel model)
where TModel : class
{
- return TryUpdateModelAsync(model, prefix: typeof(TModel).Name);
+ return TryUpdateModelAsync(model, prefix: null);
}
///
- /// Updates the specified model instance using values from the controller's current value provider
- /// and a prefix.
+ /// Updates the specified instance using values from the controller's current
+ /// and a .
///
/// The type of the model object.
/// The model instance to update.
- /// The prefix to use when looking up values in the value provider.
- /// true if the update is successful; otherwise, false.
+ /// The prefix to use when looking up values in the current
+ ///
+ /// A that on completion returns true if the update is successful
[NonAction]
public virtual async Task TryUpdateModelAsync([NotNull] TModel model,
[NotNull] string prefix)
@@ -681,7 +684,8 @@ namespace Microsoft.AspNet.Mvc
{
if (BindingContextProvider == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull("BindingContextProvider", GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
+ GetType().FullName);
throw new InvalidOperationException(message);
}
@@ -690,13 +694,15 @@ namespace Microsoft.AspNet.Mvc
}
///
- /// Updates the specified model instance using the value provider and a prefix.
+ /// Updates the specified instance using the and a
+ /// .
///
/// The type of the model object.
/// The model instance to update.
- /// The prefix to use when looking up values in the value provider.
- /// The value provider used for looking up values.
- /// true if the update is successful; otherwise, false.
+ /// The prefix to use when looking up values in the .
+ ///
+ /// The used for looking up values.
+ /// A that on completion returns true if the update is successful
[NonAction]
public virtual async Task TryUpdateModelAsync([NotNull] TModel model,
[NotNull] string prefix,
@@ -705,7 +711,8 @@ namespace Microsoft.AspNet.Mvc
{
if (BindingContextProvider == null)
{
- var message = Resources.FormatPropertyOfTypeCannotBeNull("BindingContextProvider", GetType().FullName);
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
+ GetType().FullName);
throw new InvalidOperationException(message);
}
@@ -720,6 +727,156 @@ namespace Microsoft.AspNet.Mvc
bindingContext.ValidatorProvider);
}
+ ///
+ /// Updates the specified instance using values from the controller's current
+ /// and a .
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The prefix to use when looking up values in the current .
+ ///
+ /// (s) which represent top-level properties
+ /// which need to be included for the current model.
+ /// A that on completion returns true if the update is successful
+ [NonAction]
+ public async Task TryUpdateModelAsync(
+ [NotNull] TModel model,
+ string prefix,
+ [NotNull] params Expression>[] includeExpressions)
+ where TModel : class
+ {
+ if (BindingContextProvider == null)
+ {
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
+ GetType().FullName);
+ throw new InvalidOperationException(message);
+ }
+
+ var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
+ return await ModelBindingHelper.TryUpdateModelAsync(model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ bindingContext.MetadataProvider,
+ bindingContext.ModelBinder,
+ bindingContext.ValueProvider,
+ bindingContext.ValidatorProvider,
+ includeExpressions);
+ }
+
+ ///
+ /// Updates the specified instance using values from the controller's current
+ /// and a .
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The prefix to use when looking up values in the current .
+ ///
+ /// A predicate which can be used to filter properties at runtime.
+ /// A that on completion returns true if the update is successful
+ [NonAction]
+ public async Task TryUpdateModelAsync(
+ [NotNull] TModel model,
+ string prefix,
+ [NotNull] Func predicate)
+ where TModel : class
+ {
+ if (BindingContextProvider == null)
+ {
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
+ GetType().FullName);
+ throw new InvalidOperationException(message);
+ }
+
+ var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
+ return await ModelBindingHelper.TryUpdateModelAsync(model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ bindingContext.MetadataProvider,
+ bindingContext.ModelBinder,
+ bindingContext.ValueProvider,
+ bindingContext.ValidatorProvider,
+ predicate);
+ }
+
+ ///
+ /// Updates the specified instance using the and a
+ /// .
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The prefix to use when looking up values in the
+ ///
+ /// The used for looking up values.
+ /// (s) which represent top-level properties
+ /// which need to be included for the current model.
+ /// A that on completion returns true if the update is successful
+ [NonAction]
+ public async Task TryUpdateModelAsync(
+ [NotNull] TModel model,
+ string prefix,
+ [NotNull] IValueProvider valueProvider,
+ [NotNull] params Expression>[] includeExpressions)
+ where TModel : class
+ {
+ if (BindingContextProvider == null)
+ {
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
+ GetType().FullName);
+ throw new InvalidOperationException(message);
+ }
+
+ var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
+ return await ModelBindingHelper.TryUpdateModelAsync(model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ bindingContext.MetadataProvider,
+ bindingContext.ModelBinder,
+ valueProvider,
+ bindingContext.ValidatorProvider,
+ includeExpressions);
+ }
+
+ ///
+ /// Updates the specified instance using the and a
+ /// .
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The prefix to use when looking up values in the
+ ///
+ /// The used for looking up values.
+ /// A predicate which can be used to filter properties at runtime.
+ /// A that on completion returns true if the update is successful
+ [NonAction]
+ public async Task TryUpdateModelAsync(
+ [NotNull] TModel model,
+ string prefix,
+ [NotNull] IValueProvider valueProvider,
+ [NotNull] Func predicate)
+ where TModel : class
+ {
+ if (BindingContextProvider == null)
+ {
+ var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
+ GetType().FullName);
+ throw new InvalidOperationException(message);
+ }
+
+ var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
+ return await ModelBindingHelper.TryUpdateModelAsync(model,
+ prefix,
+ ActionContext.HttpContext,
+ ModelState,
+ bindingContext.MetadataProvider,
+ bindingContext.ModelBinder,
+ valueProvider,
+ bindingContext.ValidatorProvider,
+ predicate);
+ }
+
public void Dispose()
{
Dispose(disposing: true);
@@ -729,6 +886,5 @@ namespace Microsoft.AspNet.Mvc
protected virtual void Dispose(bool disposing)
{
}
-
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
index 111a13e380..9ba8c37656 100644
--- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs
@@ -100,8 +100,8 @@ namespace Microsoft.AspNet.Mvc
ActionBindingContext actionBindingContext,
OperationBindingContext operationBindingContext)
{
- Predicate propertyFilter =
- propertyName => BindAttribute.IsPropertyAllowed(propertyName,
+ Func propertyFilter =
+ (context, propertyName) => BindAttribute.IsPropertyAllowed(propertyName,
modelMetadata.BinderIncludeProperties,
modelMetadata.BinderExcludeProperties);
diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs
index 1846f7dc20..cc26ad8d58 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs
@@ -1,8 +1,13 @@
// 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;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
+using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
@@ -10,22 +15,24 @@ namespace Microsoft.AspNet.Mvc
public static class ModelBindingHelper
{
///
- /// Updates the specified model instance using the specified binder and value provider and
- /// executes validation using the specified sequence of validator providers.
+ /// Updates the specified instance using the specified
+ /// and the specified and executes validation using the specified
+ /// .
///
/// The type of the model object.
- /// The model instance to update.
- /// The prefix to use when looking up values in the value provider.
- /// The context for the current executing request.
- /// The ModelStateDictionary used for maintaining state and
+ /// The model instance to update and validate.
+ /// The prefix to use when looking up values in the .
+ ///
+ /// The for the current executing request.
+ /// The used for maintaining state and
/// results of model-binding validation.
/// The provider used for reading metadata for the model type.
- /// The model binder used for binding.
- /// The value provider used for looking up values.
- /// The validator provider used for executing validation on the model
- /// instance.
- /// A Task with a value representing if the the update is successful.
- public static async Task TryUpdateModelAsync(
+ /// The used for binding.
+ /// The used for looking up values.
+ /// The used for executing validation
+ /// on the model instance.
+ /// A that on completion returns true if the update is successful
+ public static Task TryUpdateModelAsync(
[NotNull] TModel model,
[NotNull] string prefix,
[NotNull] HttpContext httpContext,
@@ -35,6 +42,99 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IValueProvider valueProvider,
[NotNull] IModelValidatorProvider validatorProvider)
where TModel : class
+ {
+ // Includes everything by default.
+ return TryUpdateModelAsync(
+ model,
+ prefix,
+ httpContext,
+ modelState,
+ metadataProvider,
+ modelBinder,
+ valueProvider,
+ validatorProvider,
+ predicate: (context, propertyName) => true);
+ }
+
+ ///
+ /// Updates the specified instance using the specified
+ /// and the specified and executes validation using the specified
+ /// .
+ ///
+ /// The type of the model object.
+ /// The model instance to update and validate.
+ /// The prefix to use when looking up values in the .
+ ///
+ /// The for the current executing request.
+ /// The used for maintaining state and
+ /// results of model-binding validation.
+ /// The provider used for reading metadata for the model type.
+ /// The used for binding.
+ /// The used for looking up values.
+ /// The used for executing validation
+ /// on the model
+ /// instance.
+ /// Expression(s) which represent top level properties
+ /// which need to be included for the current model.
+ /// A that on completion returns true if the update is successful
+ public static Task TryUpdateModelAsync(
+ [NotNull] TModel model,
+ [NotNull] string prefix,
+ [NotNull] HttpContext httpContext,
+ [NotNull] ModelStateDictionary modelState,
+ [NotNull] IModelMetadataProvider metadataProvider,
+ [NotNull] IModelBinder modelBinder,
+ [NotNull] IValueProvider valueProvider,
+ [NotNull] IModelValidatorProvider validatorProvider,
+ [NotNull] params Expression>[] includeExpressions)
+ where TModel : class
+ {
+ var includeExpression = GetIncludePredicateExpression(prefix, includeExpressions);
+ Func predicate = includeExpression.Compile();
+
+ return TryUpdateModelAsync(
+ model,
+ prefix,
+ httpContext,
+ modelState,
+ metadataProvider,
+ modelBinder,
+ valueProvider,
+ validatorProvider,
+ predicate: predicate);
+ }
+
+ ///
+ /// Updates the specified instance using the specified
+ /// and the specified and executes validation using the specified
+ /// .
+ ///
+ /// The type of the model object.
+ /// The model instance to update and validate.
+ /// The prefix to use when looking up values in the .
+ ///
+ /// The for the current executing request.
+ /// The used for maintaining state and
+ /// results of model-binding validation.
+ /// The provider used for reading metadata for the model type.
+ /// The used for binding.
+ /// The used for looking up values.
+ /// The used for executing validation
+ /// on the model instance.
+ /// A predicate which can be used to
+ /// filter properties(for inclusion/exclusion) at runtime.
+ /// A that on completion returns true if the update is successful
+ public static async Task TryUpdateModelAsync(
+ [NotNull] TModel model,
+ [NotNull] string prefix,
+ [NotNull] HttpContext httpContext,
+ [NotNull] ModelStateDictionary modelState,
+ [NotNull] IModelMetadataProvider metadataProvider,
+ [NotNull] IModelBinder modelBinder,
+ [NotNull] IValueProvider valueProvider,
+ [NotNull] IModelValidatorProvider validatorProvider,
+ [NotNull] Func predicate)
+ where TModel : class
{
var modelMetadata = metadataProvider.GetMetadataForType(
modelAccessor: null,
@@ -56,6 +156,7 @@ namespace Microsoft.AspNet.Mvc
ValueProvider = valueProvider,
FallbackToEmptyPrefix = true,
OperationBindingContext = operationBindingContext,
+ PropertyFilter = predicate
};
if (await modelBinder.BindModelAsync(modelBindingContext))
@@ -65,5 +166,91 @@ namespace Microsoft.AspNet.Mvc
return false;
}
+
+ internal static string GetPropertyName(Expression expression)
+ {
+ if (expression.NodeType == ExpressionType.Convert ||
+ expression.NodeType == ExpressionType.ConvertChecked)
+ {
+ // For Boxed Value Types
+ expression = ((UnaryExpression)expression).Operand;
+ }
+
+ if (expression.NodeType != ExpressionType.MemberAccess)
+ {
+ throw new InvalidOperationException(Resources.FormatInvalid_IncludePropertyExpression(
+ expression.NodeType));
+ }
+
+ var memberExpression = (MemberExpression)expression;
+ var memberInfo = memberExpression.Member as PropertyInfo;
+ if (memberInfo != null)
+ {
+ if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
+ {
+ // Chained expressions and non parameter based expressions are not supported.
+ throw new InvalidOperationException(
+ Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType));
+ }
+
+ return memberInfo.Name;
+ }
+ else
+ {
+ // Fields are also not supported.
+ throw new InvalidOperationException(Resources.FormatInvalid_IncludePropertyExpression(
+ expression.NodeType));
+ }
+ }
+
+ private static Expression> GetIncludePredicateExpression
+ (string prefix, Expression>[] expressions)
+ {
+ if (expressions.Length == 0)
+ {
+ // If nothing is included explcitly, treat everything as included.
+ return (context, propertyName) => true;
+ }
+
+ var firstExpression = GetPredicateExpression(prefix, expressions[0]);
+ var orWrapperExpression = firstExpression.Body;
+ foreach (var expression in expressions.Skip(1))
+ {
+ var predicate = GetPredicateExpression(prefix, expression);
+ orWrapperExpression = Expression.OrElse(orWrapperExpression,
+ Expression.Invoke(predicate, firstExpression.Parameters));
+ }
+
+ return Expression.Lambda>(
+ orWrapperExpression, firstExpression.Parameters);
+ }
+
+ private static Expression> GetPredicateExpression
+ (string prefix, Expression> expression)
+ {
+ var propertyName = GetPropertyName(expression.Body);
+ var property = CreatePropertyModelName(prefix, propertyName);
+
+ return
+ (context, modelPropertyName) =>
+ property.Equals(CreatePropertyModelName(context.ModelName, modelPropertyName),
+ StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static string CreatePropertyModelName(string prefix, string propertyName)
+ {
+ if (string.IsNullOrEmpty(prefix))
+ {
+ return propertyName ?? string.Empty;
+ }
+ else if (string.IsNullOrEmpty(propertyName))
+ {
+ return prefix ?? string.Empty;
+ }
+ else
+ {
+ return prefix + "." + propertyName;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index b7a013f594..714664f8df 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -618,6 +618,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("ExpressionHelper_InvalidIndexerExpression"), p0, p1);
}
+ ///
+ /// The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported.
+ ///
+ internal static string Invalid_IncludePropertyExpression
+ {
+ get { return GetString("Invalid_IncludePropertyExpression"); }
+ }
+
+ ///
+ /// The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported.
+ ///
+ internal static string FormatInvalid_IncludePropertyExpression(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Invalid_IncludePropertyExpression"), p0);
+ }
+
///
/// The IModelMetadataProvider was unable to provide metadata for expression '{0}'.
///
@@ -1403,7 +1419,7 @@ namespace Microsoft.AspNet.Mvc.Core
}
///
- /// A method '{0}' must not define attribute routed actions and non attribute routed actions at the same time:{1}{2}{1}Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, or set a route template in all attributes that constrain HTTP verbs.
+ /// A method '{0}' must not define attribute routed actions and non attribute routed actions at the same time:{1}{2}{1}{1}Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, or set a route template in all attributes that constrain HTTP verbs.
///
internal static string AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod
{
@@ -1411,7 +1427,7 @@ namespace Microsoft.AspNet.Mvc.Core
}
///
- /// A method '{0}' must not define attribute routed actions and non attribute routed actions at the same time:{1}{2}{1}Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, or set a route template in all attributes that constrain HTTP verbs.
+ /// A method '{0}' must not define attribute routed actions and non attribute routed actions at the same time:{1}{2}{1}{1}Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, or set a route template in all attributes that constrain HTTP verbs.
///
internal static string FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod(object p0, object p1, object p2)
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index 6d2c64bbf7..87355a2b09 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -232,6 +232,9 @@
The expression compiler was unable to evaluate the indexer expression '{0}' because it references the model parameter '{1}' which is unavailable.
+
+ The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported.
+
The IModelMetadataProvider was unable to provide metadata for expression '{0}'.
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
index 204582e3f8..af58f66544 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
@@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var validationInfo = GetPropertyValidationInfo(bindingContext);
return bindingContext.ModelMetadata.Properties
.Where(propertyMetadata =>
- bindingContext.PropertyFilter(propertyMetadata.PropertyName) &&
+ bindingContext.PropertyFilter(bindingContext, propertyMetadata.PropertyName) &&
(validationInfo.RequiredProperties.Contains(propertyMetadata.PropertyName) ||
!validationInfo.SkipProperties.Contains(propertyMetadata.PropertyName)) &&
CanUpdateProperty(propertyMetadata));
@@ -473,21 +473,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return addedError;
}
- private static bool IsPropertyAllowed(string propertyName,
- IReadOnlyList includeProperties,
- IReadOnlyList excludeProperties)
- {
- // We allow a property to be bound if its both in the include list AND not in the exclude list.
- // An empty exclude list implies no properties are disallowed.
- var includeProperty = (includeProperties != null) &&
- includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
-
- var excludeProperty = (excludeProperties != null) &&
- excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
-
- return includeProperty && !excludeProperty;
- }
-
internal sealed class PropertyValidationInfo
{
public PropertyValidationInfo()
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs
index 14aaaef6ea..58042ebcac 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs
@@ -13,12 +13,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
public class ModelBindingContext
{
- private static readonly Predicate _defaultPropertyFilter = _ => true;
+ private static readonly Func
+ _defaultPropertyFilter = (context, propertyName) => true;
+
private string _modelName;
private ModelStateDictionary _modelState;
private Dictionary _propertyMetadata;
private ModelValidationNode _validationNode;
- private Predicate _propertyFilter;
+ private Func _propertyFilter;
///
/// Initializes a new instance of the class.
@@ -160,7 +162,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
- public Predicate PropertyFilter
+ public Func PropertyFilter
{
get
{
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
index 4f0b900fad..e097c02950 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs
@@ -675,6 +675,7 @@ namespace Microsoft.AspNet.Mvc.Test
[Fact]
public async Task Controller_ActionFilter_SettingResult_ShortCircuits()
{
+ // Arrange, Act & Assert
await ActionFilterAttributeTests.ActionFilter_SettingResult_ShortCircuits(
new Mock());
}
@@ -682,42 +683,34 @@ namespace Microsoft.AspNet.Mvc.Test
[Fact]
public async Task Controller_ActionFilter_Calls_OnActionExecuted()
{
+ // Arrange, Act & Assert
await ActionFilterAttributeTests.ActionFilter_Calls_OnActionExecuted(
new Mock());
}
[Fact]
- public async Task TryUpdateModel_UsesModelTypeNameIfNotSpecified()
+ public async Task TryUpdateModel_FallsBackOnEmptyPrefix_IfNotSpecified()
{
+ // Arrange
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var valueProvider = Mock.Of();
var binder = new Mock();
binder.Setup(b => b.BindModelAsync(It.IsAny()))
- .Callback((ModelBindingContext b) =>
+ .Callback((ModelBindingContext context) =>
{
- Assert.Equal(typeof(MyModel).Name, b.ModelName);
- Assert.Same(valueProvider, b.ValueProvider);
+ Assert.Empty(context.ModelName);
+ Assert.Same(valueProvider, context.ValueProvider);
+
+ // Include and exclude should be null, resulting in property
+ // being included.
+ Assert.True(context.PropertyFilter(context, "Property1"));
+ Assert.True(context.PropertyFilter(context, "Property2"));
})
.Returns(Task.FromResult(false))
.Verifiable();
+
+ var controller = GetController(binder.Object, valueProvider);
var model = new MyModel();
- var actionContext = new ActionContext(Mock.Of(), new RouteData(), new ActionDescriptor());
- var bindingContext = new ActionBindingContext(actionContext,
- metadataProvider,
- binder.Object,
- valueProvider,
- Mock.Of(),
- Mock.Of());
- var bindingContextProvider = new Mock();
- bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext))
- .Returns(Task.FromResult(bindingContext));
- var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
- var controller = new Controller
- {
- ActionContext = actionContext,
- BindingContextProvider = bindingContextProvider.Object,
- ViewData = viewData
- };
// Act
var result = await controller.TryUpdateModelAsync(model);
@@ -729,36 +722,28 @@ namespace Microsoft.AspNet.Mvc.Test
[Fact]
public async Task TryUpdateModel_UsesModelTypeNameIfSpecified()
{
+ // Arrange
+ var modelName = "mymodel";
+
var metadataProvider = new DataAnnotationsModelMetadataProvider();
var valueProvider = Mock.Of();
var binder = new Mock();
- var modelName = "mymodel";
binder.Setup(b => b.BindModelAsync(It.IsAny()))
- .Callback((ModelBindingContext b) =>
+ .Callback((ModelBindingContext context) =>
{
- Assert.Equal(modelName, b.ModelName);
- Assert.Same(valueProvider, b.ValueProvider);
+ Assert.Equal(modelName, context.ModelName);
+ Assert.Same(valueProvider, context.ValueProvider);
+
+ // Include and exclude should be null, resulting in property
+ // being included.
+ Assert.True(context.PropertyFilter(context, "Property1"));
+ Assert.True(context.PropertyFilter(context, "Property2"));
})
.Returns(Task.FromResult(false))
.Verifiable();
+
+ var controller = GetController(binder.Object, valueProvider);
var model = new MyModel();
- var actionContext = new ActionContext(Mock.Of(), new RouteData(), new ActionDescriptor());
- var bindingContext = new ActionBindingContext(actionContext,
- metadataProvider,
- binder.Object,
- valueProvider,
- Mock.Of(),
- Mock.Of());
- var bindingContextProvider = new Mock();
- bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext))
- .Returns(Task.FromResult(bindingContext));
- var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
- var controller = new Controller
- {
- ActionContext = actionContext,
- BindingContextProvider = bindingContextProvider.Object,
- ViewData = viewData
- };
// Act
var result = await controller.TryUpdateModelAsync(model, modelName);
@@ -770,36 +755,27 @@ namespace Microsoft.AspNet.Mvc.Test
[Fact]
public async Task TryUpdateModel_UsesModelValueProviderIfSpecified()
{
- var metadataProvider = new DataAnnotationsModelMetadataProvider();
+ // Arrange
+ var modelName = "mymodel";
+
var valueProvider = Mock.Of();
var binder = new Mock();
- var modelName = "mymodel";
binder.Setup(b => b.BindModelAsync(It.IsAny()))
- .Callback((ModelBindingContext b) =>
+ .Callback((ModelBindingContext context) =>
{
- Assert.Equal(modelName, b.ModelName);
- Assert.Same(valueProvider, b.ValueProvider);
+ Assert.Equal(modelName, context.ModelName);
+ Assert.Same(valueProvider, context.ValueProvider);
+
+ // Include and exclude should be null, resulting in property
+ // being included.
+ Assert.True(context.PropertyFilter(context, "Property1"));
+ Assert.True(context.PropertyFilter(context, "Property2"));
})
.Returns(Task.FromResult(false))
.Verifiable();
+
+ var controller = GetController(binder.Object, provider: null);
var model = new MyModel();
- var actionContext = new ActionContext(Mock.Of(), new RouteData(), new ActionDescriptor());
- var bindingContext = new ActionBindingContext(actionContext,
- metadataProvider,
- binder.Object,
- Mock.Of(),
- Mock.Of(),
- Mock.Of());
- var bindingContextProvider = new Mock();
- bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext))
- .Returns(Task.FromResult(bindingContext));
- var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
- var controller = new Controller
- {
- ActionContext = actionContext,
- BindingContextProvider = bindingContextProvider.Object,
- ViewData = viewData
- };
// Act
var result = await controller.TryUpdateModelAsync(model, modelName, valueProvider);
@@ -807,6 +783,151 @@ namespace Microsoft.AspNet.Mvc.Test
// Assert
binder.Verify();
}
+
+ [Fact]
+ public async Task TryUpdateModel_PredicateOverload_UsesPassedArguments()
+ {
+ // Arrange
+ var modelName = "mymodel";
+
+ Func includePredicate =
+ (context, propertyName) =>
+ string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase);
+
+ var binder = new Mock();
+ var valueProvider = Mock.Of();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .Callback((ModelBindingContext context) =>
+ {
+ Assert.Equal(modelName, context.ModelName);
+ Assert.Same(valueProvider, context.ValueProvider);
+
+ Assert.True(context.PropertyFilter(context, "include1"));
+ Assert.True(context.PropertyFilter(context, "include2"));
+
+ Assert.False(context.PropertyFilter(context, "exclude1"));
+ Assert.False(context.PropertyFilter(context, "exclude2"));
+ })
+ .Returns(Task.FromResult(true))
+ .Verifiable();
+
+ var controller = GetController(binder.Object, valueProvider);
+ var model = new MyModel();
+
+ // Act
+ await controller.TryUpdateModelAsync(model, modelName, includePredicate);
+
+ // Assert
+ binder.Verify();
+ }
+
+ [Fact]
+ public async Task TryUpdateModel_PredicateWithValueProviderOverload_UsesPassedArguments()
+ {
+ // Arrange
+ var modelName = "mymodel";
+
+ Func includePredicate =
+ (context, propertyName) => string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase);
+
+ var binder = new Mock();
+ var valueProvider = Mock.Of();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .Callback((ModelBindingContext context) =>
+ {
+ Assert.Equal(modelName, context.ModelName);
+ Assert.Same(valueProvider, context.ValueProvider);
+
+ Assert.True(context.PropertyFilter(context, "include1"));
+ Assert.True(context.PropertyFilter(context, "include2"));
+
+ Assert.False(context.PropertyFilter(context, "exclude1"));
+ Assert.False(context.PropertyFilter(context, "exclude2"));
+ })
+ .Returns(Task.FromResult(true))
+ .Verifiable();
+
+ var controller = GetController(binder.Object, provider: null);
+
+ var model = new MyModel();
+
+ // Act
+ await controller.TryUpdateModelAsync(model, modelName, valueProvider, includePredicate);
+
+ // Assert
+ binder.Verify();
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("prefix")]
+ public async Task TryUpdateModel_IncludeExpressionOverload_UsesPassedArguments(string prefix)
+ {
+ // Arrange
+ var binder = new Mock();
+ var valueProvider = Mock.Of();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .Callback((ModelBindingContext context) =>
+ {
+ Assert.Equal(prefix, context.ModelName);
+ Assert.Same(valueProvider, context.ValueProvider);
+
+ Assert.True(context.PropertyFilter(context, "Property1"));
+ Assert.True(context.PropertyFilter(context, "Property2"));
+
+ Assert.False(context.PropertyFilter(context, "exclude1"));
+ Assert.False(context.PropertyFilter(context, "exclude2"));
+ })
+ .Returns(Task.FromResult(true))
+ .Verifiable();
+
+
+ var controller = GetController(binder.Object, valueProvider);
+ var model = new MyModel();
+
+ // Act
+ await controller.TryUpdateModelAsync(model, prefix, m => m.Property1, m => m.Property2);
+
+ // Assert
+ binder.Verify();
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("prefix")]
+ public async Task
+ TryUpdateModel_IncludeExpressionWithValueProviderOverload_UsesPassedArguments(string prefix)
+ {
+ // Arrange
+ var binder = new Mock();
+ var valueProvider = new Mock();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .Callback((ModelBindingContext context) =>
+ {
+ Assert.Equal(prefix, context.ModelName);
+ Assert.Same(valueProvider.Object, context.ValueProvider);
+
+ Assert.True(context.PropertyFilter(context, "Property1"));
+ Assert.True(context.PropertyFilter(context, "Property2"));
+
+ Assert.False(context.PropertyFilter(context, "exclude1"));
+ Assert.False(context.PropertyFilter(context, "exclude2"));
+ })
+ .Returns(Task.FromResult(true))
+ .Verifiable();
+
+ var controller = GetController(binder.Object, provider: null);
+ var model = new MyModel();
+
+ // Act
+ await controller.TryUpdateModelAsync(model, prefix, valueProvider.Object, m => m.Property1, m => m.Property2);
+
+ // Assert
+ binder.Verify();
+ }
+
#endif
[Fact]
@@ -966,9 +1087,55 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Same(viewEngine, result.ViewEngine);
}
+ private static Controller GetController(IModelBinder binder, IValueProvider provider)
+ {
+ var metadataProvider = new DataAnnotationsModelMetadataProvider();
+ var actionContext = new ActionContext(Mock.Of(), new RouteData(), new ActionDescriptor());
+ var bindingContext = new ActionBindingContext(actionContext,
+ metadataProvider,
+ binder,
+ provider ?? Mock.Of(),
+ Mock.Of(),
+ Mock.Of());
+ var bindingContextProvider = new Mock();
+ bindingContextProvider.Setup(b => b.GetActionBindingContextAsync(actionContext))
+ .Returns(Task.FromResult(bindingContext));
+
+ var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
+ return new Controller
+ {
+ ActionContext = actionContext,
+ BindingContextProvider = bindingContextProvider.Object,
+ ViewData = viewData
+ };
+ }
+
private class MyModel
{
- public string Foo { get; set; }
+ public string Property1 { get; set; }
+ public string Property2 { get; set; }
+ }
+
+ private class User
+ {
+ public User(int id)
+ {
+ Id = id;
+ }
+
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+
+ public Address Address { get; set; }
+
+ }
+
+ private class Address
+ {
+ public string Street { get; set; }
+ public string City { get; set; }
+ public int Zip { get; set; }
}
private class DisposableController : Controller
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs
index a6bc2644d2..8c13fabdd0 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs
@@ -70,8 +70,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
modelMetadata, actionBindingContext, Mock.Of());
// Assert
- Assert.False(context.PropertyFilter("Excluded1"));
- Assert.False(context.PropertyFilter("Excluded2"));
+ Assert.False(context.PropertyFilter(context, "Excluded1"));
+ Assert.False(context.PropertyFilter(context, "Excluded2"));
}
[Fact]
@@ -96,8 +96,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
modelMetadata, actionBindingContext, Mock.Of());
// Assert
- Assert.True(context.PropertyFilter("IncludedExplicitly1"));
- Assert.True(context.PropertyFilter("IncludedExplicitly2"));
+ Assert.True(context.PropertyFilter(context, "IncludedExplicitly1"));
+ Assert.True(context.PropertyFilter(context, "IncludedExplicitly2"));
}
[Fact]
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs
index 5bebb4b0ac..80b2e5c75c 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs
@@ -3,8 +3,11 @@
#if ASPNET50
using System;
+using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Linq.Expressions;
+using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.ModelBinding;
@@ -123,6 +126,346 @@ namespace Microsoft.AspNet.Mvc.Core.Test
Assert.Equal("MyPropertyValue", model.MyProperty);
}
+ [Fact]
+ public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsFalse_IfBinderReturnsFalse()
+ {
+ // Arrange
+ var metadataProvider = new Mock();
+ metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny()))
+ .Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
+ .Verifiable();
+
+ var binder = new Mock();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .Returns(Task.FromResult(false));
+ var model = new MyModel();
+ Func includePredicate =
+ (context, propertyName) => true;
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ null,
+ Mock.Of(),
+ new ModelStateDictionary(),
+ metadataProvider.Object,
+ GetCompositeBinder(binder.Object),
+ Mock.Of(),
+ Mock.Of(),
+ includePredicate);
+
+ // Assert
+ Assert.False(result);
+ Assert.Null(model.MyProperty);
+ Assert.Null(model.IncludedProperty);
+ Assert.Null(model.ExcludedProperty);
+ metadataProvider.Verify();
+ }
+
+ [Fact]
+ public async Task TryUpdateModel_UsingIncludePredicateOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
+ {
+ // Arrange
+ var binders = new IModelBinder[]
+ {
+ new TypeConverterModelBinder(),
+ new ComplexModelDtoModelBinder(),
+ new MutableObjectModelBinder()
+ };
+
+ var validator = new DataAnnotationsModelValidatorProvider();
+ var model = new MyModel {
+ MyProperty = "Old-Value",
+ IncludedProperty = "Old-IncludedPropertyValue",
+ ExcludedProperty = "Old-ExcludedPropertyValue"
+ };
+
+ var modelStateDictionary = new ModelStateDictionary();
+ var values = new Dictionary
+ {
+ { "", null },
+ { "MyProperty", "MyPropertyValue" },
+ { "IncludedProperty", "IncludedPropertyValue" },
+ { "ExcludedProperty", "ExcludedPropertyValue" }
+ };
+
+ Func includePredicate =
+ (context, propertyName) =>
+ string.Equals(propertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
+
+ var valueProvider = new DictionaryBasedValueProvider(values);
+
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ "",
+ Mock.Of(),
+ modelStateDictionary,
+ new DataAnnotationsModelMetadataProvider(),
+ GetCompositeBinder(binders),
+ valueProvider,
+ validator,
+ includePredicate);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal("MyPropertyValue", model.MyProperty);
+ Assert.Equal("IncludedPropertyValue", model.IncludedProperty);
+ Assert.Equal("Old-ExcludedPropertyValue", model.ExcludedProperty);
+ }
+
+ [Fact]
+ public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsFalse_IfBinderReturnsFalse()
+ {
+ // Arrange
+ var metadataProvider = new Mock();
+ metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny()))
+ .Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
+ .Verifiable();
+
+ var binder = new Mock();
+ binder.Setup(b => b.BindModelAsync(It.IsAny()))
+ .Returns(Task.FromResult(false));
+ var model = new MyModel();
+
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ null,
+ Mock.Of(),
+ new ModelStateDictionary(),
+ metadataProvider.Object,
+ GetCompositeBinder(binder.Object),
+ Mock.Of(),
+ Mock.Of(),
+ m => m.IncludedProperty );
+
+ // Assert
+ Assert.False(result);
+ Assert.Null(model.MyProperty);
+ Assert.Null(model.IncludedProperty);
+ Assert.Null(model.ExcludedProperty);
+ metadataProvider.Verify();
+ }
+
+ [Fact]
+ public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
+ {
+ // Arrange
+ var binders = new IModelBinder[]
+ {
+ new TypeConverterModelBinder(),
+ new ComplexModelDtoModelBinder(),
+ new MutableObjectModelBinder()
+ };
+
+ var validator = new DataAnnotationsModelValidatorProvider();
+ var model = new MyModel
+ {
+ MyProperty = "Old-Value",
+ IncludedProperty = "Old-IncludedPropertyValue",
+ ExcludedProperty = "Old-ExcludedPropertyValue"
+ };
+
+ var modelStateDictionary = new ModelStateDictionary();
+ var values = new Dictionary
+ {
+ { "", null },
+ { "MyProperty", "MyPropertyValue" },
+ { "IncludedProperty", "IncludedPropertyValue" },
+ { "ExcludedProperty", "ExcludedPropertyValue" }
+ };
+
+ var valueProvider = new DictionaryBasedValueProvider(values);
+
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ "",
+ Mock.Of(),
+ modelStateDictionary,
+ new DataAnnotationsModelMetadataProvider(),
+ GetCompositeBinder(binders),
+ valueProvider,
+ validator,
+ m => m.IncludedProperty,
+ m => m.MyProperty);
+
+ // Assert
+ Assert.True(result);
+ Assert.Equal("MyPropertyValue", model.MyProperty);
+ Assert.Equal("IncludedPropertyValue", model.IncludedProperty);
+ Assert.Equal("Old-ExcludedPropertyValue", model.ExcludedProperty);
+ }
+
+ [Fact]
+ public async Task TryUpdateModel_UsingDefaultIncludeOverload_IncludesAllProperties()
+ {
+ // Arrange
+ var binders = new IModelBinder[]
+ {
+ new TypeConverterModelBinder(),
+ new ComplexModelDtoModelBinder(),
+ new MutableObjectModelBinder()
+ };
+
+ var validator = new DataAnnotationsModelValidatorProvider();
+ var model = new MyModel
+ {
+ MyProperty = "Old-Value",
+ IncludedProperty = "Old-IncludedPropertyValue",
+ ExcludedProperty = "Old-ExcludedPropertyValue"
+ };
+
+ var modelStateDictionary = new ModelStateDictionary();
+ var values = new Dictionary
+ {
+ { "", null },
+ { "MyProperty", "MyPropertyValue" },
+ { "IncludedProperty", "IncludedPropertyValue" },
+ { "ExcludedProperty", "ExcludedPropertyValue" }
+ };
+
+ var valueProvider = new DictionaryBasedValueProvider(values);
+
+ // Act
+ var result = await ModelBindingHelper.TryUpdateModelAsync(
+ model,
+ "",
+ Mock.Of(),
+ modelStateDictionary,
+ new DataAnnotationsModelMetadataProvider(),
+ GetCompositeBinder(binders),
+ valueProvider,
+ validator);
+
+ // Assert
+ // Includes everything.
+ Assert.True(result);
+ Assert.Equal("MyPropertyValue", model.MyProperty);
+ Assert.Equal("IncludedPropertyValue", model.IncludedProperty);
+ Assert.Equal("ExcludedPropertyValue", model.ExcludedProperty);
+ }
+
+ [Fact]
+ public void GetPropertyName_PropertyMemberAccessReturnsPropertyName()
+ {
+ // Arrange
+ Expression> expression = m => m.Address;
+
+ // Act
+ var propertyName = ModelBindingHelper.GetPropertyName(expression.Body);
+
+ // Assert
+ Assert.Equal(nameof(User.Address), propertyName);
+ }
+
+ [Fact]
+ public void GetPropertyName_ChainedExpression_Throws()
+ {
+ // Arrange
+ Expression> expression = m => m.Address.Street;
+
+ // Act & Assert
+ var ex = Assert.Throws(() =>
+ ModelBindingHelper.GetPropertyName(expression.Body));
+
+ Assert.Equal(string.Format("The passed expression of expression node type '{0}' is invalid." +
+ " Only simple member access expressions for model properties are supported.",
+ expression.Body.NodeType),
+ ex.Message);
+ }
+
+ public static IEnumerable