Adding support for property level binding using IBinderMetadata and enabling FromXXX attributes to be decorated on properties.
This commit is contained in:
parent
27432d9453
commit
d00c7ef597
|
|
@ -52,38 +52,39 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodyBoundParameterCount = parameterMetadata.Count(
|
|
||||||
modelMetadata => modelMetadata.BinderMetadata is IFormatterBinderMetadata);
|
|
||||||
if (bodyBoundParameterCount > 1)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(Resources.MultipleBodyParametersAreNotAllowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||||
foreach (var parameter in parameterMetadata)
|
await PopulateArgumentAsync(actionBindingContext, actionArguments, parameterMetadata);
|
||||||
{
|
|
||||||
await PopulateArgumentAsync(actionBindingContext, actionArguments, parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return actionArguments;
|
return actionArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PopulateArgumentAsync(
|
private async Task PopulateArgumentAsync(
|
||||||
ActionBindingContext actionBindingContext,
|
ActionBindingContext actionBindingContext,
|
||||||
IDictionary<string, object> arguments,
|
IDictionary<string, object> arguments,
|
||||||
ModelMetadata modelMetadata)
|
IEnumerable<ModelMetadata> parameterMetadata)
|
||||||
{
|
{
|
||||||
|
var operationBindingContext = new OperationBindingContext
|
||||||
var parameterType = modelMetadata.ModelType;
|
|
||||||
var modelBindingContext = GetModelBindingContext(modelMetadata, actionBindingContext);
|
|
||||||
|
|
||||||
if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
|
|
||||||
{
|
{
|
||||||
arguments[modelMetadata.PropertyName] = modelBindingContext.Model;
|
ModelBinder = actionBindingContext.ModelBinder,
|
||||||
|
ValidatorProvider = actionBindingContext.ValidatorProvider,
|
||||||
|
MetadataProvider = actionBindingContext.MetadataProvider,
|
||||||
|
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
||||||
|
ValueProvider = actionBindingContext.ValueProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var parameter in parameterMetadata)
|
||||||
|
{
|
||||||
|
var parameterType = parameter.ModelType;
|
||||||
|
var modelBindingContext = GetModelBindingContext(parameter, actionBindingContext, operationBindingContext);
|
||||||
|
if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
|
||||||
|
{
|
||||||
|
arguments[parameter.PropertyName] = modelBindingContext.Model;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata, ActionBindingContext actionBindingContext)
|
internal static ModelBindingContext GetModelBindingContext(ModelMetadata modelMetadata,
|
||||||
|
ActionBindingContext actionBindingContext,
|
||||||
|
OperationBindingContext operationBindingContext)
|
||||||
{
|
{
|
||||||
Predicate<string> propertyFilter =
|
Predicate<string> propertyFilter =
|
||||||
propertyName => BindAttribute.IsPropertyAllowed(propertyName,
|
propertyName => BindAttribute.IsPropertyAllowed(propertyName,
|
||||||
|
|
@ -95,14 +96,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName,
|
ModelName = modelMetadata.ModelName ?? modelMetadata.PropertyName,
|
||||||
ModelMetadata = modelMetadata,
|
ModelMetadata = modelMetadata,
|
||||||
ModelState = actionBindingContext.ActionContext.ModelState,
|
ModelState = actionBindingContext.ActionContext.ModelState,
|
||||||
ModelBinder = actionBindingContext.ModelBinder,
|
|
||||||
ValidatorProvider = actionBindingContext.ValidatorProvider,
|
|
||||||
MetadataProvider = actionBindingContext.MetadataProvider,
|
|
||||||
HttpContext = actionBindingContext.ActionContext.HttpContext,
|
|
||||||
PropertyFilter = propertyFilter,
|
PropertyFilter = propertyFilter,
|
||||||
// Fallback only if there is no explicit model name set.
|
// Fallback only if there is no explicit model name set.
|
||||||
FallbackToEmptyPrefix = modelMetadata.ModelName == null,
|
FallbackToEmptyPrefix = modelMetadata.ModelName == null,
|
||||||
ValueProvider = actionBindingContext.ValueProvider,
|
ValueProvider = actionBindingContext.ValueProvider,
|
||||||
|
OperationBindingContext = operationBindingContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
return modelBindingContext;
|
return modelBindingContext;
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
if (formatter == null)
|
if (formatter == null)
|
||||||
{
|
{
|
||||||
var unsupportedContentType = Resources.FormatUnsupportedContentType(
|
var unsupportedContentType = Resources.FormatUnsupportedContentType(
|
||||||
bindingContext.HttpContext.Request.ContentType);
|
bindingContext.OperationBindingContext.HttpContext.Request.ContentType);
|
||||||
bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType);
|
bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType);
|
||||||
|
|
||||||
// Should always return true so that the model binding process ends here.
|
// Should always return true so that the model binding process ends here.
|
||||||
|
|
@ -51,8 +51,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
// Validate the deserialized object
|
// Validate the deserialized object
|
||||||
var validationContext = new ModelValidationContext(
|
var validationContext = new ModelValidationContext(
|
||||||
bindingContext.MetadataProvider,
|
bindingContext.OperationBindingContext.MetadataProvider,
|
||||||
bindingContext.ValidatorProvider,
|
bindingContext.OperationBindingContext.ValidatorProvider,
|
||||||
bindingContext.ModelState,
|
bindingContext.ModelState,
|
||||||
bindingContext.ModelMetadata,
|
bindingContext.ModelMetadata,
|
||||||
containerMetadata: null,
|
containerMetadata: null,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var modelMetadata = metadataProvider.GetMetadataForType(
|
var modelMetadata = metadataProvider.GetMetadataForType(
|
||||||
modelAccessor: null,
|
modelAccessor: null,
|
||||||
modelType: typeof(TModel));
|
modelType: typeof(TModel));
|
||||||
|
var operationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ModelBinder = modelBinder,
|
||||||
|
ValidatorProvider = validatorProvider,
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
HttpContext = httpContext
|
||||||
|
};
|
||||||
|
|
||||||
var modelBindingContext = new ModelBindingContext
|
var modelBindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
|
|
@ -46,12 +53,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
ModelName = prefix,
|
ModelName = prefix,
|
||||||
Model = model,
|
Model = model,
|
||||||
ModelState = modelState,
|
ModelState = modelState,
|
||||||
ModelBinder = modelBinder,
|
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
ValidatorProvider = validatorProvider,
|
|
||||||
MetadataProvider = metadataProvider,
|
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
HttpContext = httpContext
|
OperationBindingContext = operationBindingContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (await modelBinder.BindModelAsync(modelBindingContext))
|
if (await modelBinder.BindModelAsync(modelBindingContext))
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,6 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
private static readonly ResourceManager _resourceManager
|
private static readonly ResourceManager _resourceManager
|
||||||
= new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
= new ResourceManager("Microsoft.AspNet.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// More than one parameter is bound to the HTTP request's content.
|
|
||||||
/// </summary>
|
|
||||||
internal static string MultipleBodyParametersAreNotAllowed
|
|
||||||
{
|
|
||||||
get { return GetString("MultipleBodyParametersAreNotAllowed"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// More than one parameter is bound to the HTTP request's content.
|
|
||||||
/// </summary>
|
|
||||||
internal static string FormatMultipleBodyParametersAreNotAllowed()
|
|
||||||
{
|
|
||||||
return GetString("MultipleBodyParametersAreNotAllowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The provided anti-forgery token failed a custom data check.
|
/// The provided anti-forgery token failed a custom data check.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,6 @@
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="MultipleBodyParametersAreNotAllowed" xml:space="preserve">
|
|
||||||
<value>More than one parameter is bound to the HTTP request's content.</value>
|
|
||||||
</data>
|
|
||||||
<data name="AntiForgeryToken_AdditionalDataCheckFailed" xml:space="preserve">
|
<data name="AntiForgeryToken_AdditionalDataCheckFailed" xml:space="preserve">
|
||||||
<value>The provided anti-forgery token failed a custom data check.</value>
|
<value>The provided anti-forgery token failed a custom data check.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// This attribute is used on action parameters to indicate
|
/// This attribute is used on action parameters to indicate
|
||||||
/// they are bound from the body of the incoming request.
|
/// they are bound from the body of the incoming request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class FromBodyAttribute : Attribute, IFormatterBinderMetadata
|
public class FromBodyAttribute : Attribute, IFormatterBinderMetadata
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// This attribute is used on action parameters to indicate that
|
/// This attribute is used on action parameters to indicate that
|
||||||
/// they will be bound using form data of the incoming request.
|
/// they will be bound using form data of the incoming request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class FromFormAttribute : Attribute, IFormDataValueProviderMetadata
|
public class FromFormAttribute : Attribute, IFormDataValueProviderMetadata
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// This attribute is used on action parameters to indicate that
|
/// This attribute is used on action parameters to indicate that
|
||||||
/// they will be bound using query data of the incoming request.
|
/// they will be bound using query data of the incoming request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class FromQueryAttribute : Attribute, IQueryValueProviderMetadata
|
public class FromQueryAttribute : Attribute, IQueryValueProviderMetadata
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
/// This attribute is used on action parameters to indicate that
|
/// This attribute is used on action parameters to indicate that
|
||||||
/// they will be bound using route data of the incoming request.
|
/// they will be bound using route data of the incoming request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||||
public class FromRouteAttribute : Attribute, IRouteDataValueProviderMetadata
|
public class FromRouteAttribute : Attribute, IRouteDataValueProviderMetadata
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType == typeof(CancellationToken))
|
if (bindingContext.ModelType == typeof(CancellationToken))
|
||||||
{
|
{
|
||||||
bindingContext.Model = bindingContext.HttpContext.RequestAborted;
|
bindingContext.Model = bindingContext.OperationBindingContext.HttpContext.RequestAborted;
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var rawValueArray = RawValueToObjectArray(rawValue);
|
var rawValueArray = RawValueToObjectArray(rawValue);
|
||||||
foreach (var rawValueElement in rawValueArray)
|
foreach (var rawValueElement in rawValueArray)
|
||||||
{
|
{
|
||||||
var innerBindingContext = new ModelBindingContext(bindingContext)
|
var innerModelMetadata =
|
||||||
|
bindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(null, typeof(TElement));
|
||||||
|
var innerBindingContext = new ModelBindingContext(bindingContext,
|
||||||
|
bindingContext.ModelName,
|
||||||
|
innerModelMetadata)
|
||||||
{
|
{
|
||||||
ModelMetadata = bindingContext.MetadataProvider.GetMetadataForType(null, typeof(TElement)),
|
|
||||||
ModelName = bindingContext.ModelName,
|
|
||||||
ValueProvider = new CompositeValueProvider
|
ValueProvider = new CompositeValueProvider
|
||||||
{
|
{
|
||||||
// our temporary provider goes at the front of the list
|
// our temporary provider goes at the front of the list
|
||||||
|
|
@ -60,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
};
|
};
|
||||||
|
|
||||||
object boundValue = null;
|
object boundValue = null;
|
||||||
if (await bindingContext.ModelBinder.BindModelAsync(innerBindingContext))
|
if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(innerBindingContext))
|
||||||
{
|
{
|
||||||
boundValue = innerBindingContext.Model;
|
boundValue = innerBindingContext.Model;
|
||||||
bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
|
bindingContext.ValidationNode.ChildNodes.Add(innerBindingContext.ValidationNode);
|
||||||
|
|
@ -99,18 +101,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
foreach (var indexName in indexNames)
|
foreach (var indexName in indexNames)
|
||||||
{
|
{
|
||||||
var fullChildName = ModelBindingHelper.CreateIndexModelName(bindingContext.ModelName, indexName);
|
var fullChildName = ModelBindingHelper.CreateIndexModelName(bindingContext.ModelName, indexName);
|
||||||
var childBindingContext = new ModelBindingContext(bindingContext)
|
var childModelMetadata =
|
||||||
{
|
bindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(null, typeof(TElement));
|
||||||
ModelMetadata = bindingContext.MetadataProvider.GetMetadataForType(null, typeof(TElement)),
|
var childBindingContext = new ModelBindingContext(bindingContext, fullChildName, childModelMetadata);
|
||||||
ModelName = fullChildName
|
|
||||||
};
|
|
||||||
|
|
||||||
var didBind = false;
|
var didBind = false;
|
||||||
object boundValue = null;
|
object boundValue = null;
|
||||||
|
|
||||||
var modelType = bindingContext.ModelType;
|
var modelType = bindingContext.ModelType;
|
||||||
|
|
||||||
if (await bindingContext.ModelBinder.BindModelAsync(childBindingContext))
|
if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext))
|
||||||
{
|
{
|
||||||
didBind = true;
|
didBind = true;
|
||||||
boundValue = childBindingContext.Model;
|
boundValue = childBindingContext.Model;
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var dto = (ComplexModelDto)bindingContext.Model;
|
var dto = (ComplexModelDto)bindingContext.Model;
|
||||||
foreach (var propertyMetadata in dto.PropertyMetadata)
|
foreach (var propertyMetadata in dto.PropertyMetadata)
|
||||||
{
|
{
|
||||||
var propertyBindingContext = new ModelBindingContext(bindingContext)
|
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
|
||||||
{
|
propertyMetadata.PropertyName);
|
||||||
ModelMetadata = propertyMetadata,
|
|
||||||
ModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
|
var propertyBindingContext = new ModelBindingContext(bindingContext,
|
||||||
propertyMetadata.PropertyName)
|
propertyModelName,
|
||||||
};
|
propertyMetadata);
|
||||||
|
|
||||||
// bind and propagate the values
|
// bind and propagate the values
|
||||||
// If we can't bind, then leave the result missing (don't add a null).
|
// If we can't bind, then leave the result missing (don't add a null).
|
||||||
if (await bindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
|
if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
|
||||||
{
|
{
|
||||||
var result = new ComplexModelDtoResult(propertyBindingContext.Model,
|
var result = new ComplexModelDtoResult(propertyBindingContext.Model,
|
||||||
propertyBindingContext.ValidationNode);
|
propertyBindingContext.ValidationNode);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -79,8 +80,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
bindingContext.ModelName);
|
bindingContext.ModelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var validationContext = new ModelValidationContext(bindingContext.MetadataProvider,
|
var validationContext = new ModelValidationContext(bindingContext.OperationBindingContext.MetadataProvider,
|
||||||
bindingContext.ValidatorProvider,
|
bindingContext.OperationBindingContext.ValidatorProvider,
|
||||||
bindingContext.ModelState,
|
bindingContext.ModelState,
|
||||||
bindingContext.ModelMetadata,
|
bindingContext.ModelMetadata,
|
||||||
containerMetadata: null);
|
containerMetadata: null);
|
||||||
|
|
@ -88,6 +89,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
|
newBindingContext.ValidationNode.Validate(validationContext, parentNode: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindingContext.OperationBindingContext.BodyBindingState =
|
||||||
|
newBindingContext.OperationBindingContext.BodyBindingState;
|
||||||
bindingContext.Model = newBindingContext.Model;
|
bindingContext.Model = newBindingContext.Model;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -128,10 +131,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelName = modelName,
|
ModelName = modelName,
|
||||||
ModelState = oldBindingContext.ModelState,
|
ModelState = oldBindingContext.ModelState,
|
||||||
ValueProvider = oldBindingContext.ValueProvider,
|
ValueProvider = oldBindingContext.ValueProvider,
|
||||||
ValidatorProvider = oldBindingContext.ValidatorProvider,
|
OperationBindingContext = oldBindingContext.OperationBindingContext,
|
||||||
MetadataProvider = oldBindingContext.MetadataProvider,
|
|
||||||
ModelBinder = oldBindingContext.ModelBinder,
|
|
||||||
HttpContext = oldBindingContext.HttpContext,
|
|
||||||
PropertyFilter = oldBindingContext.PropertyFilter,
|
PropertyFilter = oldBindingContext.PropertyFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -141,11 +141,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
|
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newBindingContext.OperationBindingContext.BodyBindingState = GetBodyBindingState(oldBindingContext);
|
||||||
|
|
||||||
// look at the value providers and see if they need to be restricted.
|
// look at the value providers and see if they need to be restricted.
|
||||||
var metadata = oldBindingContext.ModelMetadata.BinderMetadata as IValueProviderMetadata;
|
var metadata = oldBindingContext.ModelMetadata.BinderMetadata as IValueProviderMetadata;
|
||||||
if (metadata != null)
|
if (metadata != null)
|
||||||
{
|
{
|
||||||
var valueProvider = oldBindingContext.ValueProvider as IMetadataAwareValueProvider;
|
// ValueProvider property might contain a filtered list of value providers.
|
||||||
|
// While deciding to bind a particular property which is annotated with a IValueProviderMetadata,
|
||||||
|
// instead of refiltering an already filtered list, we need to filter value providers from a global list
|
||||||
|
// of all value providers. This is so that every artifact that is explicitly marked using an
|
||||||
|
// IValueProviderMetadata can restrict model binding to only use value providers which support this
|
||||||
|
// IValueProviderMetadata.
|
||||||
|
var valueProvider = oldBindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
|
||||||
if (valueProvider != null)
|
if (valueProvider != null)
|
||||||
{
|
{
|
||||||
newBindingContext.ValueProvider = valueProvider.Filter(metadata);
|
newBindingContext.ValueProvider = valueProvider.Filter(metadata);
|
||||||
|
|
@ -154,5 +162,36 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
return newBindingContext;
|
return newBindingContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BodyBindingState GetBodyBindingState(ModelBindingContext oldBindingContext)
|
||||||
|
{
|
||||||
|
var binderMetadata = oldBindingContext.ModelMetadata.BinderMetadata;
|
||||||
|
var newIsFormatterBasedMetadataFound = binderMetadata is IFormatterBinderMetadata;
|
||||||
|
var newIsFormBasedMetadataFound = binderMetadata is IFormDataValueProviderMetadata;
|
||||||
|
var currentModelNeedsToReadBody = newIsFormatterBasedMetadataFound || newIsFormBasedMetadataFound;
|
||||||
|
var oldState = oldBindingContext.OperationBindingContext.BodyBindingState;
|
||||||
|
|
||||||
|
// We need to throw if there are multiple models which can cause body to be read multiple times.
|
||||||
|
// Reading form data multiple times is ok since we cache form data. For the models marked to read using
|
||||||
|
// formatters, multiple reads are not allowed.
|
||||||
|
if (oldState == BodyBindingState.FormatterBased && currentModelNeedsToReadBody ||
|
||||||
|
oldState == BodyBindingState.FormBased && newIsFormatterBasedMetadataFound)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.MultipleBodyParametersOrPropertiesAreNotAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = oldBindingContext.OperationBindingContext.BodyBindingState;
|
||||||
|
if (newIsFormatterBasedMetadataFound)
|
||||||
|
{
|
||||||
|
state = BodyBindingState.FormatterBased;
|
||||||
|
}
|
||||||
|
else if (newIsFormBasedMetadataFound && oldState != BodyBindingState.FormatterBased)
|
||||||
|
{
|
||||||
|
// Only update the model binding state if we have not discovered formatter based state already.
|
||||||
|
state = BodyBindingState.FormBased;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
_activator = activator;
|
_activator = activator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var binderType = ResolveBinderType(bindingContext.ModelType);
|
var binderType = ResolveBinderType(bindingContext.ModelType);
|
||||||
if (binderType != null)
|
if (binderType != null)
|
||||||
{
|
{
|
||||||
var binder = (IModelBinder)_activator.CreateInstance(_serviceProvider, binderType);
|
var binder = (IModelBinder)_activator.CreateInstance(_serviceProvider, binderType);
|
||||||
return binder.BindModelAsync(bindingContext);
|
await binder.BindModelAsync(bindingContext);
|
||||||
|
|
||||||
|
// Was able to resolve a binder type, hence we should tell the model binding system to return
|
||||||
|
// true so that none of the other model binders participate.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(false);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type ResolveBinderType(Type modelType)
|
private static Type ResolveBinderType(Type modelType)
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
internal async Task<BindResult<TModel>> TryBindStrongModel<TModel>(ModelBindingContext parentBindingContext,
|
internal async Task<BindResult<TModel>> TryBindStrongModel<TModel>(ModelBindingContext parentBindingContext,
|
||||||
string propertyName)
|
string propertyName)
|
||||||
{
|
{
|
||||||
var propertyBindingContext = new ModelBindingContext(parentBindingContext)
|
var propertyModelMetadata =
|
||||||
{
|
parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(modelAccessor: null,
|
||||||
ModelMetadata = parentBindingContext.MetadataProvider.GetMetadataForType(modelAccessor: null,
|
modelType: typeof(TModel));
|
||||||
modelType: typeof(TModel)),
|
var propertyModelName =
|
||||||
ModelName = ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName)
|
ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName);
|
||||||
};
|
var propertyBindingContext =
|
||||||
|
new ModelBindingContext(parentBindingContext, propertyModelName, propertyModelMetadata);
|
||||||
|
|
||||||
if (await propertyBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
|
if (await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext))
|
||||||
{
|
{
|
||||||
var untypedModel = propertyBindingContext.Model;
|
var untypedModel = propertyBindingContext.Model;
|
||||||
var model = ModelBindingHelper.CastOrDefault<TModel>(untypedModel);
|
var model = ModelBindingHelper.CastOrDefault<TModel>(untypedModel);
|
||||||
|
|
|
||||||
|
|
@ -16,35 +16,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
public virtual async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
public virtual async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
ModelBindingHelper.ValidateBindingContext(bindingContext);
|
||||||
|
|
||||||
if (!CanBindType(bindingContext.ModelType))
|
if (!CanBindType(bindingContext.ModelType))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var topLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
var mutableObjectBinderContext = new MutableObjectBinderContext()
|
||||||
var isThereAnExplicitAlias = bindingContext.ModelMetadata.ModelName != null;
|
{
|
||||||
|
ModelBindingContext = bindingContext,
|
||||||
|
PropertyMetadata = GetMetadataForProperties(bindingContext),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!(await CanCreateModel(mutableObjectBinderContext)))
|
||||||
// The first check is necessary because if we fallback to empty prefix, we do not want to depend
|
|
||||||
// on a value provider to provide a value for empty prefix.
|
|
||||||
var containsPrefix = (bindingContext.ModelName == string.Empty && topLevelObject) ||
|
|
||||||
await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName);
|
|
||||||
|
|
||||||
// Always create the model if
|
|
||||||
// 1. It is a top level object and the model name is empty.
|
|
||||||
// 2. There is a value provider which can provide value for the model name.
|
|
||||||
// 3. There is an explicit alias provided by the user and it is a top level object.
|
|
||||||
// The reson we depend on explicit alias is that otherwise we want the FallToEmptyPrefix codepath
|
|
||||||
// to kick in so that empty prefix values could be bound.
|
|
||||||
if (!containsPrefix && !(isThereAnExplicitAlias && topLevelObject))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureModel(bindingContext);
|
EnsureModel(bindingContext);
|
||||||
var propertyMetadatas = GetMetadataForProperties(bindingContext).ToArray();
|
var dto = await CreateAndPopulateDto(bindingContext, mutableObjectBinderContext.PropertyMetadata);
|
||||||
var dto = CreateAndPopulateDto(bindingContext, propertyMetadatas);
|
|
||||||
|
|
||||||
// post-processing, e.g. property setters and hooking up validation
|
// post-processing, e.g. property setters and hooking up validation
|
||||||
ProcessDto(bindingContext, dto);
|
ProcessDto(bindingContext, dto);
|
||||||
|
|
@ -58,6 +47,121 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return CanUpdatePropertyInternal(propertyMetadata);
|
return CanUpdatePropertyInternal(propertyMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task<bool> CanCreateModel(MutableObjectBinderContext context)
|
||||||
|
{
|
||||||
|
var bindingContext = context.ModelBindingContext;
|
||||||
|
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
|
||||||
|
var isThereAnExplicitAlias = bindingContext.ModelMetadata.ModelName != null;
|
||||||
|
|
||||||
|
// The fact that this has reached here,
|
||||||
|
// it is a complex object which was not directly bound by any previous model binders.
|
||||||
|
// Check if this was supposed to be handled by a non value provider based binder.
|
||||||
|
// if it was then it should be not be bound using mutable object binder.
|
||||||
|
// This check would prevent it from recursing in if a model contains a property of its own type.
|
||||||
|
// We skip this check if it is a top level object because we want to always evaluate
|
||||||
|
// the creation of top level object (this is also required for ModelBinderAttribute to work.)
|
||||||
|
if (!isTopLevelObject &&
|
||||||
|
bindingContext.ModelMetadata.BinderMetadata != null &&
|
||||||
|
!(bindingContext.ModelMetadata.BinderMetadata is IValueProviderMetadata))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the object if :
|
||||||
|
// 1. It is a top level model with an explicit user supplied prefix.
|
||||||
|
// In this case since it will never fallback to empty prefix, we need to create the model here.
|
||||||
|
if (isTopLevelObject && isThereAnExplicitAlias)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. It is a top level object and there is no model name ( Fallback to empty prefix case ).
|
||||||
|
// This is necessary as we do not want to depend on a value provider to contain an empty prefix.
|
||||||
|
if (isTopLevelObject && bindingContext.ModelName == string.Empty)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. The model name is not prefixed and a value provider can directly provide a value for the model name.
|
||||||
|
// The fact that it is not prefixed means that the containsPrefixAsync call checks for the exact model name
|
||||||
|
// instead of doing a prefix match.
|
||||||
|
if (!bindingContext.ModelName.Contains(".") &&
|
||||||
|
await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Any of the model properties can be bound using a value provider.
|
||||||
|
if (await CanValueBindAnyModelProperties(context))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> CanValueBindAnyModelProperties(MutableObjectBinderContext context)
|
||||||
|
{
|
||||||
|
// We need to enumerate the non marked properties and properties marked with IValueProviderMetadata
|
||||||
|
// instead of checking bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName)
|
||||||
|
// because there can be a case
|
||||||
|
// where a value provider might be willing to provide a marked property, which might never be bound.
|
||||||
|
// For example if person.Name is marked with FromQuery, and FormValueProvider has a key person.Name, and the
|
||||||
|
// QueryValueProvider does not, we do not want to create Person.
|
||||||
|
var isAnyPropertyEnabledForValueProviderBasedBinding = false;
|
||||||
|
foreach (var propertyMetadata in context.PropertyMetadata)
|
||||||
|
{
|
||||||
|
// This check will skip properties which are marked explicitly using a non value binder.
|
||||||
|
if (propertyMetadata.BinderMetadata == null ||
|
||||||
|
propertyMetadata.BinderMetadata is IValueProviderMetadata)
|
||||||
|
{
|
||||||
|
isAnyPropertyEnabledForValueProviderBasedBinding = true;
|
||||||
|
|
||||||
|
// If any property can return a true value.
|
||||||
|
if (await CanBindValue(context.ModelBindingContext, propertyMetadata))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAnyPropertyEnabledForValueProviderBasedBinding)
|
||||||
|
{
|
||||||
|
// Either there are no properties or all the properties are marked as
|
||||||
|
// a non value provider based marker.
|
||||||
|
// This would be the case when the model has all its properties annotated with
|
||||||
|
// a IBinderMetadata. We want to be able to create such a model.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> CanBindValue(ModelBindingContext bindingContext, ModelMetadata metadata)
|
||||||
|
{
|
||||||
|
var valueProvider = bindingContext.ValueProvider;
|
||||||
|
var valueProviderMetadata = metadata.BinderMetadata as IValueProviderMetadata;
|
||||||
|
if (valueProviderMetadata != null)
|
||||||
|
{
|
||||||
|
// if there is a binder metadata and since the property can be bound using a value provider.
|
||||||
|
var metadataAwareValueProvider = bindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
|
||||||
|
if (metadataAwareValueProvider != null)
|
||||||
|
{
|
||||||
|
valueProvider = metadataAwareValueProvider.Filter(valueProviderMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName,
|
||||||
|
metadata.PropertyName);
|
||||||
|
|
||||||
|
if (await valueProvider.ContainsPrefixAsync(propertyModelName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool CanBindType(Type modelType)
|
private static bool CanBindType(Type modelType)
|
||||||
{
|
{
|
||||||
// Simple types cannot use this binder
|
// Simple types cannot use this binder
|
||||||
|
|
@ -106,19 +210,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComplexModelDto CreateAndPopulateDto(ModelBindingContext bindingContext,
|
private async Task<ComplexModelDto> CreateAndPopulateDto(ModelBindingContext bindingContext,
|
||||||
IEnumerable<ModelMetadata> propertyMetadatas)
|
IEnumerable<ModelMetadata> propertyMetadatas)
|
||||||
{
|
{
|
||||||
// create a DTO and call into the DTO binder
|
// create a DTO and call into the DTO binder
|
||||||
var originalDto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
|
var originalDto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
|
||||||
var dtoBindingContext = new ModelBindingContext(bindingContext)
|
var complexModelDtoMetadata =
|
||||||
{
|
bindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(() => originalDto,
|
||||||
ModelMetadata = bindingContext.MetadataProvider.GetMetadataForType(() => originalDto,
|
typeof(ComplexModelDto));
|
||||||
typeof(ComplexModelDto)),
|
var dtoBindingContext =
|
||||||
ModelName = bindingContext.ModelName
|
new ModelBindingContext(bindingContext, bindingContext.ModelName, complexModelDtoMetadata);
|
||||||
};
|
|
||||||
|
|
||||||
bindingContext.ModelBinder.BindModelAsync(dtoBindingContext);
|
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(dtoBindingContext);
|
||||||
return (ComplexModelDto)dtoBindingContext.Model;
|
return (ComplexModelDto)dtoBindingContext.Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,8 +268,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
protected virtual IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
var validationInfo = GetPropertyValidationInfo(bindingContext);
|
||||||
var propertyTypeMetadata = bindingContext.MetadataProvider
|
var propertyTypeMetadata = bindingContext.OperationBindingContext
|
||||||
.GetMetadataForType(null, bindingContext.ModelType);
|
.MetadataProvider
|
||||||
|
.GetMetadataForType(null, bindingContext.ModelType);
|
||||||
Predicate<string> newPropertyFilter =
|
Predicate<string> newPropertyFilter =
|
||||||
propertyName => bindingContext.PropertyFilter(propertyName) &&
|
propertyName => bindingContext.PropertyFilter(propertyName) &&
|
||||||
BindAttribute.IsPropertyAllowed(
|
BindAttribute.IsPropertyAllowed(
|
||||||
|
|
@ -198,7 +302,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
var propertyName = property.Name;
|
var propertyName = property.Name;
|
||||||
var propertyMetadata = bindingContext.PropertyMetadata[propertyName];
|
var propertyMetadata = bindingContext.PropertyMetadata[propertyName];
|
||||||
var requiredValidator = bindingContext.ValidatorProvider
|
var requiredValidator = bindingContext.OperationBindingContext
|
||||||
|
.ValidatorProvider
|
||||||
.GetValidators(propertyMetadata)
|
.GetValidators(propertyMetadata)
|
||||||
.FirstOrDefault(v => v != null && v.IsRequired);
|
.FirstOrDefault(v => v != null && v.IsRequired);
|
||||||
if (requiredValidator != null)
|
if (requiredValidator != null)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// 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 Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
public class MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
public ModelBindingContext ModelBindingContext { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<ModelMetadata> PropertyMetadata { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents state of models which are bound using body.
|
||||||
|
/// </summary>
|
||||||
|
public enum BodyBindingState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents if there has been no metadata found which needs to read the body during the current
|
||||||
|
/// model binding process.
|
||||||
|
/// </summary>
|
||||||
|
NotBodyBased,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents if there is a <see cref="IFormatterBinderMetadata"/> that
|
||||||
|
/// has been found during the current model binding process.
|
||||||
|
/// </summary>
|
||||||
|
FormatterBased,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents if there is a <see cref = "IFormDataValueProviderMetadata" /> that
|
||||||
|
/// has been found during the current model binding process.
|
||||||
|
/// </summary>
|
||||||
|
FormBased
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -83,7 +83,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
parameterName,
|
parameterName,
|
||||||
binderMetadata);
|
binderMetadata);
|
||||||
|
|
||||||
return CreateMetadataFromPrototype(parameterInfo.Prototype, modelAccessor);
|
var metadata = CreateMetadataFromPrototype(parameterInfo.Prototype, modelAccessor);
|
||||||
|
|
||||||
|
|
||||||
|
// If there is no metadata associated with the parameter itself get it from the type.
|
||||||
|
if (metadata != null && metadata.BinderMetadata == null)
|
||||||
|
{
|
||||||
|
var typeInfo = GetTypeInformation(parameter.ParameterType);
|
||||||
|
metadata.BinderMetadata = typeInfo.Prototype.BinderMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
|
private IEnumerable<ModelMetadata> GetMetadataForPropertiesCore(object container, Type containerType)
|
||||||
|
|
@ -115,6 +125,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
metadata.IsReadOnly = true;
|
metadata.IsReadOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to update the property after the prototype creation because otherwise
|
||||||
|
// if the property type is same as the containing type, it would cause infinite recursion.
|
||||||
|
// If there is no metadata associated with the property itself get it from the type.
|
||||||
|
if (metadata != null && metadata.BinderMetadata == null)
|
||||||
|
{
|
||||||
|
if (propertyInfo.Prototype != null)
|
||||||
|
{
|
||||||
|
var typeInfo = GetTypeInformation(propertyInfo.Prototype.ModelType);
|
||||||
|
metadata.BinderMetadata = typeInfo.Prototype.BinderMetadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,25 +29,32 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ModelBindingContext"/> class using the
|
/// Initializes a new instance of the <see cref="ModelBindingContext"/> class using the
|
||||||
/// <param name="bindingContext" />.
|
/// <paramref name="bindingContext" />.
|
||||||
// </summary>
|
/// </summary>
|
||||||
|
/// <param name="bindingContext">Existing <see cref="ModelBindingContext"/>.</param>
|
||||||
|
/// <param name="modelName">Model name of associated with the new <see cref="ModelBindingContext"/>.</param>
|
||||||
|
/// <param name="modelMetadata">Model metadata of associated with the new <see cref="ModelBindingContext"/>.
|
||||||
|
/// </param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This constructor copies certain values that won't change between parent and child objects,
|
/// This constructor copies certain values that won't change between parent and child objects,
|
||||||
/// e.g. ValueProvider, ModelState
|
/// e.g. ValueProvider, ModelState
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public ModelBindingContext(ModelBindingContext bindingContext)
|
public ModelBindingContext([NotNull] ModelBindingContext bindingContext,
|
||||||
|
[NotNull] string modelName,
|
||||||
|
[NotNull] ModelMetadata modelMetadata)
|
||||||
{
|
{
|
||||||
if (bindingContext != null)
|
ModelName = modelName;
|
||||||
{
|
ModelMetadata = modelMetadata;
|
||||||
ModelState = bindingContext.ModelState;
|
ModelState = bindingContext.ModelState;
|
||||||
ValueProvider = bindingContext.ValueProvider;
|
ValueProvider = bindingContext.ValueProvider;
|
||||||
MetadataProvider = bindingContext.MetadataProvider;
|
OperationBindingContext = bindingContext.OperationBindingContext;
|
||||||
ModelBinder = bindingContext.ModelBinder;
|
|
||||||
ValidatorProvider = bindingContext.ValidatorProvider;
|
|
||||||
HttpContext = bindingContext.HttpContext;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the <see cref="OperationBindingContext"/> associated with this context.
|
||||||
|
/// </summary>
|
||||||
|
public OperationBindingContext OperationBindingContext { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the model associated with this context.
|
/// Gets or sets the model associated with this context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -129,32 +136,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool FallbackToEmptyPrefix { get; set; }
|
public bool FallbackToEmptyPrefix { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="HttpContext"/> for the current request.
|
|
||||||
/// </summary>
|
|
||||||
public HttpContext HttpContext { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="IValueProvider"/> associated with this context.
|
/// Gets or sets the <see cref="IValueProvider"/> associated with this context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IValueProvider ValueProvider { get; set; }
|
public IValueProvider ValueProvider { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="IModelBinder"/> associated with this context.
|
|
||||||
/// </summary>
|
|
||||||
public IModelBinder ModelBinder { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="IModelMetadataProvider"/> associated with this context.
|
|
||||||
/// </summary>
|
|
||||||
public IModelMetadataProvider MetadataProvider { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="IModelValidatorProvider"/> instance used for model validation with this
|
|
||||||
/// context.
|
|
||||||
/// </summary>
|
|
||||||
public IModelValidatorProvider ValidatorProvider { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a dictionary of property name to <see cref="ModelMetadata"/> instances for
|
/// Gets a dictionary of property name to <see cref="ModelMetadata"/> instances for
|
||||||
/// <see cref="ModelMetadata.Properties"/>
|
/// <see cref="ModelMetadata.Properties"/>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
// 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 Microsoft.AspNet.Http;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A context that contains information specific to the current request and the action whose parameters
|
||||||
|
/// are being model bound.
|
||||||
|
/// </summary>
|
||||||
|
public class OperationBindingContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents if there has been a body bound model found during the current model binding process.
|
||||||
|
/// </summary>
|
||||||
|
public BodyBindingState BodyBindingState { get; set; } = BodyBindingState.NotBodyBased;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="HttpContext"/> for the current request.
|
||||||
|
/// </summary>
|
||||||
|
public HttpContext HttpContext { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets unaltered value provider collection.
|
||||||
|
/// Value providers can be filtered by specific model binders.
|
||||||
|
/// </summary>
|
||||||
|
public IValueProvider ValueProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="IModelBinder"/> associated with this context.
|
||||||
|
/// </summary>
|
||||||
|
public IModelBinder ModelBinder { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="IModelMetadataProvider"/> associated with this context.
|
||||||
|
/// </summary>
|
||||||
|
public IModelMetadataProvider MetadataProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the <see cref="IModelValidatorProvider"/> instance used for model validation with this
|
||||||
|
/// context.
|
||||||
|
/// </summary>
|
||||||
|
public IModelValidatorProvider ValidatorProvider { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
private static readonly ResourceManager _resourceManager
|
private static readonly ResourceManager _resourceManager
|
||||||
= new ResourceManager("Microsoft.AspNet.Mvc.ModelBinding.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
= new ResourceManager("Microsoft.AspNet.Mvc.ModelBinding.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// More than one parameter and/or property is bound to the HTTP request's content.
|
||||||
|
/// </summary>
|
||||||
|
internal static string MultipleBodyParametersOrPropertiesAreNotAllowed
|
||||||
|
{
|
||||||
|
get { return GetString("MultipleBodyParametersOrPropertiesAreNotAllowed"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// More than one parameter and/or property is bound to the HTTP request's content.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatMultipleBodyParametersOrPropertiesAreNotAllowed()
|
||||||
|
{
|
||||||
|
return GetString("MultipleBodyParametersOrPropertiesAreNotAllowed");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Value cannot be null or empty.
|
/// Value cannot be null or empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,9 @@
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<data name="MultipleBodyParametersOrPropertiesAreNotAllowed" xml:space="preserve">
|
||||||
|
<value>More than one parameter and/or property is bound to the HTTP request's content.</value>
|
||||||
|
</data>
|
||||||
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
|
||||||
<value>Value cannot be null or empty.</value>
|
<value>Value cannot be null or empty.</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public ModelValidationContext([NotNull] ModelBindingContext bindingContext,
|
public ModelValidationContext([NotNull] ModelBindingContext bindingContext,
|
||||||
[NotNull] ModelMetadata metadata)
|
[NotNull] ModelMetadata metadata)
|
||||||
: this(bindingContext.MetadataProvider,
|
: this(bindingContext.OperationBindingContext.MetadataProvider,
|
||||||
bindingContext.ValidatorProvider,
|
bindingContext.OperationBindingContext.ValidatorProvider,
|
||||||
bindingContext.ModelState,
|
bindingContext.ModelState,
|
||||||
metadata,
|
metadata,
|
||||||
bindingContext.ModelMetadata)
|
bindingContext.ModelMetadata)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||||
{
|
{
|
||||||
if (bindingContext.ModelType == typeof(HttpRequestMessage))
|
if (bindingContext.ModelType == typeof(HttpRequestMessage))
|
||||||
{
|
{
|
||||||
bindingContext.Model = bindingContext.HttpContext.GetHttpRequestMessage();
|
bindingContext.Model = bindingContext.OperationBindingContext.HttpContext.GetHttpRequestMessage();
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// Arrange
|
// Arrange
|
||||||
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
|
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
|
||||||
bindingContext.ModelMetadata.BinderMetadata = Mock.Of<IFormatterBinderMetadata>();
|
bindingContext.ModelMetadata.BinderMetadata = Mock.Of<IFormatterBinderMetadata>();
|
||||||
var binder = bindingContext.ModelBinder;
|
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
|
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
|
||||||
bindingContext.ModelMetadata.BinderMetadata = useBody ? Mock.Of<IFormatterBinderMetadata>() :
|
bindingContext.ModelMetadata.BinderMetadata = useBody ? Mock.Of<IFormatterBinderMetadata>() :
|
||||||
Mock.Of<IBinderMetadata>();
|
Mock.Of<IBinderMetadata>();
|
||||||
var binder = bindingContext.ModelBinder;
|
var binder = bindingContext.OperationBindingContext.ModelBinder;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var binderResult = await binder.BindModelAsync(bindingContext);
|
var binderResult = await binder.BindModelAsync(bindingContext);
|
||||||
|
|
@ -82,15 +82,20 @@ namespace Microsoft.AspNet.Mvc
|
||||||
private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter)
|
private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter)
|
||||||
{
|
{
|
||||||
var metadataProvider = new EmptyModelMetadataProvider();
|
var metadataProvider = new EmptyModelMetadataProvider();
|
||||||
|
var operationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ModelBinder = GetBodyBinder(inputFormatter, null),
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
HttpContext = new DefaultHttpContext(),
|
||||||
|
};
|
||||||
|
|
||||||
ModelBindingContext bindingContext = new ModelBindingContext
|
ModelBindingContext bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = Mock.Of<IValueProvider>(),
|
ValueProvider = Mock.Of<IValueProvider>(),
|
||||||
ModelBinder = GetBodyBinder(inputFormatter, null),
|
ModelState = new ModelStateDictionary(),
|
||||||
MetadataProvider = metadataProvider,
|
OperationBindingContext = operationBindingContext,
|
||||||
HttpContext = new DefaultHttpContext(),
|
|
||||||
ModelState = new ModelStateDictionary()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
Mock.Of<IModelValidatorProvider>());
|
Mock.Of<IModelValidatorProvider>());
|
||||||
// Act
|
// Act
|
||||||
var context = DefaultControllerActionArgumentBinder
|
var context = DefaultControllerActionArgumentBinder
|
||||||
.GetModelBindingContext(modelMetadata, actionBindingContext);
|
.GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
||||||
|
|
@ -106,63 +106,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
Mock.Of<IModelValidatorProvider>());
|
Mock.Of<IModelValidatorProvider>());
|
||||||
// Act
|
// Act
|
||||||
var context = DefaultControllerActionArgumentBinder
|
var context = DefaultControllerActionArgumentBinder
|
||||||
.GetModelBindingContext(modelMetadata, actionBindingContext);
|
.GetModelBindingContext(modelMetadata, actionBindingContext, Mock.Of<OperationBindingContext>());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
|
||||||
Assert.Equal(expectedModelName, context.ModelName);
|
Assert.Equal(expectedModelName, context.ModelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Parameters_WithMultipleFromBody_Throw()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var actionDescriptor = new ControllerActionDescriptor
|
|
||||||
{
|
|
||||||
MethodInfo = typeof(TestController).GetMethod("ActionWithTwoBodyParam"),
|
|
||||||
Parameters = new List<ParameterDescriptor>
|
|
||||||
{
|
|
||||||
new ParameterDescriptor
|
|
||||||
{
|
|
||||||
Name = "bodyParam",
|
|
||||||
ParameterType = typeof(Person),
|
|
||||||
},
|
|
||||||
new ParameterDescriptor
|
|
||||||
{
|
|
||||||
Name = "bodyParam1",
|
|
||||||
ParameterType = typeof(Person),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var binder = new Mock<IModelBinder>();
|
|
||||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
|
||||||
var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()),
|
|
||||||
actionDescriptor);
|
|
||||||
actionContext.Controller = Mock.Of<object>();
|
|
||||||
var bindingContext = new ActionBindingContext(actionContext,
|
|
||||||
metadataProvider,
|
|
||||||
Mock.Of<IModelBinder>(),
|
|
||||||
Mock.Of<IValueProvider>(),
|
|
||||||
Mock.Of<IInputFormatterSelector>(),
|
|
||||||
Mock.Of<IModelValidatorProvider>());
|
|
||||||
|
|
||||||
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
|
|
||||||
actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
|
|
||||||
.Returns(Task.FromResult(bindingContext));
|
|
||||||
|
|
||||||
var invoker = new DefaultControllerActionArgumentBinder(
|
|
||||||
actionBindingContextProvider.Object);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
||||||
() => invoker.GetActionArgumentsAsync(actionContext));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("More than one parameter is bound to the HTTP request's content.",
|
|
||||||
ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsFalse()
|
public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsFalse()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
using Microsoft.AspNet.TestHost;
|
using Microsoft.AspNet.TestHost;
|
||||||
|
|
@ -48,6 +49,32 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(expectedValue, await response.Content.ReadAsStringAsync());
|
Assert.Equal(expectedValue, await response.Content.ReadAsStringAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TryUpdateModel_WithAPropertyFromBody()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// the name would be of customer.Department.Name
|
||||||
|
// and not for the top level customer object.
|
||||||
|
var input = "{\"Name\":\"RandomDepartment\"}";
|
||||||
|
var content = new StringContent(input, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsync("http://localhost/Home/GetCustomer?Id=1234", content);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var customer = JsonConvert.DeserializeObject<Customer>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.NotNull(customer.Department);
|
||||||
|
Assert.Equal("RandomDepartment", customer.Department.Name);
|
||||||
|
Assert.Equal(1234, customer.Id);
|
||||||
|
Assert.Equal(25, customer.Age);
|
||||||
|
Assert.Equal("dummy", customer.Name);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MultipleParametersMarkedWithFromBody_Throws()
|
public async Task MultipleParametersMarkedWithFromBody_Throws()
|
||||||
{
|
{
|
||||||
|
|
@ -57,12 +84,280 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
client.GetAsync("http://localhost/MultipleParametersFromBody/MultipleParametersFromBodyThrows"));
|
client.GetAsync("http://localhost/FromAttributes/FromBodyParametersThrows"));
|
||||||
|
|
||||||
Assert.Equal("More than one parameter is bound to the HTTP request's content.",
|
Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.",
|
||||||
ex.Message);
|
ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MultipleParameterAndPropertiesMarkedWithFromBody_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
client.GetAsync("http://localhost/FromAttributes/FromBodyParameterAndPropertyThrows"));
|
||||||
|
|
||||||
|
Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MultipleParametersMarkedWith_FromFormAndFromBody_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
client.GetAsync("http://localhost/FromAttributes/FormAndBody_AsParameters_Throws"));
|
||||||
|
|
||||||
|
Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MultipleParameterAndPropertiesMarkedWith_FromFormAndFromBody_Throws()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||||
|
client.GetAsync("http://localhost/FromAttributes/FormAndBody_Throws"));
|
||||||
|
|
||||||
|
Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.",
|
||||||
|
ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanBind_MultipleParameters_UsingFromForm()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post,
|
||||||
|
"http://localhost/FromAttributes/MultipleFromFormParameters");
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string,string>("homeAddress.Street", "1"),
|
||||||
|
new KeyValuePair<string,string>("homeAddress.State", "WA_Form_Home"),
|
||||||
|
new KeyValuePair<string,string>("homeAddress.Zip", "2"),
|
||||||
|
new KeyValuePair<string,string>("officeAddress.Street", "3"),
|
||||||
|
new KeyValuePair<string,string>("officeAddress.State", "WA_Form_Office"),
|
||||||
|
new KeyValuePair<string,string>("officeAddress.Zip", "4"),
|
||||||
|
};
|
||||||
|
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var user = JsonConvert.DeserializeObject<User_FromForm>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
Assert.Equal("WA_Form_Home", user.HomeAddress.State);
|
||||||
|
Assert.Equal(1, user.HomeAddress.Street);
|
||||||
|
Assert.Equal(2, user.HomeAddress.Zip);
|
||||||
|
|
||||||
|
Assert.Equal("WA_Form_Office", user.OfficeAddress.State);
|
||||||
|
Assert.Equal(3, user.OfficeAddress.Street);
|
||||||
|
Assert.Equal(4, user.OfficeAddress.Zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanBind_MultipleProperties_UsingFromForm()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post,
|
||||||
|
"http://localhost/FromAttributes/MultipleFromFormParameterAndProperty");
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string,string>("Street", "1"),
|
||||||
|
new KeyValuePair<string,string>("State", "WA_Form_Home"),
|
||||||
|
new KeyValuePair<string,string>("Zip", "2"),
|
||||||
|
new KeyValuePair<string,string>("officeAddress.Street", "3"),
|
||||||
|
new KeyValuePair<string,string>("officeAddress.State", "WA_Form_Office"),
|
||||||
|
new KeyValuePair<string,string>("officeAddress.Zip", "4"),
|
||||||
|
};
|
||||||
|
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var user = JsonConvert.DeserializeObject<User_FromForm>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
Assert.Equal("WA_Form_Home", user.HomeAddress.State);
|
||||||
|
Assert.Equal(1, user.HomeAddress.Street);
|
||||||
|
Assert.Equal(2, user.HomeAddress.Zip);
|
||||||
|
|
||||||
|
Assert.Equal("WA_Form_Office", user.OfficeAddress.State);
|
||||||
|
Assert.Equal(3, user.OfficeAddress.Street);
|
||||||
|
Assert.Equal(4, user.OfficeAddress.Zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanBind_ComplexData_OnParameters_UsingFromAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Provide all three values, it should bind based on the attribute on the action method.
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post,
|
||||||
|
"http://localhost/FromAttributes/GetUser/5/WA_Route/6" +
|
||||||
|
"?Street=3&State=WA_Query&Zip=4");
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string,string>("Street", "1"),
|
||||||
|
new KeyValuePair<string,string>("State", "WA_Form"),
|
||||||
|
new KeyValuePair<string,string>("Zip", "2"),
|
||||||
|
};
|
||||||
|
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var user = JsonConvert.DeserializeObject<User_FromForm>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
// Assert FromRoute
|
||||||
|
Assert.Equal("WA_Route", user.HomeAddress.State);
|
||||||
|
Assert.Equal(5, user.HomeAddress.Street);
|
||||||
|
Assert.Equal(6, user.HomeAddress.Zip);
|
||||||
|
|
||||||
|
// Assert FromForm
|
||||||
|
Assert.Equal("WA_Form", user.OfficeAddress.State);
|
||||||
|
Assert.Equal(1, user.OfficeAddress.Street);
|
||||||
|
Assert.Equal(2, user.OfficeAddress.Zip);
|
||||||
|
|
||||||
|
// Assert FromQuery
|
||||||
|
Assert.Equal("WA_Query", user.ShippingAddress.State);
|
||||||
|
Assert.Equal(3, user.ShippingAddress.Street);
|
||||||
|
Assert.Equal(4, user.ShippingAddress.Zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanBind_ComplexData_OnProperties_UsingFromAttributes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Provide all three values, it should bind based on the attribute on the action method.
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post,
|
||||||
|
"http://localhost/FromAttributes/GetUser_FromForm/5/WA_Route/6" +
|
||||||
|
"?ShippingAddress.Street=3&ShippingAddress.State=WA_Query&ShippingAddress.Zip=4");
|
||||||
|
var nameValueCollection = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string,string>("OfficeAddress.Street", "1"),
|
||||||
|
new KeyValuePair<string,string>("OfficeAddress.State", "WA_Form"),
|
||||||
|
new KeyValuePair<string,string>("OfficeAddress.Zip", "2"),
|
||||||
|
};
|
||||||
|
|
||||||
|
request.Content = new FormUrlEncodedContent(nameValueCollection);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var user = JsonConvert.DeserializeObject<User_FromForm>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
// Assert FromRoute
|
||||||
|
Assert.Equal("WA_Route", user.HomeAddress.State);
|
||||||
|
Assert.Equal(5, user.HomeAddress.Street);
|
||||||
|
Assert.Equal(6, user.HomeAddress.Zip);
|
||||||
|
|
||||||
|
// Assert FromForm
|
||||||
|
Assert.Equal("WA_Form", user.OfficeAddress.State);
|
||||||
|
Assert.Equal(1, user.OfficeAddress.Street);
|
||||||
|
Assert.Equal(2, user.OfficeAddress.Zip);
|
||||||
|
|
||||||
|
// Assert FromQuery
|
||||||
|
Assert.Equal("WA_Query", user.ShippingAddress.State);
|
||||||
|
Assert.Equal(3, user.ShippingAddress.Street);
|
||||||
|
Assert.Equal(4, user.ShippingAddress.Zip);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanBind_ComplexData_OnProperties_UsingFromAttributes_WithBody()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Provide all three values, it should bind based on the attribute on the action method.
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post,
|
||||||
|
"http://localhost/FromAttributes/GetUser_FromBody/5/WA_Route/6" +
|
||||||
|
"?ShippingAddress.Street=3&ShippingAddress.State=WA_Query&ShippingAddress.Zip=4");
|
||||||
|
var input = "{\"State\":\"WA_Body\",\"Street\":1,\"Zip\":2}";
|
||||||
|
|
||||||
|
request.Content = new StringContent(input, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var user = JsonConvert.DeserializeObject<User_FromBody>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
// Assert FromRoute
|
||||||
|
Assert.Equal("WA_Route", user.HomeAddress.State);
|
||||||
|
Assert.Equal(5, user.HomeAddress.Street);
|
||||||
|
Assert.Equal(6, user.HomeAddress.Zip);
|
||||||
|
|
||||||
|
// Assert FromBody
|
||||||
|
Assert.Equal("WA_Body", user.OfficeAddress.State);
|
||||||
|
Assert.Equal(1, user.OfficeAddress.Street);
|
||||||
|
Assert.Equal(2, user.OfficeAddress.Zip);
|
||||||
|
|
||||||
|
// Assert FromQuery
|
||||||
|
Assert.Equal("WA_Query", user.ShippingAddress.State);
|
||||||
|
Assert.Equal(3, user.ShippingAddress.Street);
|
||||||
|
Assert.Equal(4, user.ShippingAddress.Zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NonExistingModelBinder_ForABinderMetadata_DoesNotRecurseInfinitely()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var response = await client.GetStringAsync("http://localhost/WithMetadata/EchoDocument");
|
||||||
|
|
||||||
|
var document = JsonConvert.DeserializeObject<Document>
|
||||||
|
(response);
|
||||||
|
|
||||||
|
Assert.NotNull(document);
|
||||||
|
Assert.Null(document.Version);
|
||||||
|
Assert.Null(document.SubDocument);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ParametersWithNoValueProviderMetadataUseTheAvailableValueProviders()
|
public async Task ParametersWithNoValueProviderMetadataUseTheAvailableValueProviders()
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +381,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ParametersAreAlwaysCreated()
|
public async Task ParametersAreAlwaysCreated_IfValuesAreProvidedWithoutModelName()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var server = TestServer.Create(_services, _app);
|
var server = TestServer.Create(_services, _app);
|
||||||
|
|
@ -107,6 +402,136 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
Assert.Equal(12, person.Age);
|
Assert.Equal(12, person.Age);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParametersAreAlwaysCreated_IfValueIsProvidedForModelName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await
|
||||||
|
client.GetAsync("http://localhost/WithoutMetadata" +
|
||||||
|
"/GetPersonParameter?p="); // here p is the model name.
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var person = JsonConvert.DeserializeObject<Person>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.NotNull(person);
|
||||||
|
Assert.Null(person.Name);
|
||||||
|
Assert.Equal(0, person.Age);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParametersAreAlwaysCreated_IfNoValuesAreProvided()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await
|
||||||
|
client.GetAsync("http://localhost/WithoutMetadata" +
|
||||||
|
"/GetPersonParameter");
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var person = JsonConvert.DeserializeObject<Person>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.NotNull(person);
|
||||||
|
Assert.Null(person.Name);
|
||||||
|
Assert.Equal(0, person.Age);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PropertiesAreBound_IfTheyAreProvidedByValueProviders()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await
|
||||||
|
client.GetAsync("http://localhost/Properties" +
|
||||||
|
"/GetCompany?Employees[0].Name=somename&Age=12");
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.NotNull(company);
|
||||||
|
Assert.NotNull(company.Employees);
|
||||||
|
Assert.Equal(1, company.Employees.Count);
|
||||||
|
Assert.NotNull(company.Employees[0].Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PropertiesAreBound_IfTheyAreMarkedExplicitly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await
|
||||||
|
client.GetAsync("http://localhost/Properties" +
|
||||||
|
"/GetCompany");
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.NotNull(company);
|
||||||
|
Assert.NotNull(company.CEO);
|
||||||
|
Assert.Null(company.CEO.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PropertiesAreBound_IfTheyArePocoMetadataMarkedTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await
|
||||||
|
client.GetAsync("http://localhost/Properties" +
|
||||||
|
"/GetCompany");
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.NotNull(company);
|
||||||
|
|
||||||
|
// Department property is not null because it was a marker poco.
|
||||||
|
Assert.NotNull(company.Department);
|
||||||
|
|
||||||
|
// beacause no value is provided.
|
||||||
|
Assert.Null(company.Department.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PropertiesAreNotBound_ByDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await
|
||||||
|
client.GetAsync("http://localhost/Properties" +
|
||||||
|
"/GetCompany");
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var company = JsonConvert.DeserializeObject<Company>(
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.NotNull(company);
|
||||||
|
Assert.Null(company.Employees);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("http://localhost/Home/ActionWithPersonFromUrlWithPrefix/Javier/26")]
|
[InlineData("http://localhost/Home/ActionWithPersonFromUrlWithPrefix/Javier/26")]
|
||||||
[InlineData("http://localhost/Home/ActionWithPersonFromUrlWithoutPrefix/Javier/26")]
|
[InlineData("http://localhost/Home/ActionWithPersonFromUrlWithoutPrefix/Javier/26")]
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
ModelMetadata = metadataProvider.GetMetadataForType(null, typeof(int[])),
|
ModelMetadata = metadataProvider.GetMetadataForType(null, typeof(int[])),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
ModelBinder = CreateIntBinder(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = metadataProvider
|
{
|
||||||
|
ModelBinder = CreateIntBinder(),
|
||||||
|
MetadataProvider = metadataProvider
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
||||||
ModelName = "foo",
|
ModelName = "foo",
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
MetadataProvider = metadataProvider
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
MetadataProvider = metadataProvider
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(bound);
|
Assert.True(bound);
|
||||||
Assert.Equal(bindingContext.HttpContext.RequestAborted, bindingContext.Model);
|
Assert.Equal(bindingContext.OperationBindingContext.HttpContext.RequestAborted, bindingContext.Model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -52,9 +52,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
ModelMetadata = metadataProvider.GetMetadataForType(null, modelType),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = new SimpleHttpValueProvider(),
|
ValueProvider = new SimpleHttpValueProvider(),
|
||||||
ModelBinder = new CancellationTokenModelBinder(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = metadataProvider,
|
{
|
||||||
HttpContext = new DefaultHttpContext(),
|
ModelBinder = new CancellationTokenModelBinder(),
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
HttpContext = new DefaultHttpContext(),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
|
var bindingContext = GetModelBindingContext(new SimpleHttpValueProvider());
|
||||||
|
|
||||||
ModelValidationNode childValidationNode = null;
|
ModelValidationNode childValidationNode = null;
|
||||||
Mock.Get<IModelBinder>(bindingContext.ModelBinder)
|
Mock.Get<IModelBinder>(bindingContext.OperationBindingContext.ModelBinder)
|
||||||
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
.Setup(o => o.BindModelAsync(It.IsAny<ModelBindingContext>()))
|
||||||
.Returns((ModelBindingContext mbc) =>
|
.Returns((ModelBindingContext mbc) =>
|
||||||
{
|
{
|
||||||
|
|
@ -162,8 +162,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
ModelMetadata = metadataProvider.GetMetadataForType(null, typeof(int)),
|
ModelMetadata = metadataProvider.GetMetadataForType(null, typeof(int)),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
ModelBinder = CreateIntBinder(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = metadataProvider
|
{
|
||||||
|
ModelBinder = CreateIntBinder(),
|
||||||
|
MetadataProvider = metadataProvider
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
{ "someName", "dummyValue" }
|
{ "someName", "dummyValue" }
|
||||||
},
|
},
|
||||||
ValidatorProvider = GetValidatorProvider()
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = GetValidatorProvider()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var mockIntBinder = new Mock<IModelBinder>();
|
var mockIntBinder = new Mock<IModelBinder>();
|
||||||
|
|
@ -78,7 +81,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
{ "someOtherName", "dummyValue" }
|
{ "someOtherName", "dummyValue" }
|
||||||
},
|
},
|
||||||
ValidatorProvider = GetValidatorProvider()
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = GetValidatorProvider()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var mockIntBinder = new Mock<IModelBinder>();
|
var mockIntBinder = new Mock<IModelBinder>();
|
||||||
|
|
@ -151,7 +157,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
|
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(int)),
|
||||||
ModelState = new ModelStateDictionary()
|
ModelState = new ModelStateDictionary(),
|
||||||
|
OperationBindingContext = Mock.Of<OperationBindingContext>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -306,13 +313,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelBinder = binder,
|
|
||||||
FallbackToEmptyPrefix = true,
|
FallbackToEmptyPrefix = true,
|
||||||
MetadataProvider = metadataProvider,
|
|
||||||
ModelMetadata = metadataProvider.GetMetadataForType(null, type),
|
ModelMetadata = metadataProvider.GetMetadataForType(null, type),
|
||||||
ModelState = new ModelStateDictionary(),
|
ModelState = new ModelStateDictionary(),
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
ValidatorProvider = validatorProvider
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
MetadataProvider = metadataProvider,
|
||||||
|
ModelBinder = binder,
|
||||||
|
ValidatorProvider = validatorProvider
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
{ "someName[0]", new KeyValuePair<int, string>(42, "forty-two") },
|
{ "someName[0]", new KeyValuePair<int, string>(42, "forty-two") },
|
||||||
{ "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") }
|
{ "someName[1]", new KeyValuePair<int, string>(84, "eighty-four") }
|
||||||
},
|
},
|
||||||
ModelBinder = CreateKvpBinder(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = metadataProvider
|
{
|
||||||
|
ModelBinder = CreateKvpBinder(),
|
||||||
|
MetadataProvider = metadataProvider
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var binder = new DictionaryModelBinder<int, string>();
|
var binder = new DictionaryModelBinder<int, string>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
ModelMetadata = metataProvider.GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
|
ModelMetadata = metataProvider.GetMetadataForType(null, typeof(KeyValuePair<int, string>)),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = valueProvider,
|
ValueProvider = valueProvider,
|
||||||
ModelBinder = innerBinder ?? CreateIntBinder(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = metataProvider,
|
{
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ModelBinder = innerBinder ?? CreateIntBinder(),
|
||||||
|
MetadataProvider = metataProvider,
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return bindingContext;
|
return bindingContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
|
||||||
ValueProvider = new SimpleHttpValueProvider()
|
ValueProvider = new SimpleHttpValueProvider()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var newModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(null, typeof(object));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var newBindingContext = new ModelBindingContext(originalBindingContext);
|
var newBindingContext = new ModelBindingContext(originalBindingContext, string.Empty, newModelMetadata);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Null(newBindingContext.ModelMetadata);
|
Assert.Same(newModelMetadata, newBindingContext.ModelMetadata);
|
||||||
Assert.Equal("", newBindingContext.ModelName);
|
Assert.Equal("", newBindingContext.ModelName);
|
||||||
Assert.Equal(originalBindingContext.ModelState, newBindingContext.ModelState);
|
Assert.Equal(originalBindingContext.ModelState, newBindingContext.ModelState);
|
||||||
Assert.Equal(originalBindingContext.ValueProvider, newBindingContext.ValueProvider);
|
Assert.Equal(originalBindingContext.ValueProvider, newBindingContext.ValueProvider);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,316 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
public class MutableObjectModelBinderTest
|
public class MutableObjectModelBinderTest
|
||||||
{
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(Person), true)]
|
||||||
|
[InlineData(typeof(Person), false)]
|
||||||
|
[InlineData(typeof(EmptyModel), true)]
|
||||||
|
[InlineData(typeof(EmptyModel), false)]
|
||||||
|
public async Task
|
||||||
|
CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsExplicitPrefix(Type modelType, bool isPrefixProvided)
|
||||||
|
{
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(false));
|
||||||
|
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
// Random type.
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(Person)),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Setting it to empty ensures that model does not get created becasue of no model name.
|
||||||
|
ModelName = "dummyModelName",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bindingContext.ModelBindingContext.ModelMetadata.ModelName = isPrefixProvided ? "prefix" : null;
|
||||||
|
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||||
|
bindingContext.ModelBindingContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(isPrefixProvided, retModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(Person), true)]
|
||||||
|
[InlineData(typeof(Person), false)]
|
||||||
|
[InlineData(typeof(EmptyModel), true)]
|
||||||
|
[InlineData(typeof(EmptyModel), false)]
|
||||||
|
public async Task
|
||||||
|
CanCreateModel_CreatesModel_ForTopLevelObjectIfThereIsEmptyModelName(Type modelType, bool emptyModelName)
|
||||||
|
{
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(false));
|
||||||
|
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
// Random type.
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(Person)),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bindingContext.ModelBindingContext.ModelName = emptyModelName ? string.Empty : "dummyModelName";
|
||||||
|
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||||
|
bindingContext.ModelBindingContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(emptyModelName, retModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanCreateModel_ReturnsFalse_ForNonTopLevelModel_IfModelIsMarkedWithBinderMetadata()
|
||||||
|
{
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
// Get the property metadata so that it is not a top level object.
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(Document))
|
||||||
|
.Properties
|
||||||
|
.First(metadata => metadata.PropertyName == nameof(Document.SubDocument)),
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var mutableBinder = new MutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var canCreate = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(canCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanCreateModel_ReturnsTrue_ForTopLevelModel_IfModelIsMarkedWithBinderMetadata()
|
||||||
|
{
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
// Here the metadata represents a top level object.
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(Document)),
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var mutableBinder = new MutableObjectModelBinder();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var canCreate = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(canCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CanCreateModel_CreatesModel_IfTheModelIsBinderPoco()
|
||||||
|
{
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(false));
|
||||||
|
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(typeof(BinderMetadataPocoType)),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Setting it to empty ensures that model does not get created becasue of no model name.
|
||||||
|
ModelName = "dummyModelName",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||||
|
bindingContext.ModelBindingContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(retModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(TypeWithNoBinderMetadata), false)]
|
||||||
|
[InlineData(typeof(TypeWithNoBinderMetadata), true)]
|
||||||
|
[InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)]
|
||||||
|
[InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)]
|
||||||
|
[InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false)]
|
||||||
|
[InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true)]
|
||||||
|
public async Task
|
||||||
|
CanCreateModel_CreatesModelForValueProviderBasedBinderMetadatas_IfAValueProviderProvidesValue
|
||||||
|
(Type modelType, bool valueProviderProvidesValue)
|
||||||
|
{
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(valueProviderProvidesValue));
|
||||||
|
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(modelType),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
},
|
||||||
|
// Setting it to empty ensures that model does not get created becasue of no model name.
|
||||||
|
ModelName = "dummyName"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||||
|
bindingContext.ModelBindingContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(valueProviderProvidesValue, retModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)]
|
||||||
|
[InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)]
|
||||||
|
public async Task CanCreateModel_ForExplicitValueProviderMetadata_UsesOriginalValueProvider(Type modelType, bool originalValueProviderProvidesValue)
|
||||||
|
{
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(false));
|
||||||
|
|
||||||
|
var mockOriginalValueProvider = new Mock<IMetadataAwareValueProvider>();
|
||||||
|
mockOriginalValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(originalValueProviderProvidesValue));
|
||||||
|
mockOriginalValueProvider.Setup(o => o.Filter(It.IsAny<IValueProviderMetadata>()))
|
||||||
|
.Returns<IValueProviderMetadata>(
|
||||||
|
valueProviderMetadata =>
|
||||||
|
{
|
||||||
|
if(valueProviderMetadata is ValueBinderMetadataAttribute)
|
||||||
|
{
|
||||||
|
return mockOriginalValueProvider.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(modelType),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValueProvider = mockOriginalValueProvider.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Setting it to empty ensures that model does not get created becasue of no model name.
|
||||||
|
ModelName = "dummyName"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||||
|
bindingContext.ModelBindingContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(originalValueProviderProvidesValue, retModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false)]
|
||||||
|
[InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true)]
|
||||||
|
[InlineData(typeof(TypeWithNoBinderMetadata), false)]
|
||||||
|
[InlineData(typeof(TypeWithNoBinderMetadata), true)]
|
||||||
|
public async Task CanCreateModel_UnmarkedProperties_UsesCurrentValueProvider(Type modelType, bool valueProviderProvidesValue)
|
||||||
|
{
|
||||||
|
var mockValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(valueProviderProvidesValue));
|
||||||
|
|
||||||
|
var mockOriginalValueProvider = new Mock<IValueProvider>();
|
||||||
|
mockOriginalValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny<string>()))
|
||||||
|
.Returns(Task.FromResult(false));
|
||||||
|
|
||||||
|
var bindingContext = new MutableObjectBinderContext
|
||||||
|
{
|
||||||
|
ModelBindingContext = new ModelBindingContext
|
||||||
|
{
|
||||||
|
ModelMetadata = GetMetadataForType(modelType),
|
||||||
|
ValueProvider = mockValueProvider.Object,
|
||||||
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
ValueProvider = mockOriginalValueProvider.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
},
|
||||||
|
// Setting it to empty ensures that model does not get created becasue of no model name.
|
||||||
|
ModelName = "dummyName"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var mutableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
bindingContext.PropertyMetadata = mutableBinder.GetMetadataForProperties(
|
||||||
|
bindingContext.ModelBindingContext);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var retModel = await mutableBinder.CanCreateModel(bindingContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(valueProviderProvidesValue, retModel);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BindModel_InitsInstance()
|
public async Task BindModel_InitsInstance()
|
||||||
{
|
{
|
||||||
|
|
@ -31,9 +341,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelMetadata = GetMetadataForObject(new Person()),
|
ModelMetadata = GetMetadataForObject(new Person()),
|
||||||
ModelName = "someName",
|
ModelName = "someName",
|
||||||
ValueProvider = mockValueProvider.Object,
|
ValueProvider = mockValueProvider.Object,
|
||||||
ModelBinder = mockDtoBinder.Object,
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
{
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ModelBinder = mockDtoBinder.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mockDtoBinder
|
mockDtoBinder
|
||||||
|
|
@ -46,8 +359,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
testableBinder.Setup(o => o.EnsureModelPublic(bindingContext)).Verifiable();
|
testableBinder.Setup(o => o.EnsureModelPublic(bindingContext)).Verifiable();
|
||||||
testableBinder.Setup(o => o.GetMetadataForPropertiesPublic(bindingContext))
|
testableBinder.Setup(o => o.GetMetadataForProperties(bindingContext))
|
||||||
.Returns(new ModelMetadata[0]).Verifiable();
|
.Returns(new ModelMetadata[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
||||||
|
|
@ -73,9 +386,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelMetadata = GetMetadataForObject(new Person()),
|
ModelMetadata = GetMetadataForObject(new Person()),
|
||||||
ModelName = "",
|
ModelName = "",
|
||||||
ValueProvider = mockValueProvider.Object,
|
ValueProvider = mockValueProvider.Object,
|
||||||
ModelBinder = mockDtoBinder.Object,
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
{
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
ModelBinder = mockDtoBinder.Object,
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider(),
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mockDtoBinder
|
mockDtoBinder
|
||||||
|
|
@ -88,8 +404,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
|
|
||||||
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
var testableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
|
||||||
testableBinder.Setup(o => o.EnsureModelPublic(bindingContext)).Verifiable();
|
testableBinder.Setup(o => o.EnsureModelPublic(bindingContext)).Verifiable();
|
||||||
testableBinder.Setup(o => o.GetMetadataForPropertiesPublic(bindingContext))
|
testableBinder.Setup(o => o.GetMetadataForProperties(bindingContext))
|
||||||
.Returns(new ModelMetadata[0]).Verifiable();
|
.Returns(new ModelMetadata[0]);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
var retValue = await testableBinder.Object.BindModelAsync(bindingContext);
|
||||||
|
|
@ -237,14 +553,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(bindingContext);
|
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
|
||||||
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -267,14 +586,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(Person)),
|
ModelMetadata = GetMetadataForType(typeof(Person)),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(bindingContext);
|
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
|
||||||
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -300,14 +622,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
|
ModelMetadata = GetMetadataForType(typeof(TypeWithExcludedPropertiesUsingBindAttribute)),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(bindingContext);
|
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
|
||||||
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -334,14 +659,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForType(typeof(TypeWithIncludedPropertiesUsingBindAttribute)),
|
ModelMetadata = GetMetadataForType(typeof(TypeWithIncludedPropertiesUsingBindAttribute)),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
OperationBindingContext = new OperationBindingContext
|
||||||
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
|
||||||
|
MetadataProvider = new DataAnnotationsModelMetadataProvider()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var testableBinder = new TestableMutableObjectModelBinder();
|
var testableBinder = new TestableMutableObjectModelBinder();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(bindingContext);
|
var propertyMetadatas = testableBinder.GetMetadataForProperties(bindingContext);
|
||||||
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
var returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -355,7 +683,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var bindingContext = new ModelBindingContext
|
var bindingContext = new ModelBindingContext
|
||||||
{
|
{
|
||||||
ModelMetadata = GetMetadataForObject(new ModelWithMixedBindingBehaviors()),
|
ModelMetadata = GetMetadataForObject(new ModelWithMixedBindingBehaviors()),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -429,7 +760,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
ModelMetadata = containerMetadata,
|
ModelMetadata = containerMetadata,
|
||||||
ModelName = "theModel",
|
ModelName = "theModel",
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
|
||||||
|
|
||||||
|
|
@ -472,10 +806,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelMetadata = containerMetadata,
|
ModelMetadata = containerMetadata,
|
||||||
ModelName = "theModel",
|
ModelName = "theModel",
|
||||||
ModelState = new ModelStateDictionary(),
|
ModelState = new ModelStateDictionary(),
|
||||||
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = Mock.Of<IModelValidatorProvider>()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var validationContext = new ModelValidationContext(new EmptyModelMetadataProvider(),
|
var validationContext = new ModelValidationContext(new EmptyModelMetadataProvider(),
|
||||||
bindingContext.ValidatorProvider,
|
bindingContext.OperationBindingContext
|
||||||
|
.ValidatorProvider,
|
||||||
bindingContext.ModelState,
|
bindingContext.ModelState,
|
||||||
containerMetadata,
|
containerMetadata,
|
||||||
null);
|
null);
|
||||||
|
|
@ -705,7 +1043,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.First(o => o.PropertyName == "PropertyWithDefaultValue");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.First(o => o.PropertyName == "PropertyWithDefaultValue");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||||
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode);
|
||||||
var requiredValidator = bindingContext.ValidatorProvider
|
var requiredValidator = bindingContext.OperationBindingContext
|
||||||
|
.ValidatorProvider
|
||||||
.GetValidators(propertyMetadata)
|
.GetValidators(propertyMetadata)
|
||||||
.FirstOrDefault(v => v.IsRequired);
|
.FirstOrDefault(v => v.IsRequired);
|
||||||
|
|
||||||
|
|
@ -748,7 +1087,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
|
||||||
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
var validationNode = new ModelValidationNode(propertyMetadata, "foo");
|
||||||
var dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
|
var dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
|
||||||
var requiredValidator = bindingContext.ValidatorProvider
|
var requiredValidator = bindingContext.OperationBindingContext
|
||||||
|
.ValidatorProvider
|
||||||
.GetValidators(propertyMetadata)
|
.GetValidators(propertyMetadata)
|
||||||
.FirstOrDefault(v => v.IsRequired);
|
.FirstOrDefault(v => v.IsRequired);
|
||||||
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
var validationContext = new ModelValidationContext(bindingContext, propertyMetadata);
|
||||||
|
|
@ -895,13 +1235,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
ModelState = new ModelStateDictionary(),
|
ModelState = new ModelStateDictionary(),
|
||||||
ModelMetadata = metadata,
|
ModelMetadata = metadata,
|
||||||
ModelName = "theModel",
|
ModelName = "theModel",
|
||||||
ValidatorProvider = new CompositeModelValidatorProvider(provider.Object)
|
OperationBindingContext = new OperationBindingContext
|
||||||
|
{
|
||||||
|
ValidatorProvider = new CompositeModelValidatorProvider(provider.Object)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IModelValidator GetRequiredValidator(ModelBindingContext bindingContext, ModelMetadata propertyMetadata)
|
private static IModelValidator GetRequiredValidator(ModelBindingContext bindingContext, ModelMetadata propertyMetadata)
|
||||||
{
|
{
|
||||||
return bindingContext.ValidatorProvider
|
return bindingContext.OperationBindingContext
|
||||||
|
.ValidatorProvider
|
||||||
.GetValidators(propertyMetadata)
|
.GetValidators(propertyMetadata)
|
||||||
.FirstOrDefault(v => v.IsRequired);
|
.FirstOrDefault(v => v.IsRequired);
|
||||||
}
|
}
|
||||||
|
|
@ -934,6 +1278,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
binderMetadata: null);
|
binderMetadata: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class EmptyModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private class Person
|
private class Person
|
||||||
{
|
{
|
||||||
private DateTime? _dateOfDeath;
|
private DateTime? _dateOfDeath;
|
||||||
|
|
@ -1033,6 +1381,53 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TypeWithNoBinderMetadata
|
||||||
|
{
|
||||||
|
public int UnMarkedProperty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BinderMetadataPocoType
|
||||||
|
{
|
||||||
|
[NonValueBinderMetadata]
|
||||||
|
public string MarkedWithABinderMetadata { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a Metadata poco because there is a property with value binder Metadata.
|
||||||
|
private class TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata
|
||||||
|
{
|
||||||
|
[NonValueBinderMetadata]
|
||||||
|
public string MarkedWithABinderMetadata { get; set; }
|
||||||
|
|
||||||
|
[ValueBinderMetadata]
|
||||||
|
public string MarkedWithAValueBinderMetadata { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a Metadata poco because there is an unmarked property.
|
||||||
|
private class TypeWithUnmarkedAndBinderMetadataMarkedProperties
|
||||||
|
{
|
||||||
|
public int UnmarkedProperty { get; set; }
|
||||||
|
|
||||||
|
[NonValueBinderMetadata]
|
||||||
|
public string MarkedWithABinderMetadata { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Document
|
||||||
|
{
|
||||||
|
[NonValueBinderMetadata]
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
[NonValueBinderMetadata]
|
||||||
|
public Document SubDocument { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NonValueBinderMetadataAttribute : Attribute, IBinderMetadata
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ValueBinderMetadataAttribute : Attribute, IValueProviderMetadata
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public class TestableMutableObjectModelBinder : MutableObjectModelBinder
|
public class TestableMutableObjectModelBinder : MutableObjectModelBinder
|
||||||
{
|
{
|
||||||
public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)
|
public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)
|
||||||
|
|
@ -1065,16 +1460,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
EnsureModelPublic(bindingContext);
|
EnsureModelPublic(bindingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IEnumerable<ModelMetadata> GetMetadataForPropertiesPublic(ModelBindingContext bindingContext)
|
public virtual new IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
return base.GetMetadataForProperties(bindingContext);
|
return base.GetMetadataForProperties(bindingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<ModelMetadata> GetMetadataForProperties(ModelBindingContext bindingContext)
|
|
||||||
{
|
|
||||||
return GetMetadataForPropertiesPublic(bindingContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetPropertyPublic(ModelBindingContext bindingContext,
|
public virtual void SetPropertyPublic(ModelBindingContext bindingContext,
|
||||||
ModelMetadata propertyMetadata,
|
ModelMetadata propertyMetadata,
|
||||||
ComplexModelDtoResult dtoResult,
|
ComplexModelDtoResult dtoResult,
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,52 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForProperty_WithNoBinderMetadata_GetsItFromType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var propertyMetadata = provider.GetMetadataForProperty(null, typeof(Person), nameof(Person.Parent));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(propertyMetadata.BinderMetadata);
|
||||||
|
Assert.IsType<TestBinderMetadataAttribute>(propertyMetadata.BinderMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ASPNET50
|
||||||
|
[Fact]
|
||||||
|
public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var provider = new DataAnnotationsModelMetadataProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var parameterMetadata = provider.GetMetadataForParameter(null,
|
||||||
|
typeof(Person).GetMethod("Update"),
|
||||||
|
"person",
|
||||||
|
null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(parameterMetadata.BinderMetadata);
|
||||||
|
Assert.IsType<TestBinderMetadataAttribute>(parameterMetadata.BinderMetadata);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
public class TestBinderMetadataAttribute : Attribute, IBinderMetadata
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestBinderMetadata]
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
public Person Parent { get; set; }
|
||||||
|
|
||||||
|
public void Update(Person person)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetMetadataForProperty
|
// GetMetadataForProperty
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ namespace FiltersWebSite
|
||||||
|
|
||||||
public override void OnActionExecuting(ActionExecutingContext context)
|
public override void OnActionExecuting(ActionExecutingContext context)
|
||||||
{
|
{
|
||||||
|
if (context.ActionArguments["fromGlobalActionFilter"] == null)
|
||||||
|
{
|
||||||
|
context.ActionArguments["fromGlobalActionFilter"] = new List<ContentResult>();
|
||||||
|
}
|
||||||
(context.ActionArguments["fromGlobalActionFilter"] as List<ContentResult>)
|
(context.ActionArguments["fromGlobalActionFilter"] as List<ContentResult>)
|
||||||
.Add(Helpers.GetContentResult(context.Result, "Controller override - OnActionExecuting"));
|
.Add(Helpers.GetContentResult(context.Result, "Controller override - OnActionExecuting"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
// 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 Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class FromAttributesController : Controller
|
||||||
|
{
|
||||||
|
[Route("/FromAttributes/[action]/{HomeAddress.Street}/{HomeAddress.State}/{HomeAddress.Zip}")]
|
||||||
|
public User_FromBody GetUser_FromBody(User_FromBody user)
|
||||||
|
{
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/FromAttributes/[action]/{HomeAddress.Street}/{HomeAddress.State}/{HomeAddress.Zip}")]
|
||||||
|
public User_FromForm GetUser_FromForm(User_FromForm user)
|
||||||
|
{
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/FromAttributes/[action]/{HomeAddress.Street}/{HomeAddress.State}/{HomeAddress.Zip}")]
|
||||||
|
public User_FromForm GetUser([FromRoute] Address homeAddress,
|
||||||
|
[FromForm] Address officeAddress,
|
||||||
|
[FromQuery] Address shippingAddress)
|
||||||
|
{
|
||||||
|
return new User_FromForm
|
||||||
|
{
|
||||||
|
HomeAddress = homeAddress,
|
||||||
|
OfficeAddress = officeAddress,
|
||||||
|
ShippingAddress = shippingAddress
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public User_FromForm MultipleFromFormParameters([FromForm] Address homeAddress,
|
||||||
|
[FromForm] Address officeAddress)
|
||||||
|
{
|
||||||
|
return new User_FromForm
|
||||||
|
{
|
||||||
|
HomeAddress = homeAddress,
|
||||||
|
OfficeAddress = officeAddress,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// User_FromForm has a FromForm property.
|
||||||
|
public User_FromForm MultipleFromFormParameterAndProperty(User_FromForm user,
|
||||||
|
[FromForm] Address defaultAddress)
|
||||||
|
{
|
||||||
|
user.HomeAddress = defaultAddress;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FromBodyParametersThrows([FromBody] int id, [FromBody] string emp)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customer has a FromBody Property.
|
||||||
|
public void FromBodyParameterAndPropertyThrows([FromBody] Person p, Customer customer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FormAndBody_Throws([FromForm] Person p, Customer customer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FormAndBody_AsParameters_Throws([FromBody] int id, [FromForm] string emp)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,26 @@ namespace ModelBindingWebSite.Controllers
|
||||||
return person;
|
return person;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Customer GetCustomer(int id)
|
||||||
|
{
|
||||||
|
var customer = CreateCustomer(id);
|
||||||
|
|
||||||
|
// should update customer.Department from body.
|
||||||
|
TryUpdateModelAsync(customer);
|
||||||
|
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Customer CreateCustomer(int id)
|
||||||
|
{
|
||||||
|
return new Customer()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = "dummy",
|
||||||
|
Age = 25,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private Dictionary<string, string> CreateValidationDictionary()
|
private Dictionary<string, string> CreateValidationDictionary()
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>();
|
var result = new Dictionary<string, string>();
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
namespace ModelBindingWebSite.Controllers
|
namespace ModelBindingWebSite.Controllers
|
||||||
{
|
{
|
||||||
public class MultipleParametersFromBodyController : Controller
|
public class PropertiesController : Controller
|
||||||
{
|
{
|
||||||
public void MultipleParametersFromBodyThrows([FromBody] int i, [FromBody] string emp)
|
public Company GetCompany(Company c)
|
||||||
{
|
{
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.AspNet.Mvc;
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace ModelBindingWebSite.Controllers
|
namespace ModelBindingWebSite.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -25,5 +27,10 @@ namespace ModelBindingWebSite.Controllers
|
||||||
{
|
{
|
||||||
return emp;
|
return emp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Document EchoDocument(Document poco)
|
||||||
|
{
|
||||||
|
return poco;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace ModelBindingWebSite
|
namespace ModelBindingWebSite
|
||||||
{
|
{
|
||||||
public class ExternalType
|
public class FromNonExistantBinderAttribute : Attribute, IBinderMetadata
|
||||||
{
|
{
|
||||||
public string Department { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +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 Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class FromTestAttribute : Attribute, IBinderMetadata
|
||||||
|
{
|
||||||
|
public object Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class Address
|
||||||
|
{
|
||||||
|
public int Street { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
public int Zip { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// 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 Company
|
||||||
|
{
|
||||||
|
public Department Department { get; set; }
|
||||||
|
|
||||||
|
[FromTest]
|
||||||
|
public Person CEO { get; set; }
|
||||||
|
|
||||||
|
public IList<Employee> Employees { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// 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 Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class Customer : Person
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[FromBody]
|
||||||
|
public Department Department { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class Department
|
||||||
|
{
|
||||||
|
// A single property marked with a binder metadata attribute makes it a binder metadata poco.
|
||||||
|
[FromTest]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class Document
|
||||||
|
{
|
||||||
|
[FromNonExistantBinder]
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
[FromNonExistantBinder]
|
||||||
|
public Document SubDocument { 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 Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class User_FromBody
|
||||||
|
{
|
||||||
|
[FromRoute]
|
||||||
|
public Address HomeAddress { get; set; }
|
||||||
|
|
||||||
|
[FromBody]
|
||||||
|
public Address OfficeAddress { get; set; }
|
||||||
|
|
||||||
|
[FromQuery]
|
||||||
|
public Address ShippingAddress { get; set; }
|
||||||
|
|
||||||
|
// Should get it from the first value provider which
|
||||||
|
// can provide values for this.
|
||||||
|
public Address DefaultAddress { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// 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 Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class User_FromForm
|
||||||
|
{
|
||||||
|
[FromRoute]
|
||||||
|
public Address HomeAddress { get; set; }
|
||||||
|
|
||||||
|
[FromForm]
|
||||||
|
public Address OfficeAddress { get; set; }
|
||||||
|
|
||||||
|
[FromQuery]
|
||||||
|
public Address ShippingAddress { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ namespace ModelBindingWebSite
|
||||||
.Configure<MvcOptions>(m =>
|
.Configure<MvcOptions>(m =>
|
||||||
{
|
{
|
||||||
m.MaxModelValidationErrors = 8;
|
m.MaxModelValidationErrors = 8;
|
||||||
|
m.ModelBinders.Insert(0, typeof(TestMetadataAwareBinder));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace ModelBindingWebSite
|
||||||
|
{
|
||||||
|
public class TestMetadataAwareBinder : MetadataAwareBinder<FromTestAttribute>
|
||||||
|
{
|
||||||
|
protected override Task<bool> BindAsync(ModelBindingContext bindingContext, FromTestAttribute metadata)
|
||||||
|
{
|
||||||
|
bindingContext.Model = metadata.Value;
|
||||||
|
|
||||||
|
if (!IsSimpleType(bindingContext.ModelType))
|
||||||
|
{
|
||||||
|
bindingContext.Model = Activator.CreateInstance(bindingContext.ModelType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool IsSimpleType(Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().IsPrimitive ||
|
||||||
|
type.Equals(typeof(decimal)) ||
|
||||||
|
type.Equals(typeof(string)) ||
|
||||||
|
type.Equals(typeof(DateTime)) ||
|
||||||
|
type.Equals(typeof(Guid)) ||
|
||||||
|
type.Equals(typeof(DateTimeOffset)) ||
|
||||||
|
type.Equals(typeof(TimeSpan));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue