Adding Support for TryUpdateModel using include expressions and predicate.
This commit is contained in:
parent
b54c326ee6
commit
2353bd911a
|
|
@ -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
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified model instance using values from the controller's current value provider.
|
||||
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
|
||||
/// <see cref="IValueProvider"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <returns>true if the update is successful; otherwise, false.</returns>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
[NonAction]
|
||||
public virtual Task<bool> TryUpdateModelAsync<TModel>([NotNull] TModel model)
|
||||
where TModel : class
|
||||
{
|
||||
return TryUpdateModelAsync(model, prefix: typeof(TModel).Name);
|
||||
return TryUpdateModelAsync(model, prefix: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified model instance using values from the controller's current value provider
|
||||
/// and a prefix.
|
||||
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
|
||||
/// <see cref="IValueProvider"/> and a <paramref name="prefix"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the value provider.</param>
|
||||
/// <returns>true if the update is successful; otherwise, false.</returns>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>
|
||||
/// </param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
[NonAction]
|
||||
public virtual async Task<bool> TryUpdateModelAsync<TModel>([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
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified model instance using the value provider and a prefix.
|
||||
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
|
||||
/// <paramref name="prefix"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the value provider.</param>
|
||||
/// <param name="valueProvider">The value provider used for looking up values.</param>
|
||||
/// <returns>true if the update is successful; otherwise, false.</returns>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
||||
/// </param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
[NonAction]
|
||||
public virtual async Task<bool> TryUpdateModelAsync<TModel>([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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
|
||||
/// <see cref="IValueProvider"/> and a <paramref name="prefix"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>.
|
||||
/// </param>
|
||||
/// <param name="includeExpressions"> <see cref="Expression"/>(s) which represent top-level properties
|
||||
/// which need to be included for the current model.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
[NonAction]
|
||||
public async Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[NotNull] TModel model,
|
||||
string prefix,
|
||||
[NotNull] params Expression<Func<TModel, object>>[] 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
|
||||
/// <see cref="IValueProvider"/> and a <paramref name="prefix"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the current <see cref="IValueProvider"/>.
|
||||
/// </param>
|
||||
/// <param name="predicate">A predicate which can be used to filter properties at runtime.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
[NonAction]
|
||||
public async Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[NotNull] TModel model,
|
||||
string prefix,
|
||||
[NotNull] Func<ModelBindingContext, string, bool> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
|
||||
/// <paramref name="prefix"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>
|
||||
/// </param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="includeExpressions"> <see cref="Expression"/>(s) which represent top-level properties
|
||||
/// which need to be included for the current model.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
[NonAction]
|
||||
public async Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[NotNull] TModel model,
|
||||
string prefix,
|
||||
[NotNull] IValueProvider valueProvider,
|
||||
[NotNull] params Expression<Func<TModel, object>>[] 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the <paramref name="valueProvider"/> and a
|
||||
/// <paramref name="prefix"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>
|
||||
/// </param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="predicate">A predicate which can be used to filter properties at runtime.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
[NonAction]
|
||||
public async Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[NotNull] TModel model,
|
||||
string prefix,
|
||||
[NotNull] IValueProvider valueProvider,
|
||||
[NotNull] Func<ModelBindingContext, string, bool> 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)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
ActionBindingContext actionBindingContext,
|
||||
OperationBindingContext operationBindingContext)
|
||||
{
|
||||
Predicate<string> propertyFilter =
|
||||
propertyName => BindAttribute.IsPropertyAllowed(propertyName,
|
||||
Func<ModelBindingContext, string, bool> propertyFilter =
|
||||
(context, propertyName) => BindAttribute.IsPropertyAllowed(propertyName,
|
||||
modelMetadata.BinderIncludeProperties,
|
||||
modelMetadata.BinderExcludeProperties);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the value provider.</param>
|
||||
/// <param name="httpContext">The context for the current executing request.</param>
|
||||
/// <param name="modelState">The ModelStateDictionary used for maintaining state and
|
||||
/// <param name="model">The model instance to update and validate.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
||||
/// </param>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
||||
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
||||
/// results of model-binding validation.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The model binder used for binding.</param>
|
||||
/// <param name="valueProvider">The value provider used for looking up values.</param>
|
||||
/// <param name="validatorProvider">The validator provider used for executing validation on the model
|
||||
/// instance.</param>
|
||||
/// <returns>A Task with a value representing if the the update is successful.</returns>
|
||||
public static async Task<bool> TryUpdateModelAsync<TModel>(
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
||||
/// on the model instance.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
public static Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update and validate.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
||||
/// </param>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
||||
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
||||
/// results of model-binding validation.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
||||
/// on the model
|
||||
/// instance.</param>
|
||||
/// <param name="includeExpressions">Expression(s) which represent top level properties
|
||||
/// which need to be included for the current model.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
public static Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[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<Func<TModel, object>>[] includeExpressions)
|
||||
where TModel : class
|
||||
{
|
||||
var includeExpression = GetIncludePredicateExpression(prefix, includeExpressions);
|
||||
Func<ModelBindingContext, string, bool> predicate = includeExpression.Compile();
|
||||
|
||||
return TryUpdateModelAsync(
|
||||
model,
|
||||
prefix,
|
||||
httpContext,
|
||||
modelState,
|
||||
metadataProvider,
|
||||
modelBinder,
|
||||
valueProvider,
|
||||
validatorProvider,
|
||||
predicate: predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the specified <paramref name="model"/> instance using the specified <paramref name="modelBinder"/>
|
||||
/// and the specified <paramref name="valueProvider"/> and executes validation using the specified
|
||||
/// <paramref name="validatorProvider"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of the model object.</typeparam>
|
||||
/// <param name="model">The model instance to update and validate.</param>
|
||||
/// <param name="prefix">The prefix to use when looking up values in the <paramref name="valueProvider"/>.
|
||||
/// </param>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> for the current executing request.</param>
|
||||
/// <param name="modelState">The <see cref="ModelStateDictionary"/> used for maintaining state and
|
||||
/// results of model-binding validation.</param>
|
||||
/// <param name="metadataProvider">The provider used for reading metadata for the model type.</param>
|
||||
/// <param name="modelBinder">The <see cref="IModelBinder"/> used for binding.</param>
|
||||
/// <param name="valueProvider">The <see cref="IValueProvider"/> used for looking up values.</param>
|
||||
/// <param name="validatorProvider">The <see cref="IModelValidatorProvider"/> used for executing validation
|
||||
/// on the model instance.</param>
|
||||
/// <param name="predicate">A predicate which can be used to
|
||||
/// filter properties(for inclusion/exclusion) at runtime.</param>
|
||||
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful</returns>
|
||||
public static async Task<bool> TryUpdateModelAsync<TModel>(
|
||||
[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<ModelBindingContext, string, bool> 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<Func<ModelBindingContext, string, bool>> GetIncludePredicateExpression<TModel>
|
||||
(string prefix, Expression<Func<TModel, object>>[] 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<Func<ModelBindingContext, string, bool>>(
|
||||
orWrapperExpression, firstExpression.Parameters);
|
||||
}
|
||||
|
||||
private static Expression<Func<ModelBindingContext, string, bool>> GetPredicateExpression<TModel>
|
||||
(string prefix, Expression<Func<TModel, object>> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -618,6 +618,22 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("ExpressionHelper_InvalidIndexerExpression"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported.
|
||||
/// </summary>
|
||||
internal static string Invalid_IncludePropertyExpression
|
||||
{
|
||||
get { return GetString("Invalid_IncludePropertyExpression"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported.
|
||||
/// </summary>
|
||||
internal static string FormatInvalid_IncludePropertyExpression(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Invalid_IncludePropertyExpression"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The IModelMetadataProvider was unable to provide metadata for expression '{0}'.
|
||||
/// </summary>
|
||||
|
|
@ -1403,7 +1419,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod
|
||||
{
|
||||
|
|
@ -1411,7 +1427,7 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
internal static string FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod(object p0, object p1, object p2)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -232,6 +232,9 @@
|
|||
<data name="ExpressionHelper_InvalidIndexerExpression" xml:space="preserve">
|
||||
<value>The expression compiler was unable to evaluate the indexer expression '{0}' because it references the model parameter '{1}' which is unavailable.</value>
|
||||
</data>
|
||||
<data name="Invalid_IncludePropertyExpression" xml:space="preserve">
|
||||
<value>The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported.</value>
|
||||
</data>
|
||||
<data name="HtmlHelper_NullModelMetadata" xml:space="preserve">
|
||||
<value>The IModelMetadataProvider was unable to provide metadata for expression '{0}'.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -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<string> includeProperties,
|
||||
IReadOnlyList<string> 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()
|
||||
|
|
|
|||
|
|
@ -13,12 +13,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
/// </summary>
|
||||
public class ModelBindingContext
|
||||
{
|
||||
private static readonly Predicate<string> _defaultPropertyFilter = _ => true;
|
||||
private static readonly Func<ModelBindingContext, string, bool>
|
||||
_defaultPropertyFilter = (context, propertyName) => true;
|
||||
|
||||
private string _modelName;
|
||||
private ModelStateDictionary _modelState;
|
||||
private Dictionary<string, ModelMetadata> _propertyMetadata;
|
||||
private ModelValidationNode _validationNode;
|
||||
private Predicate<string> _propertyFilter;
|
||||
private Func<ModelBindingContext, string, bool> _propertyFilter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelBindingContext"/> class.
|
||||
|
|
@ -160,7 +162,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
public Predicate<string> PropertyFilter
|
||||
public Func<ModelBindingContext, string, bool> PropertyFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<Controller>());
|
||||
}
|
||||
|
|
@ -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<Controller>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_UsesModelTypeNameIfNotSpecified()
|
||||
public async Task TryUpdateModel_FallsBackOnEmptyPrefix_IfNotSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var valueProvider = Mock.Of<IValueProvider>();
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.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<HttpContext>(), new RouteData(), new ActionDescriptor());
|
||||
var bindingContext = new ActionBindingContext(actionContext,
|
||||
metadataProvider,
|
||||
binder.Object,
|
||||
valueProvider,
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
var bindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
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<IValueProvider>();
|
||||
var binder = new Mock<IModelBinder>();
|
||||
var modelName = "mymodel";
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.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<HttpContext>(), new RouteData(), new ActionDescriptor());
|
||||
var bindingContext = new ActionBindingContext(actionContext,
|
||||
metadataProvider,
|
||||
binder.Object,
|
||||
valueProvider,
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
var bindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
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<IValueProvider>();
|
||||
var binder = new Mock<IModelBinder>();
|
||||
var modelName = "mymodel";
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.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<HttpContext>(), new RouteData(), new ActionDescriptor());
|
||||
var bindingContext = new ActionBindingContext(actionContext,
|
||||
metadataProvider,
|
||||
binder.Object,
|
||||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
var bindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
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<ModelBindingContext, string, bool> includePredicate =
|
||||
(context, propertyName) =>
|
||||
string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
var valueProvider = Mock.Of<IValueProvider>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.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<ModelBindingContext, string, bool> includePredicate =
|
||||
(context, propertyName) => string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
var valueProvider = Mock.Of<IValueProvider>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.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<IModelBinder>();
|
||||
var valueProvider = Mock.Of<IValueProvider>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.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<IModelBinder>();
|
||||
var valueProvider = new Mock<IValueProvider>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.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<HttpContext>(), new RouteData(), new ActionDescriptor());
|
||||
var bindingContext = new ActionBindingContext(actionContext,
|
||||
metadataProvider,
|
||||
binder,
|
||||
provider ?? Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
var bindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||
|
||||
// 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<OperationBindingContext>());
|
||||
|
||||
// Assert
|
||||
Assert.True(context.PropertyFilter("IncludedExplicitly1"));
|
||||
Assert.True(context.PropertyFilter("IncludedExplicitly2"));
|
||||
Assert.True(context.PropertyFilter(context, "IncludedExplicitly1"));
|
||||
Assert.True(context.PropertyFilter(context, "IncludedExplicitly2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -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<IModelMetadataProvider>();
|
||||
metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny<Type>()))
|
||||
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
||||
.Verifiable();
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
var model = new MyModel();
|
||||
Func<ModelBindingContext, string, bool> includePredicate =
|
||||
(context, propertyName) => true;
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
null,
|
||||
Mock.Of<HttpContext>(),
|
||||
new ModelStateDictionary(),
|
||||
metadataProvider.Object,
|
||||
GetCompositeBinder(binder.Object),
|
||||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
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<string, object>
|
||||
{
|
||||
{ "", null },
|
||||
{ "MyProperty", "MyPropertyValue" },
|
||||
{ "IncludedProperty", "IncludedPropertyValue" },
|
||||
{ "ExcludedProperty", "ExcludedPropertyValue" }
|
||||
};
|
||||
|
||||
Func<ModelBindingContext, string, bool> includePredicate =
|
||||
(context, propertyName) =>
|
||||
string.Equals(propertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var valueProvider = new DictionaryBasedValueProvider<TestValueBinderMetadata>(values);
|
||||
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
"",
|
||||
Mock.Of<HttpContext>(),
|
||||
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<IModelMetadataProvider>();
|
||||
metadataProvider.Setup(m => m.GetMetadataForType(null, It.IsAny<Type>()))
|
||||
.Returns(new ModelMetadata(metadataProvider.Object, null, null, typeof(MyModel), null))
|
||||
.Verifiable();
|
||||
|
||||
var binder = new Mock<IModelBinder>();
|
||||
binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||
.Returns(Task.FromResult(false));
|
||||
var model = new MyModel();
|
||||
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
null,
|
||||
Mock.Of<HttpContext>(),
|
||||
new ModelStateDictionary(),
|
||||
metadataProvider.Object,
|
||||
GetCompositeBinder(binder.Object),
|
||||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IModelValidatorProvider>(),
|
||||
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<string, object>
|
||||
{
|
||||
{ "", null },
|
||||
{ "MyProperty", "MyPropertyValue" },
|
||||
{ "IncludedProperty", "IncludedPropertyValue" },
|
||||
{ "ExcludedProperty", "ExcludedPropertyValue" }
|
||||
};
|
||||
|
||||
var valueProvider = new DictionaryBasedValueProvider<TestValueBinderMetadata>(values);
|
||||
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
"",
|
||||
Mock.Of<HttpContext>(),
|
||||
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<string, object>
|
||||
{
|
||||
{ "", null },
|
||||
{ "MyProperty", "MyPropertyValue" },
|
||||
{ "IncludedProperty", "IncludedPropertyValue" },
|
||||
{ "ExcludedProperty", "ExcludedPropertyValue" }
|
||||
};
|
||||
|
||||
var valueProvider = new DictionaryBasedValueProvider<TestValueBinderMetadata>(values);
|
||||
|
||||
// Act
|
||||
var result = await ModelBindingHelper.TryUpdateModelAsync(
|
||||
model,
|
||||
"",
|
||||
Mock.Of<HttpContext>(),
|
||||
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<Func<User, object>> 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<Func<User, object>> expression = m => m.Address.Street;
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
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<object[]> InvalidExpressionDataSet
|
||||
{
|
||||
get
|
||||
{
|
||||
Expression<Func<User, object>> expression = m => new Func<User>(() => m);
|
||||
yield return new object[] { expression }; // lambda expression.
|
||||
|
||||
expression = m => m.Save();
|
||||
yield return new object[] { expression }; // method call expression.
|
||||
|
||||
expression = m => m.Friends[0]; // ArrayIndex expression.
|
||||
yield return new object[] { expression };
|
||||
|
||||
expression = m => m.Colleagues[0]; // Indexer expression.
|
||||
yield return new object[] { expression };
|
||||
|
||||
expression = m => m; // Parameter expression.
|
||||
yield return new object[] { expression };
|
||||
|
||||
object someVariable = "something";
|
||||
expression = m => someVariable; // Variable accessor.
|
||||
yield return new object[] { expression };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidExpressionDataSet))]
|
||||
public void GetPropertyName_ExpressionsOtherThanMemberAccess_Throws(Expression<Func<User, object>> expression)
|
||||
{
|
||||
// Arrange Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyName_NonParameterBasedExpression_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var someUser = new User();
|
||||
|
||||
// PropertyAccessor with a property name invalid as it originates from a variable accessor.
|
||||
Expression<Func<User, object>> expression = m => someUser.Address;
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyName_TopLevelCollectionIndexer_Throws()
|
||||
{
|
||||
// Arrange
|
||||
Expression<Func<List<User>, object>> expression = m => m[0];
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPropertyName_FieldExpression_Throws()
|
||||
{
|
||||
// Arrange
|
||||
Expression<Func<User, object>> expression = m => m._userId;
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
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);
|
||||
}
|
||||
|
||||
private static IModelBinder GetCompositeBinder(params IModelBinder[] binders)
|
||||
{
|
||||
var binderProvider = new Mock<IModelBinderProvider>();
|
||||
|
|
@ -131,10 +474,43 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
return new CompositeModelBinder(binderProvider.Object);
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public string _userId;
|
||||
|
||||
public Address Address { get; set; }
|
||||
|
||||
public User[] Friends { get; set; }
|
||||
|
||||
public List<User> Colleagues { get; set; }
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public User Save()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class Address
|
||||
{
|
||||
public string Street { get; set; }
|
||||
}
|
||||
|
||||
private class MyModel
|
||||
{
|
||||
[Required]
|
||||
public string MyProperty { get; set; }
|
||||
|
||||
public string IncludedProperty { get; set; }
|
||||
|
||||
public string ExcludedProperty { get; set; }
|
||||
}
|
||||
|
||||
private class TestValueBinderMetadata : IValueProviderMetadata
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
|
@ -826,5 +827,179 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_IncludeTopLevelProperty_IncludesAllSubProperties()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||
"GetUserAsync_IncludesAllSubProperties" +
|
||||
"?id=123&Key=34&RegistrationMonth=March&Address.Street=123&Address.Country.Name=USA&" +
|
||||
"Address.State=WA&Address.Country.Cities[0].CityName=Seattle&Address.Country.Cities[0].CityCode=SEA");
|
||||
|
||||
// Assert
|
||||
var user = JsonConvert.DeserializeObject<User>(response);
|
||||
|
||||
// Should update everything under Address.
|
||||
Assert.Equal(123, user.Address.Street); // Included by default as sub properties are included.
|
||||
Assert.Equal("WA", user.Address.State); // Included by default as sub properties of address are included.
|
||||
Assert.Equal("USA", user.Address.Country.Name); // Included by default.
|
||||
Assert.Equal("Seattle", user.Address.Country.Cities[0].CityName); // Included by default.
|
||||
Assert.Equal("SEA", user.Address.Country.Cities[0].CityCode); // Included by default.
|
||||
|
||||
// Should not update Any property at the same level as address.
|
||||
// Key is id + 20.
|
||||
Assert.Equal(143, user.Key);
|
||||
Assert.Null(user.RegisterationMonth);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_ChainedPropertyExpression_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
Expression<Func<User, object>> expression = model => model.Address.Country;
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
client.GetAsync("http://localhost/TryUpdateModel/GetUserAsync_WithChainedProperties?id=123"));
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_FailsToUpdateProperties()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||
"TryUpdateModelFails" +
|
||||
"?id=123&RegisterationMonth=March&Key=123&UserName=SomeName");
|
||||
|
||||
// Assert
|
||||
var result = JsonConvert.DeserializeObject<bool>(response);
|
||||
|
||||
// Act
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_IncludeExpression_WorksOnlyAtTopLevel()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||
"GetPerson" +
|
||||
"?Parent.Name=fatherName&Parent.Parent.Name=grandFatherName");
|
||||
|
||||
// Assert
|
||||
var person = JsonConvert.DeserializeObject<Person>(response);
|
||||
|
||||
// Act
|
||||
Assert.Equal("fatherName", person.Parent.Name);
|
||||
|
||||
// Includes this as there is data from value providers, the include filter
|
||||
// only works for top level objects.
|
||||
Assert.Equal("grandFatherName", person.Parent.Parent.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModel_Validates_ForTopLevelNotIncludedProperties()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||
"CreateAndUpdateUser" +
|
||||
"?RegisterationMonth=March");
|
||||
|
||||
// Assert
|
||||
var result = JsonConvert.DeserializeObject<bool>(response);
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModelExcludeSpecific_Properties()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||
"GetUserAsync_ExcludeSpecificProperties" +
|
||||
"?id=123&RegisterationMonth=March&Key=123&UserName=SomeName");
|
||||
|
||||
// Assert
|
||||
var user = JsonConvert.DeserializeObject<User>(response);
|
||||
|
||||
// Should not update excluded properties.
|
||||
Assert.NotEqual(123, user.Key);
|
||||
|
||||
// Should Update all explicitly included properties.
|
||||
Assert.Equal("March", user.RegisterationMonth);
|
||||
Assert.Equal("SomeName", user.UserName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModelIncludeSpecific_Properties()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||
"GetUserAsync_IncludeSpecificProperties" +
|
||||
"?id=123&RegisterationMonth=March&Key=123&UserName=SomeName");
|
||||
|
||||
// Assert
|
||||
var user = JsonConvert.DeserializeObject<User>(response);
|
||||
|
||||
// Should not update any not explicitly mentioned properties.
|
||||
Assert.NotEqual("SomeName", user.UserName);
|
||||
Assert.NotEqual(123, user.Key);
|
||||
|
||||
// Should Update all included properties.
|
||||
Assert.Equal("March", user.RegisterationMonth);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TryUpdateModelIncludesAllProperties_ByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_services, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
// Act
|
||||
var response = await client.GetStringAsync("http://localhost/TryUpdateModel/" +
|
||||
"GetUserAsync_IncludeAllByDefault" +
|
||||
"?id=123&RegisterationMonth=March&Key=123&UserName=SomeName");
|
||||
|
||||
// Assert
|
||||
var user = JsonConvert.DeserializeObject<User>(response);
|
||||
|
||||
// Should not update any not explicitly mentioned properties.
|
||||
Assert.Equal("SomeName", user.UserName);
|
||||
Assert.Equal(123, user.Key);
|
||||
|
||||
// Should Update all included properties.
|
||||
Assert.Equal("March", user.RegisterationMonth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace ModelBindingWebSite.Controllers
|
||||
{
|
||||
public class TryUpdateModelController : Controller
|
||||
{
|
||||
public async Task<Person> GetPerson()
|
||||
{
|
||||
// Person has a property of type Person. Only Top level should be updated.
|
||||
var person = new Person();
|
||||
await TryUpdateModelAsync(
|
||||
person,
|
||||
prefix: string.Empty,
|
||||
includeExpressions: m => m.Parent);
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
public async Task<User> GetUserAsync_IncludeAllByDefault(int id)
|
||||
{
|
||||
var user = GetUser(id);
|
||||
|
||||
await TryUpdateModelAsync<User>(user, prefix: string.Empty);
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<User> GetUserAsync_ExcludeSpecificProperties(int id)
|
||||
{
|
||||
var user = GetUser(id);
|
||||
await TryUpdateModelAsync(
|
||||
user,
|
||||
prefix: string.Empty,
|
||||
predicate:
|
||||
(context, modelName) =>
|
||||
!string.Equals(modelName, nameof(ModelBindingWebSite.User.Id), StringComparison.Ordinal) &&
|
||||
!string.Equals(modelName, nameof(ModelBindingWebSite.User.Key), StringComparison.Ordinal));
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<bool> CreateAndUpdateUser()
|
||||
{
|
||||
// don't update the id.
|
||||
var user = new User();
|
||||
return await TryUpdateModelAsync(user,
|
||||
prefix: string.Empty,
|
||||
includeExpressions: model => model.RegisterationMonth);
|
||||
}
|
||||
|
||||
public async Task<User> GetUserAsync_IncludeSpecificProperties(int id)
|
||||
{
|
||||
var user = GetUser(id);
|
||||
await TryUpdateModelAsync(user,
|
||||
prefix: string.Empty,
|
||||
includeExpressions: model => model.RegisterationMonth);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<bool> TryUpdateModelFails(int id)
|
||||
{
|
||||
var user = GetUser(id);
|
||||
return await TryUpdateModelAsync(user,
|
||||
prefix: string.Empty,
|
||||
valueProvider: new CustomValueProvider());
|
||||
}
|
||||
|
||||
public async Task<User> GetUserAsync_IncludeAndExcludeListNull(int id)
|
||||
{
|
||||
var user = GetUser(id);
|
||||
await TryUpdateModelAsync(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<User> GetUserAsync_IncludesAllSubProperties(int id)
|
||||
{
|
||||
var user = GetUser(id);
|
||||
|
||||
await TryUpdateModelAsync(user, prefix: string.Empty, includeExpressions: model => model.Address);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public async Task<User> GetUserAsync_WithChainedProperties(int id)
|
||||
{
|
||||
var user = GetUser(id);
|
||||
|
||||
// Since this is a chained expression this would throw
|
||||
await TryUpdateModelAsync(user, prefix: string.Empty, includeExpressions: model => model.Address.Country);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private User GetUser(int id)
|
||||
{
|
||||
return new User
|
||||
{
|
||||
UserName = "User_" + id,
|
||||
Id = id,
|
||||
Key = id + 20,
|
||||
};
|
||||
}
|
||||
|
||||
public class CustomValueProvider : IValueProvider
|
||||
{
|
||||
public Task<bool> ContainsPrefixAsync(string prefix)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public Task<ValueProviderResult> GetValueAsync(string key)
|
||||
{
|
||||
return Task.FromResult<ValueProviderResult>(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,5 +8,7 @@ namespace ModelBindingWebSite
|
|||
public int Street { get; set; }
|
||||
public string State { get; set; }
|
||||
public int Zip { get; set; }
|
||||
|
||||
public Country Country { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace ModelBindingWebSite
|
||||
{
|
||||
public class Country
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public City[] Cities { get; set; }
|
||||
|
||||
public int[] StateCodes { get; set; }
|
||||
}
|
||||
|
||||
public class City
|
||||
{
|
||||
public string CityName { get; set; }
|
||||
|
||||
public string CityCode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.DataAnnotations;
|
||||
|
||||
namespace ModelBindingWebSite
|
||||
{
|
||||
public class User
|
||||
{
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
public int Key { get; set; }
|
||||
|
||||
[Required]
|
||||
public string RegisterationMonth { get; set; }
|
||||
public string UserName { get; set; }
|
||||
|
||||
public Address Address { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue