Removing ModelMetadataProvider.GetModelMetadataForParameters

This commit is contained in:
Harsh Gupta 2015-03-12 20:46:45 -07:00
parent d166517dd6
commit ac908d405e
54 changed files with 696 additions and 688 deletions

17
Mvc.sln
View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22609.0
VisualStudioVersion = 14.0.22711.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -154,6 +154,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TestCo
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsWebSite", "test\WebSites\CorsWebSite\CorsWebSite.xproj", "{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsMiddlewareWebSite", "test\WebSites\CorsMiddlewareWebSite\CorsMiddlewareWebSite.xproj", "{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -908,6 +910,18 @@ Global
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.ActiveCfg = Release|Any CPU
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.Build.0 = Release|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|x86.ActiveCfg = Debug|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|x86.Build.0 = Debug|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.Build.0 = Release|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|x86.ActiveCfg = Release|Any CPU
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -983,5 +997,6 @@ Global
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{94BA134D-04B3-48AA-BA55-5A4DB8640F2D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Cors;
using Microsoft.AspNet.Cors.Core;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Framework.Internal;
using Microsoft.Framework.OptionsModel;
@ -343,7 +344,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
var attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType<object>().ToArray();
var parameterModel = new ParameterModel(parameterInfo, attributes);
parameterModel.BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
var bindingInfo = BindingInfo.GetBindingInfo(attributes);
parameterModel.BindingInfo = bindingInfo;
parameterModel.ParameterName = parameterInfo.Name;

View File

@ -1,10 +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.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ApplicationModels
@ -24,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
{
Action = other.Action;
Attributes = new List<object>(other.Attributes);
BinderMetadata = other.BinderMetadata;
BindingInfo = other.BindingInfo;
ParameterInfo = other.ParameterInfo;
ParameterName = other.ParameterName;
}
@ -33,10 +35,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public IReadOnlyList<object> Attributes { get; }
public IBinderMetadata BinderMetadata { get; set; }
public ParameterInfo ParameterInfo { get; private set; }
public string ParameterName { get; set; }
public BindingInfo BindingInfo { get; set; }
}
}

View File

@ -276,9 +276,9 @@ namespace Microsoft.AspNet.Mvc
{
var parameterDescriptor = new ParameterDescriptor()
{
BinderMetadata = parameter.BinderMetadata,
Name = parameter.ParameterName,
ParameterType = parameter.ParameterInfo.ParameterType,
BindingInfo = parameter.BindingInfo
};
return parameterDescriptor;

View File

@ -4,9 +4,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
@ -44,29 +46,20 @@ namespace Microsoft.AspNet.Mvc
nameof(actionContext));
}
var methodParameters = actionDescriptor.MethodInfo.GetParameters();
var parameterMetadata = new List<ModelMetadata>();
foreach (var parameter in actionDescriptor.Parameters)
{
var parameterInfo = methodParameters.Where(p => p.Name == parameter.Name).Single();
var metadata = _modelMetadataProvider.GetMetadataForParameter(
parameterInfo,
attributes: new object[] { parameter.BinderMetadata });
parameterMetadata.Add(metadata);
}
var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
await PopulateArgumentAsync(actionContext, actionBindingContext, actionArguments, parameterMetadata);
await PopulateArgumentsAsync(
actionContext,
actionBindingContext,
actionArguments,
actionDescriptor.Parameters);
return actionArguments;
}
}
private async Task PopulateArgumentAsync(
private async Task PopulateArgumentsAsync(
ActionContext actionContext,
ActionBindingContext bindingContext,
IDictionary<string, object> arguments,
IEnumerable<ModelMetadata> parameterMetadata)
IEnumerable<ParameterDescriptor> parameterMetadata)
{
var operationBindingContext = new OperationBindingContext
{
@ -81,15 +74,24 @@ namespace Microsoft.AspNet.Mvc
modelState.MaxAllowedErrors = _options.MaxModelValidationErrors;
foreach (var parameter in parameterMetadata)
{
var parameterType = parameter.ModelType;
var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
var parameterType = parameter.ParameterType;
var modelBindingContext = GetModelBindingContext(
parameter.Name,
metadata,
parameter.BindingInfo,
modelState,
operationBindingContext);
var modelBindingContext = GetModelBindingContext(parameter, modelState, operationBindingContext);
var modelBindingResult = await bindingContext.ModelBinder.BindModelAsync(modelBindingContext);
if (modelBindingResult != null && modelBindingResult.IsModelSet)
{
var modelExplorer = new ModelExplorer(_modelMetadataProvider, parameter, modelBindingResult.Model);
var modelExplorer = new ModelExplorer(
_modelMetadataProvider,
metadata,
modelBindingResult.Model);
arguments[parameter.PropertyName] = modelBindingResult.Model;
arguments[parameter.Name] = modelBindingResult.Model;
var validationContext = new ModelValidationContext(
modelBindingResult.Key,
bindingContext.ValidatorProvider,
@ -100,23 +102,21 @@ namespace Microsoft.AspNet.Mvc
}
}
// Internal for tests
internal static ModelBindingContext GetModelBindingContext(
ModelMetadata modelMetadata,
private static ModelBindingContext GetModelBindingContext(
string parameterName,
ModelMetadata metadata,
BindingInfo bindingInfo,
ModelStateDictionary modelState,
OperationBindingContext operationBindingContext)
{
var modelBindingContext = new ModelBindingContext
{
ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName,
ModelMetadata = modelMetadata,
ModelState = modelState,
var modelBindingContext = ModelBindingContext.GetModelBindingContext(
metadata,
bindingInfo,
parameterName);
// Fallback only if there is no explicit model name set.
FallbackToEmptyPrefix = modelMetadata.BinderModelName == null,
ValueProvider = operationBindingContext.ValueProvider,
OperationBindingContext = operationBindingContext,
};
modelBindingContext.ModelState = modelState;
modelBindingContext.ValueProvider = operationBindingContext.ValueProvider;
modelBindingContext.OperationBindingContext = operationBindingContext;
return modelBindingContext;
}

View File

@ -4,8 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Template;
using Microsoft.Framework.Internal;
@ -127,7 +129,6 @@ namespace Microsoft.AspNet.Mvc.Description
return apiDescription;
}
private IList<ApiParameterDescription> GetParameters(ApiParameterContext context)
{
// First, get parameters from the model-binding/parameter-binding side of the world.
@ -136,7 +137,13 @@ namespace Microsoft.AspNet.Mvc.Description
foreach (var actionParameter in context.ActionDescriptor.Parameters)
{
var visitor = new PseudoModelBindingVisitor(context, actionParameter);
visitor.WalkParameter();
var metadata = _modelMetadataProvider.GetMetadataForType(actionParameter.ParameterType);
var bindingContext = ApiParameterDescriptionContext.GetContext(
metadata,
actionParameter.BindingInfo,
propertyName: actionParameter.Name);
visitor.WalkParameter(bindingContext);
}
}
@ -422,6 +429,32 @@ namespace Microsoft.AspNet.Mvc.Description
public IReadOnlyList<TemplatePart> RouteParameters { get; }
}
private class ApiParameterDescriptionContext
{
public ModelMetadata ModelMetadata { get; set; }
public string BinderModelName { get; set; }
public BindingSource BindingSource { get; set; }
public string PropertyName { get; set; }
public static ApiParameterDescriptionContext GetContext(
ModelMetadata metadata,
BindingInfo bindingInfo,
string propertyName)
{
// BindingMetadata can be null if the metadata represents properties.
return new ApiParameterDescriptionContext
{
ModelMetadata = metadata,
BinderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName,
BindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource,
PropertyName = propertyName ?? metadata.PropertyName
};
}
}
private class PseudoModelBindingVisitor
{
public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter)
@ -439,29 +472,20 @@ namespace Microsoft.AspNet.Mvc.Description
// Avoid infinite recursion by tracking properties.
private HashSet<PropertyKey> Visited { get; }
public void WalkParameter()
public void WalkParameter(ApiParameterDescriptionContext context)
{
var parameterInfo =
Context.ActionDescriptor.MethodInfo.GetParameters()
.Where(p => p.Name == Parameter.Name)
.Single();
var modelMetadata = Context.MetadataProvider.GetMetadataForParameter(
parameterInfo,
attributes: new object[] { Parameter.BinderMetadata });
// Attempt to find a binding source for the parameter
//
// The default is ModelBinding (aka all default value providers)
var source = BindingSource.ModelBinding;
if (!Visit(modelMetadata, source, containerName: string.Empty))
if (!Visit(context, source, containerName: string.Empty))
{
// If we get here, then it means we didn't find a match for any of the model. This means that it's
// likely 'model-bound' in the traditional MVC sense (formdata + query string + route data) and
// doesn't use any IBinderMetadata.
//
// Add a single 'default' parameter description for the model.
Context.Results.Add(CreateResult(modelMetadata, source, containerName: string.Empty));
Context.Results.Add(CreateResult(context, source, containerName: string.Empty));
}
}
@ -481,18 +505,23 @@ namespace Microsoft.AspNet.Mvc.Description
/// or NONE of it. If a parameter description is created for ANY sub-properties of the model, then a parameter
/// description will be created for ALL of them.
/// </remarks>
private bool Visit(ModelMetadata modelMetadata, BindingSource ambientSource, string containerName)
private bool Visit(
ApiParameterDescriptionContext bindingContext,
BindingSource ambientSource,
string containerName)
{
var source = modelMetadata.BindingSource;
var source = bindingContext.BindingSource;
if (source != null && source.IsGreedy)
{
// We have a definite answer for this model. This is a greedy source like
// [FromBody] so there's no need to consider properties.
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
Context.Results.Add(CreateResult(bindingContext, source, containerName));
return true;
}
var modelMetadata = bindingContext.ModelMetadata;
// For any property which is a leaf node, we don't want to keep traversing:
//
// 1) Collections - while it's possible to have binder attributes on the inside of a collection,
@ -517,7 +546,7 @@ namespace Microsoft.AspNet.Mvc.Description
{
// We found a new source, and this model has no properties. This is probabaly
// a simple type with an attribute like [FromQuery].
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
Context.Results.Add(CreateResult(bindingContext, source, containerName));
return true;
}
}
@ -545,29 +574,33 @@ namespace Microsoft.AspNet.Mvc.Description
// Order - source: Body
//
var unboundProperties = new HashSet<ModelMetadata>();
var unboundProperties = new HashSet<ApiParameterDescriptionContext>();
// We don't want to append the **parameter** name when building a model name.
var newContainerName = containerName;
if (modelMetadata.ContainerType != null)
{
newContainerName = GetName(containerName, modelMetadata);
newContainerName = GetName(containerName, bindingContext);
}
foreach (var propertyMetadata in modelMetadata.Properties)
{
var key = new PropertyKey(propertyMetadata, source);
var propertyContext = ApiParameterDescriptionContext.GetContext(
propertyMetadata,
bindingInfo: null,
propertyName: null);
if (Visited.Add(key))
{
if (!Visit(propertyMetadata, source ?? ambientSource, newContainerName))
if (!Visit(propertyContext, source ?? ambientSource, newContainerName))
{
unboundProperties.Add(propertyMetadata);
unboundProperties.Add(propertyContext);
}
}
else
{
unboundProperties.Add(propertyMetadata);
unboundProperties.Add(propertyContext);
}
}
@ -582,7 +615,7 @@ namespace Microsoft.AspNet.Mvc.Description
{
// We found a new source, and didn't create a result for any of the properties yet,
// so create a result for the current object.
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
Context.Results.Add(CreateResult(bindingContext, source, containerName));
return true;
}
}
@ -600,20 +633,20 @@ namespace Microsoft.AspNet.Mvc.Description
}
private ApiParameterDescription CreateResult(
ModelMetadata metadata,
ApiParameterDescriptionContext bindingContext,
BindingSource source,
string containerName)
{
return new ApiParameterDescription()
{
ModelMetadata = metadata,
Name = GetName(containerName, metadata),
ModelMetadata = bindingContext.ModelMetadata,
Name = GetName(containerName, bindingContext),
Source = source,
Type = metadata.ModelType,
Type = bindingContext.ModelMetadata.ModelType,
};
}
private static string GetName(string containerName, ModelMetadata metadata)
private static string GetName(string containerName, ApiParameterDescriptionContext metadata)
{
if (!string.IsNullOrEmpty(metadata.BinderModelName))
{

View File

@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.Logging
{
ParameterName = inner.Name;
ParameterType = inner.ParameterType;
BinderMetadataType = inner.BinderMetadata?.GetType();
}
/// <summary>
@ -30,11 +29,6 @@ namespace Microsoft.AspNet.Mvc.Logging
/// </summary>
public Type ParameterType { get; }
/// <summary>
/// The <see cref="Type"/> of the <see cref="ParameterDescriptor.BinderMetadata"/>.
/// </summary>
public Type BinderMetadataType { get; }
public override string Format()
{
return LogFormatter.FormatLogValues(this);

View File

@ -19,7 +19,6 @@ namespace Microsoft.AspNet.Mvc.Logging
{
ParameterName = inner.ParameterName;
ParameterType = inner.ParameterInfo.ParameterType;
BinderMetadata = inner.BinderMetadata?.GetType();
}
/// <summary>
@ -32,11 +31,6 @@ namespace Microsoft.AspNet.Mvc.Logging
/// </summary>
public Type ParameterType { get; }
/// <summary>
/// The <see cref="Type"/> of the <see cref="ParameterModel.BinderMetadata"/>.
/// </summary>
public Type BinderMetadata { get; }
public override string Format()
{
return LogFormatter.FormatLogValues(this);

View File

@ -3,6 +3,7 @@
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNet.Mvc
{
@ -12,6 +13,6 @@ namespace Microsoft.AspNet.Mvc
public Type ParameterType { get; set; }
public IBinderMetadata BinderMetadata { get; set; }
public BindingInfo BindingInfo { get; set; }
}
}

View File

@ -1,12 +0,0 @@
// 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>
/// Interface for metadata related to model binders.
/// </summary>
public interface IBinderMetadata
{
}
}

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary>
/// Metadata which specificies the data source for model binding.
/// </summary>
public interface IBindingSourceMetadata : IBinderMetadata
public interface IBindingSourceMetadata
{
/// <summary>
/// Gets the <see cref="BindingSource"/>.

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelMetadata.BinderType == null)
if (bindingContext.BinderType == null)
{
// Return null so that we are able to continue with the default set of model binders,
// if there is no specific model binder provided.
@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.ModelMetadata.BinderType, _createFactory);
var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.BinderType, _createFactory);
var instance = createFactory(requestServices, arguments: null);
var modelBinder = instance as IModelBinder;
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
throw new InvalidOperationException(
Resources.FormatBinderType_MustBeIModelBinderOrIModelBinderProvider(
bindingContext.ModelMetadata.BinderType.FullName,
bindingContext.BinderType.FullName,
typeof(IModelBinder).FullName,
typeof(IModelBinderProvider).FullName));
}

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <inheritdoc />
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext context)
{
var allowedBindingSource = context.ModelMetadata.BindingSource;
var allowedBindingSource = context.BindingSource;
if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource))
{
// Binding Sources are opt-in. This model either didn't specify one or specified something

View File

@ -50,16 +50,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var rawValueArray = RawValueToObjectArray(rawValue);
foreach (var rawValueElement in rawValueArray)
{
var innerBindingContext = new ModelBindingContext(bindingContext,
bindingContext.ModelName,
elementMetadata)
var innerBindingContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
bindingContext.ModelName,
elementMetadata);
innerBindingContext.ValueProvider = new CompositeValueProvider
{
ValueProvider = new CompositeValueProvider
{
// our temporary provider goes at the front of the list
new ElementalValueProvider(bindingContext.ModelName, rawValueElement, culture),
bindingContext.ValueProvider
}
// our temporary provider goes at the front of the list
new ElementalValueProvider(bindingContext.ModelName, rawValueElement, culture),
bindingContext.ValueProvider
};
object boundValue = null;
@ -105,7 +104,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
foreach (var indexName in indexNames)
{
var fullChildName = ModelBindingHelper.CreateIndexModelName(bindingContext.ModelName, indexName);
var childBindingContext = new ModelBindingContext(bindingContext, fullChildName, elementMetadata);
var childBindingContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
fullChildName,
elementMetadata);
var didBind = false;
object boundValue = null;

View File

@ -22,13 +22,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var dto = (ComplexModelDto)bindingContext.Model;
foreach (var propertyMetadata in dto.PropertyMetadata)
{
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ModelName,
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ModelName,
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
var propertyBindingContext = new ModelBindingContext(bindingContext,
propertyModelName,
propertyMetadata);
var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
propertyModelName,
propertyMetadata);
// bind and propagate the values
// If we can't bind then leave the result missing (don't add a null).

View File

@ -117,6 +117,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ValueProvider = oldBindingContext.ValueProvider,
OperationBindingContext = oldBindingContext.OperationBindingContext,
PropertyFilter = oldBindingContext.PropertyFilter,
BinderModelName = oldBindingContext.BinderModelName,
BindingSource = oldBindingContext.BindingSource,
BinderType = oldBindingContext.BinderType,
};
newBindingContext.OperationBindingContext.BodyBindingState = GetBodyBindingState(oldBindingContext);
@ -139,7 +142,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// public IActionResult UpdatePerson([FromForm] Person person) { }
//
// In this example, [FromQuery] overrides the ambient data source (form).
var bindingSource = oldBindingContext.ModelMetadata.BindingSource;
var bindingSource = oldBindingContext.BindingSource;
if (bindingSource != null && !bindingSource.IsGreedy)
{
var valueProvider =
@ -155,7 +158,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private static BodyBindingState GetBodyBindingState(ModelBindingContext oldBindingContext)
{
var bindingSource = oldBindingContext.ModelMetadata.BindingSource;
var bindingSource = oldBindingContext.BindingSource;
var willReadBodyWithFormatter = bindingSource == BindingSource.Body;
var willReadBodyAsFormData = bindingSource == BindingSource.Form;

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelMetadata = bindingContext.ModelMetadata;
// Property name can be null if the model metadata represents a type (rather than a property or parameter).
var headerName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName;
var headerName = bindingContext.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName;
object model = null;
if (bindingContext.ModelType == typeof(string))
{

View File

@ -54,8 +54,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel));
var propertyModelName =
ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName);
var propertyBindingContext =
new ModelBindingContext(parentBindingContext, propertyModelName, propertyModelMetadata);
var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
parentBindingContext,
propertyModelName,
propertyModelMetadata);
propertyBindingContext.BinderModelName = propertyModelMetadata.BinderModelName;
var modelBindingResult =
await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext);
if (modelBindingResult != null)

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -53,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
var bindingContext = context.ModelBindingContext;
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
var hasExplicitAlias = bindingContext.ModelMetadata.BinderModelName != null;
var hasExplicitAlias = bindingContext.BinderModelName != null;
// If we get here the model is a complex object which was not directly bound by any previous model binder,
// so we want to decide if we want to continue binding. This is important to get right to avoid infinite
@ -67,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
//
// 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.)
var bindingSource = bindingContext.ModelMetadata.BindingSource;
var bindingSource = bindingContext.BindingSource;
if (!isTopLevelObject &&
bindingSource != null &&
bindingSource.IsGreedy)
@ -144,8 +145,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
isAnyPropertyEnabledForValueProviderBasedBinding = true;
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
context.ModelBindingContext.ModelName,
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
var propertyModelBindingContext = ModelBindingContext.GetChildModelBindingContext(
context.ModelBindingContext,
propertyModelName,
propertyMetadata);
// If any property can return a true value.
if (await CanBindValue(context.ModelBindingContext, propertyMetadata))
if (await CanBindValue(propertyModelBindingContext))
{
return true;
}
@ -164,11 +174,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return false;
}
private async Task<bool> CanBindValue(ModelBindingContext bindingContext, ModelMetadata metadata)
private async Task<bool> CanBindValue(ModelBindingContext bindingContext)
{
var valueProvider = bindingContext.ValueProvider;
var bindingSource = metadata.BindingSource;
var bindingSource = bindingContext.BindingSource;
if (bindingSource != null && !bindingSource.IsGreedy)
{
var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
@ -178,11 +188,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ModelName,
metadata.BinderModelName ?? metadata.PropertyName);
if (await valueProvider.ContainsPrefixAsync(propertyModelName))
if (await valueProvider.ContainsPrefixAsync(bindingContext.ModelName))
{
return true;
}
@ -248,13 +254,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var dtoMetadata = metadataProvider.GetMetadataForType(typeof(ComplexModelDto));
var childContext = new ModelBindingContext(
var childContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
bindingContext.ModelName,
dtoMetadata)
{
Model = dto,
};
dtoMetadata);
childContext.Model = dto;
return await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext);
}

View File

@ -0,0 +1,124 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// Binding info which represents metadata associated to an action parameter.
/// </summary>
public class BindingInfo
{
/// <summary>
/// Gets or sets the <see cref="ModelBinding.BindingSource"/>.
/// </summary>
public BindingSource BindingSource { get; set; }
/// <summary>
/// Gets or sets the binder model name.
/// </summary>
public string BinderModelName { get; set; }
/// <summary>
/// Gets or sets the <see cref="Type"/> of the model binder used to bind the model.
/// </summary>
public Type BinderType { get; set; }
/// <summary>
/// Gets or sets the <see cref="ModelBinding.IPropertyBindingPredicateProvider"/>.
/// </summary>
public IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; set; }
/// <summary>
/// Constructs a new instance of <see cref="BindingInfo"/> from the given <paramref name="attributes"/>.
/// </summary>
/// <param name="attributes">A collection of attributes which are used to construct <see cref="BindingInfo"/>
/// </param>
/// <returns>A new instance of <see cref="BindingInfo"/>.</returns>
public static BindingInfo GetBindingInfo(IEnumerable<object> attributes)
{
var bindingInfo = new BindingInfo();
// BinderModelName
foreach (var binderModelNameAttribute in attributes.OfType<IModelNameProvider>())
{
if (binderModelNameAttribute?.Name != null)
{
bindingInfo.BinderModelName = binderModelNameAttribute.Name;
break;
}
}
// BinderType
foreach (var binderTypeAttribute in attributes.OfType<IBinderTypeProviderMetadata>())
{
if (binderTypeAttribute.BinderType != null)
{
bindingInfo.BinderType = binderTypeAttribute.BinderType;
break;
}
}
// BindingSource
foreach (var bindingSourceAttribute in attributes.OfType<IBindingSourceMetadata>())
{
if (bindingSourceAttribute.BindingSource != null)
{
bindingInfo.BindingSource = bindingSourceAttribute.BindingSource;
break;
}
}
// PropertyBindingPredicateProvider
var predicateProviders = attributes.OfType<IPropertyBindingPredicateProvider>().ToArray();
if (predicateProviders.Length > 0)
{
bindingInfo.PropertyBindingPredicateProvider = new CompositePredicateProvider(
predicateProviders);
}
return bindingInfo;
}
private class CompositePredicateProvider : IPropertyBindingPredicateProvider
{
private readonly IEnumerable<IPropertyBindingPredicateProvider> _providers;
public CompositePredicateProvider(IEnumerable<IPropertyBindingPredicateProvider> providers)
{
_providers = providers;
}
public Func<ModelBindingContext, string, bool> PropertyFilter
{
get
{
return CreatePredicate();
}
}
private Func<ModelBindingContext, string, bool> CreatePredicate()
{
var predicates = _providers
.Select(p => p.PropertyFilter)
.Where(p => p != null);
return (context, propertyName) =>
{
foreach (var predicate in predicates)
{
if (!predicate(context, propertyName))
{
return false;
}
}
return true;
};
}
}
}
}

View File

@ -13,7 +13,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
ModelMetadata GetMetadataForType([NotNull] Type modelType);
IEnumerable<ModelMetadata> GetMetadataForProperties([NotNull] Type modelType);
ModelMetadata GetMetadataForParameter([NotNull] ParameterInfo parameter, IEnumerable<object> attributes);
}
}

View File

@ -16,17 +16,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// <inheritdoc />
public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context)
{
// For Model Name - we only use the first attribute we find. An attribute on the parameter
// is considered an override of an attribute on the type. This is for compatibility with [Bind]
// from MVC 5.
//
// BinderType and BindingSource fall back to the first attribute to provide a value.
// BinderModelName
var binderModelNameAttribute = context.Attributes.OfType<IModelNameProvider>().FirstOrDefault();
if (binderModelNameAttribute?.Name != null)
foreach (var binderModelNameAttribute in context.Attributes.OfType<IModelNameProvider>())
{
context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name;
if (binderModelNameAttribute?.Name != null)
{
context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name;
break;
}
}
// BinderType

View File

@ -15,7 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
public class DefaultModelMetadataProvider : IModelMetadataProvider
{
private readonly TypeCache _typeCache = new TypeCache();
private readonly ParameterCache _parameterCache = new ParameterCache();
private readonly PropertiesCache _propertiesCache = new PropertiesCache();
/// <summary>
@ -32,23 +31,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// </summary>
protected ICompositeMetadataDetailsProvider DetailsProvider { get; }
/// <inheritdoc />
public virtual ModelMetadata GetMetadataForParameter(
[NotNull] ParameterInfo parameterInfo,
[NotNull] IEnumerable<object> attributes)
{
var key = ModelMetadataIdentity.ForParameter(parameterInfo);
DefaultMetadataDetailsCache entry;
if (!_parameterCache.TryGetValue(key, out entry))
{
entry = CreateParameterCacheEntry(key, attributes);
entry = _parameterCache.GetOrAdd(key, entry);
}
return CreateModelMetadata(entry);
}
/// <inheritdoc />
public virtual IEnumerable<ModelMetadata> GetMetadataForProperties([NotNull]Type modelType)
{
@ -152,37 +134,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
return new DefaultMetadataDetailsCache(key, attributes);
}
/// <summary>
/// Creates the <see cref="DefaultMetadataDetailsCache"/> entry for a parameter.
/// </summary>
/// <param name="key">
/// The <see cref="ModelMetadataIdentity"/> identifying the model parameter.
/// </param>
/// <param name="attributes">
/// The set of model attributes applied to the parameter.
/// </param>
/// <returns>A cache object for the model parameter.</returns>
/// <remarks>
/// The results of this method will be cached and used to satisfy calls to
/// <see cref="GetMetadataForParameter(ParameterInfo, IEnumerable{object}))"/>.
/// Override this method to provide a different set of attributes.
/// </remarks>
protected virtual DefaultMetadataDetailsCache CreateParameterCacheEntry(
[NotNull] ModelMetadataIdentity key,
[NotNull] IEnumerable<object> attributes)
{
var allAttributes = new List<object>();
if (attributes != null)
{
allAttributes.AddRange(attributes);
}
allAttributes.AddRange(ModelAttributes.GetAttributesForParameter(key.ParameterInfo));
return new DefaultMetadataDetailsCache(key, allAttributes);
}
private class TypeCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache>
{
}
@ -190,9 +141,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
private class PropertiesCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache[]>
{
}
private class ParameterCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache>
{
}
}
}

View File

@ -15,22 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
public static class ModelAttributes
{
/// <summary>
/// Gets the attributes for the given <paramref name="parameter"/>.
/// </summary>
/// <param name="parameter">A <see cref="ParameterInfo"/> for which attributes need to be resolved.
/// </param>
/// <returns>An <see cref="IEnumerable{object}"/> containing the attributes on the
/// <paramref name="parameter"/> before the attributes on the <paramref name="parameter"/> type.</returns>
public static IEnumerable<object> GetAttributesForParameter(ParameterInfo parameter)
{
// Return the parameter attributes first.
var parameterAttributes = parameter.GetCustomAttributes();
var typeAttributes = parameter.ParameterType.GetTypeInfo().GetCustomAttributes();
return parameterAttributes.Concat(typeAttributes);
}
/// <summary>
/// Gets the attributes for the given <paramref name="property"/>.
/// </summary>

View File

@ -89,11 +89,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
{
get
{
if (ParameterInfo != null)
{
return ModelMetadataKind.Parameter;
}
else if (ContainerType != null && Name != null)
if (ContainerType != null && Name != null)
{
return ModelMetadataKind.Property;
}

View File

@ -17,10 +17,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// Used for <see cref="ModelMetadata"/> for a property.
/// </summary>
Property,
/// <summary>
/// Used for <see cref="ModelMetadata"/> for a parameter.
/// </summary>
Parameter,
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -28,26 +29,57 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
/// <summary>
/// Initializes a new instance of the <see cref="ModelBindingContext"/> class using the
/// Constructs a new instance of the <see cref="ModelBindingContext"/> class using the
/// <paramref name="bindingContext" />.
/// </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>
/// This constructor copies certain values that won't change between parent and child objects,
/// e.g. ValueProvider, ModelState
/// </remarks>
public ModelBindingContext([NotNull] ModelBindingContext bindingContext,
[NotNull] string modelName,
[NotNull] ModelMetadata modelMetadata)
public static ModelBindingContext GetChildModelBindingContext(
[NotNull] ModelBindingContext bindingContext,
[NotNull] string modelName,
[NotNull] ModelMetadata modelMetadata)
{
ModelName = modelName;
ModelMetadata = modelMetadata;
ModelState = bindingContext.ModelState;
ValueProvider = bindingContext.ValueProvider;
OperationBindingContext = bindingContext.OperationBindingContext;
var modelBindingContext = new ModelBindingContext();
modelBindingContext.ModelName = modelName;
modelBindingContext.ModelMetadata = modelMetadata;
modelBindingContext.ModelState = bindingContext.ModelState;
modelBindingContext.ValueProvider = bindingContext.ValueProvider;
modelBindingContext.OperationBindingContext = bindingContext.OperationBindingContext;
modelBindingContext.BindingSource = modelMetadata.BindingSource;
modelBindingContext.BinderModelName = modelMetadata.BinderModelName;
modelBindingContext.BinderType = modelMetadata.BinderType;
return modelBindingContext;
}
/// <summary>
/// Constructs a new instance of <see cref="ModelBindingContext"/> from given <paramref name="metadata"/>
/// and <paramref name="bindingInfo"/>.
/// </summary>
/// <param name="metadata"><see cref="ModelMetadata"/> associated with the model.</param>
/// <param name="bindingInfo"><see cref="BindingInfo"/> associated with the model.</param>
/// <param name="modelName">An optional name of the model to be used.</param>
/// <returns>A new instance of <see cref="ModelBindingContext"/>.</returns>
public static ModelBindingContext GetModelBindingContext(
[NotNull] ModelMetadata metadata,
[NotNull] BindingInfo bindingInfo,
string modelName)
{
var binderModelName = bindingInfo.BinderModelName ?? metadata.BinderModelName;
var propertyPredicateProvider =
bindingInfo.PropertyBindingPredicateProvider ?? metadata.PropertyBindingPredicateProvider;
return new ModelBindingContext()
{
ModelMetadata = metadata,
BindingSource = bindingInfo.BindingSource ?? metadata.BindingSource,
PropertyFilter = propertyPredicateProvider?.PropertyFilter,
BinderType = bindingInfo.BinderType ?? metadata.BinderType,
BinderModelName = binderModelName,
ModelName = binderModelName ?? metadata.PropertyName ?? modelName,
FallbackToEmptyPrefix = binderModelName == null,
};
}
/// <summary>
@ -118,6 +150,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
}
/// <summary>
/// Gets or sets a model name which is explicitly set using an <see cref="IModelNameProvider"/>.
/// <see cref="Model"/>.
/// </summary>
public string BinderModelName { get; set; }
/// <summary>
/// Gets or sets a value which represents the <see cref="BindingSource"/> associated with the
/// <see cref="Model"/>.
/// </summary>
public BindingSource BindingSource { get; set; }
/// <summary>
/// Gets the <see cref="Type"/> of an <see cref="IModelBinder"/> associated with the
/// <see cref="Model"/>.
/// </summary>
public Type BinderType { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether the binder should use an empty prefix to look up
/// values in <see cref="IValueProvider"/> when no values are found using the

View File

@ -2,6 +2,7 @@
// 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.Linq;
using System.Web.Http;
using Microsoft.AspNet.Mvc.ApplicationModels;
@ -15,30 +16,42 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
if (IsConventionApplicable(action.Controller))
{
var optionalParameters = new HashSet<string>();
var uriBindingSource = (new FromUriAttribute()).BindingSource;
foreach (var parameter in action.Parameters)
{
if (parameter.BinderMetadata is IBinderMetadata)
// Some IBindingSourceMetadata attributes like ModelBinder attribute return null
// as their binding source. Special case to ensure we do not ignore them.
if (parameter.BindingInfo.BindingSource != null ||
parameter.Attributes.OfType<IBindingSourceMetadata>().Any())
{
// This has a binding behavior configured, just leave it alone.
}
else if (ValueProviderResult.CanConvertFromString(parameter.ParameterInfo.ParameterType))
{
// Simple types are by-default from the URI.
parameter.BinderMetadata = new FromUriAttribute();
parameter.BindingInfo.BindingSource = uriBindingSource;
}
else
{
// Complex types are by-default from the body.
parameter.BinderMetadata = new FromBodyAttribute();
parameter.BindingInfo.BindingSource = BindingSource.Body;
}
// If the parameter has a default value, we want to consider it as optional parameter by default.
var optionalMetadata = parameter.BinderMetadata as FromUriAttribute;
if (parameter.ParameterInfo.HasDefaultValue && optionalMetadata != null)
// For all non IOptionalBinderMetadata, which are not URL source (like FromQuery etc.) do not
// participate in overload selection and hence are added to the hashset so that they can be
// ignored in OverloadActionConstraint.
var optionalMetadata = parameter.Attributes.OfType<IOptionalBinderMetadata>().SingleOrDefault();
if (parameter.ParameterInfo.HasDefaultValue && parameter.BindingInfo.BindingSource == uriBindingSource ||
optionalMetadata != null && optionalMetadata.IsOptional ||
optionalMetadata == null && parameter.BindingInfo.BindingSource != uriBindingSource)
{
optionalMetadata.IsOptional = true;
optionalParameters.Add(parameter.ParameterName);
}
}
action.Properties.Add("OptionalParameters", optionalParameters);
}
}

View File

@ -87,12 +87,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
}
var parameters = new List<OverloadedParameter>();
object optionalParametersObject;
candidate.Action.Properties.TryGetValue("OptionalParameters", out optionalParametersObject);
var optionalParameters = (HashSet<string>)optionalParametersObject;
foreach (var parameter in candidate.Action.Parameters)
{
// We only consider parameters that are marked as bound from the URL.
var bindingSourceMetadata = parameter?.BinderMetadata as IBindingSourceMetadata;
var source = bindingSourceMetadata?.BindingSource;
var source = parameter.BindingInfo.BindingSource;
if (source == null)
{
continue;
@ -102,17 +103,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
source.CanAcceptDataFrom(BindingSource.Query)) &&
ValueProviderResult.CanConvertFromString(parameter.ParameterType))
{
var optionalMetadata = parameter.BinderMetadata as IOptionalBinderMetadata;
if (optionalMetadata == null || optionalMetadata.IsOptional)
if (optionalParameters != null)
{
// Optional parameters are ignored in overloading. If a parameter doesn't specify that it's
// required then treat it as optional (MVC default). WebAPI parameters will all by-default
// specify themselves as required unless they have a default value.
continue;
var isOptional = optionalParameters.Contains(parameter.Name);
if (isOptional)
{
// Optional parameters are ignored in overloading. If a parameter doesn't specify that it's
// required then treat it as optional (MVC default). WebAPI parameters will all by-default
// specify themselves as required unless they have a default value.
continue;
}
}
var nameProvider = parameter.BinderMetadata as IModelNameProvider;
var prefix = nameProvider?.Name ?? parameter.Name;
var prefix = parameter.BindingInfo.BinderModelName ?? parameter.Name;
parameters.Add(new OverloadedParameter()
{

View File

@ -4,13 +4,13 @@
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// An <see cref="IBinderMetadata"/> that designates an optional parameter for the purposes
/// An type that designates an optional parameter for the purposes
/// of WebAPI action overloading. Optional parameters do not participate in overloading, and
/// do not have to have a value for the action to be selected.
///
/// This has no impact when used without WebAPI action overloading.
/// </summary>
public interface IOptionalBinderMetadata : IBinderMetadata
public interface IOptionalBinderMetadata
{
bool IsOptional { get; }
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Xunit;
namespace Microsoft.AspNet.Mvc.ApplicationModels
@ -18,7 +19,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
new List<object>() { new FromBodyAttribute() });
parameter.Action = new ActionModel(typeof(TestController).GetMethod("Edit"), new List<object>());
parameter.BinderMetadata = (IBinderMetadata)parameter.Attributes[0];
parameter.BindingInfo = new BindingInfo()
{
BindingSource = BindingSource.Body
};
parameter.ParameterName = "id";
// Act

View File

@ -95,6 +95,7 @@ namespace Microsoft.AspNet.Mvc
provider.ForType<Person>().BindingDetails(d => d.BindingSource = BindingSource.Header);
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
bindingContext.BindingSource = BindingSource.Header;
var binder = bindingContext.OperationBindingContext.ModelBinder;
@ -113,6 +114,7 @@ namespace Microsoft.AspNet.Mvc
provider.ForType<Person>().BindingDetails(d => d.BindingSource = null);
var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider);
bindingContext.BindingSource = null;
var binder = bindingContext.OperationBindingContext.ModelBinder;
@ -186,6 +188,7 @@ namespace Microsoft.AspNet.Mvc
ValueProvider = Mock.Of<IValueProvider>(),
ModelState = new ModelStateDictionary(),
OperationBindingContext = operationBindingContext,
BindingSource = BindingSource.Body,
};
return bindingContext;

View File

@ -8,6 +8,7 @@ using System.Reflection;
using Microsoft.AspNet.Mvc.ApplicationModels;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Framework.Internal;
using Moq;
@ -110,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.Test
var id = Assert.Single(main.Parameters);
Assert.Equal("id", id.Name);
Assert.Null(id.BinderMetadata);
Assert.Null(id.BindingInfo.BindingSource);
Assert.Equal(typeof(int), id.ParameterType);
}
@ -129,13 +130,13 @@ namespace Microsoft.AspNet.Mvc.Test
var id = Assert.Single(main.Parameters, p => p.Name == "id");
Assert.Equal("id", id.Name);
Assert.Null(id.BinderMetadata);
Assert.Null(id.BindingInfo.BindingSource);
Assert.Equal(typeof(int), id.ParameterType);
var entity = Assert.Single(main.Parameters, p => p.Name == "entity");
Assert.Equal("entity", entity.Name);
Assert.IsType<FromBodyAttribute>(entity.BinderMetadata);
Assert.Equal(entity.BindingInfo.BindingSource, BindingSource.Body);
Assert.Equal(typeof(TestActionParameter), entity.ParameterType);
}
@ -154,19 +155,19 @@ namespace Microsoft.AspNet.Mvc.Test
var id = Assert.Single(main.Parameters, p => p.Name == "id");
Assert.Equal("id", id.Name);
Assert.Null(id.BinderMetadata);
Assert.Null(id.BindingInfo.BindingSource);
Assert.Equal(typeof(int), id.ParameterType);
var upperCaseId = Assert.Single(main.Parameters, p => p.Name == "ID");
Assert.Equal("ID", upperCaseId.Name);
Assert.Null(upperCaseId.BinderMetadata);
Assert.Null(upperCaseId.BindingInfo.BindingSource);
Assert.Equal(typeof(int), upperCaseId.ParameterType);
var pascalCaseId = Assert.Single(main.Parameters, p => p.Name == "Id");
Assert.Equal("Id", pascalCaseId.Name);
Assert.Null(id.BinderMetadata);
Assert.Null(id.BindingInfo.BindingSource);
Assert.Equal(typeof(int), pascalCaseId.ParameterType);
}
@ -187,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Test
var entity = Assert.Single(fromBody.Parameters);
Assert.Equal("entity", entity.Name);
Assert.IsType<FromBodyAttribute>(entity.BinderMetadata);
Assert.Equal(entity.BindingInfo.BindingSource, BindingSource.Body);
Assert.Equal(typeof(TestActionParameter), entity.ParameterType);
}
@ -208,7 +209,7 @@ namespace Microsoft.AspNet.Mvc.Test
var entity = Assert.Single(notFromBody.Parameters);
Assert.Equal("entity", entity.Name);
Assert.Null(entity.BinderMetadata);
Assert.Null(entity.BindingInfo.BindingSource);
Assert.Equal(typeof(TestActionParameter), entity.ParameterType);
}

View File

@ -12,6 +12,7 @@ using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing;
using Microsoft.Framework.DependencyInjection;
@ -2073,6 +2074,7 @@ namespace Microsoft.AspNet.Mvc
{
Name = "value",
ParameterType = typeof(int),
BindingInfo = new BindingInfo(),
}
},
FilterDescriptors = new List<FilterDescriptor>()
@ -2106,11 +2108,11 @@ namespace Microsoft.AspNet.Mvc
metadataProvider,
new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider),
new MockMvcOptionsAccessor()),
new MockModelBinderProvider() { ModelBinders = new List<IModelBinder>() { binder.Object } },
new MockModelValidatorProviderProvider(),
new MockValueProviderFactoryProvider(),
new MockScopedInstance<ActionBindingContext>(),
Mock.Of<ITempDataDictionary>());
new MockModelBinderProvider() { ModelBinders = new List<IModelBinder>() { binder.Object } },
new MockModelValidatorProviderProvider(),
new MockValueProviderFactoryProvider(),
new MockScopedInstance<ActionBindingContext>(),
Mock.Of<ITempDataDictionary>());
// Act
await invoker.InvokeAsync();

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Constraints;
@ -964,9 +965,9 @@ namespace Microsoft.AspNet.Mvc.Description
{
action.Parameters.Add(new ParameterDescriptor()
{
BinderMetadata = parameter.GetCustomAttributes().OfType<IBinderMetadata>().FirstOrDefault(),
Name = parameter.Name,
ParameterType = parameter.ParameterType,
BindingInfo = BindingInfo.GetBindingInfo(parameter.GetCustomAttributes().OfType<object>())
});
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Routing;
using Moq;
using Xunit;
@ -48,109 +49,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
}
[Fact]
public void GetModelBindingContext_ReturnsOnlyIncludedProperties_UsingBindAttributeInclude()
{
// Arrange
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelMetadata = metadataProvider.GetMetadataForType(
typeof(TypeWithIncludedPropertiesUsingBindAttribute));
// Act
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
modelMetadata,
new ModelStateDictionary(),
Mock.Of<OperationBindingContext>());
// Assert
Assert.True(context.PropertyFilter(context, "IncludedExplicitly1"));
Assert.True(context.PropertyFilter(context, "IncludedExplicitly2"));
}
[Fact]
public void GetModelBindingContext_UsesBindAttributeOnType_IfNoBindAttributeOnParameter_ForPrefix()
{
// Arrange
var type = typeof(ControllerActionArgumentBinderTests);
var methodInfo = type.GetMethod("ParameterWithNoBindAttribute");
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter").Single();
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelMetadata = metadataProvider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Act
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
modelMetadata,
new ModelStateDictionary(),
Mock.Of<OperationBindingContext>());
// Assert
Assert.Equal("TypePrefix", context.ModelName);
}
[Theory]
[InlineData("ParameterHasFieldPrefix", false, "simpleModelPrefix")]
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
[InlineData("ParameterHasPrefixAndComplexType", false, "simpleModelPrefix")]
[InlineData("ParameterHasEmptyBindAttribute", true, "parameter")]
public void GetModelBindingContext_ModelBindingContextIsSetWithModelName_ForParameters(
string actionMethodName,
bool expectedFallToEmptyPrefix,
string expectedModelName)
{
// Arrange
var type = typeof(ControllerActionArgumentBinderTests);
var methodInfo = type.GetMethod(actionMethodName);
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter").Single();
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelMetadata = metadataProvider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Act
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
modelMetadata,
new ModelStateDictionary(),
Mock.Of<OperationBindingContext>());
// Assert
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
Assert.Equal(expectedModelName, context.ModelName);
}
[Theory]
[InlineData("ParameterHasEmptyFieldPrefix", false, "")]
[InlineData("ParameterHasPrefixAndComplexType", false, "simpleModelPrefix")]
[InlineData("ParameterHasEmptyBindAttribute", true, "parameter1")]
public void GetModelBindingContext_ModelBindingContextIsNotSet_ForTypes(
string actionMethodName,
bool expectedFallToEmptyPrefix,
string expectedModelName)
{
// Arrange
var type = typeof(ControllerActionArgumentBinderTests);
var methodInfo = type.GetMethod(actionMethodName);
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter1").Single();
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelMetadata = metadataProvider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Act
var context = DefaultControllerActionArgumentBinder.GetModelBindingContext(
modelMetadata,
new ModelStateDictionary(),
Mock.Of<OperationBindingContext>());
// Assert
Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix);
Assert.Equal(expectedModelName, context.ModelName);
}
[Fact]
public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsFalse()
{
@ -165,6 +63,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
Name = "foo",
ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
}
}
};
@ -215,6 +114,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
Name = "foo",
ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
}
}
};
@ -266,6 +166,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
Name = "foo",
ParameterType = typeof(string),
BindingInfo = new BindingInfo(),
}
},
};
@ -322,6 +223,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
Name = "foo",
ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
}
}
};
@ -375,6 +277,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
Name = "foo",
ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
}
}
};
@ -423,6 +326,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
Name = "foo",
ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
}
}
};

View File

@ -898,7 +898,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
}
[Fact]
public async Task BindAttribute_DoesNotUseTypePrefix()
public async Task BindAttribute_FallsBackOnTypePrefixIfNoParameterPrefixIsProvided()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
@ -906,24 +906,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"TypePrefixIsNeverUsed" +
"?param.Value=someValue");
// Assert
Assert.Equal("someValue", response);
}
[Fact]
public async Task BindAttribute_FallsBackOnEmptyPrefixIfNoParameterPrefixIsProvided()
{
// Arrange
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var response = await client.GetStringAsync("http://localhost/BindAttribute/" +
"TypePrefixIsNeverUsed" +
"?Value=someValue");
"TypePrefixIsUsed" +
"?TypePrefix.Value=someValue");
// Assert
Assert.Equal("someValue", response);

View File

@ -133,6 +133,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
ValueProvider = Mock.Of<IValueProvider>(),
ModelState = new ModelStateDictionary(),
OperationBindingContext = operationBindingContext,
BinderType = binderType
};
return bindingContext;

View File

@ -74,9 +74,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Arrange
var provider = new TestModelMetadataProvider();
provider.ForType<string>().BindingDetails(d => d.BindingSource = BindingSource.Body);
var context = new ModelBindingContext();
context.ModelMetadata = provider.GetMetadataForType(typeof(string));
var modelMetadata = provider.GetMetadataForType(typeof(string));
var context = new ModelBindingContext()
{
ModelMetadata = modelMetadata,
BindingSource = modelMetadata.BindingSource,
BinderModelName = modelMetadata.BinderModelName
};
var binder = new TestableBindingSourceModelBinder(BindingSource.Body);

View File

@ -76,17 +76,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
var metadataProvider = new TestModelMetadataProvider();
metadataProvider.ForType(modelType).BindingDetails(d => d.BindingSource = BindingSource.Header);
var modelMetadata = metadataProvider.GetMetadataForType(modelType);
var bindingContext = new ModelBindingContext
{
ModelMetadata = metadataProvider.GetMetadataForType(modelType),
ModelMetadata = modelMetadata,
ModelName = "modelName",
OperationBindingContext = new OperationBindingContext
{
ModelBinder = new HeaderModelBinder(),
MetadataProvider = metadataProvider,
HttpContext = new DefaultHttpContext()
}
},
BinderModelName = modelMetadata.BinderModelName,
BindingSource = modelMetadata.BindingSource,
};
return bindingContext;

View File

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
@ -8,24 +10,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public class ModelBindingContextTest
{
[Fact]
public void CopyConstructor()
public void GetChildModelBindingContext()
{
// Arrange
var originalBindingContext = new ModelBindingContext
{
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)),
ModelMetadata = new TestModelMetadataProvider().GetMetadataForType(typeof(object)),
ModelName = "theName",
ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleHttpValueProvider()
};
var newModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(object));
var metadataProvider = new TestModelMetadataProvider();
metadataProvider.ForType<object>().BindingDetails(d =>
{
d.BindingSource = BindingSource.Custom;
d.BinderType = typeof(TestModelBinder);
d.BinderModelName = "custom";
});
var newModelMetadata = metadataProvider.GetMetadataForType(typeof(object));
// Act
var newBindingContext = new ModelBindingContext(originalBindingContext, string.Empty, newModelMetadata);
var newBindingContext = ModelBindingContext.GetChildModelBindingContext(
originalBindingContext,
string.Empty,
newModelMetadata);
// Assert
Assert.Same(newModelMetadata, newBindingContext.ModelMetadata);
Assert.Same(newModelMetadata.BindingSource, newBindingContext.BindingSource);
Assert.Same(newModelMetadata.BinderModelName, newBindingContext.BinderModelName);
Assert.Same(newModelMetadata.BinderType, newBindingContext.BinderType);
Assert.Equal("", newBindingContext.ModelName);
Assert.Equal(originalBindingContext.ModelState, newBindingContext.ModelState);
Assert.Equal(originalBindingContext.ValueProvider, newBindingContext.ValueProvider);
@ -43,5 +59,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert
Assert.Equal(typeof(int), bindingContext.ModelType);
}
private class TestModelBinder : IModelBinder
{
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -32,11 +32,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
.Returns(Task.FromResult(false));
var metadataProvider = new TestModelMetadataProvider();
if (isPrefixProvided)
{
metadataProvider.ForType<Person>().BindingDetails(bd => bd.BinderModelName = "prefix");
}
var bindingContext = new MutableObjectBinderContext
{
ModelBindingContext = new ModelBindingContext
@ -53,6 +48,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Setting it to empty ensures that model does not get created becasue of no model name.
ModelName = "dummyModelName",
BinderModelName = isPrefixProvided ? "prefix" : null,
}
};
@ -110,6 +106,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact]
public async Task CanCreateModel_ReturnsFalse_ForNonTopLevelModel_IfModelIsMarkedWithBinderMetadata()
{
var modelMetadata = GetMetadataForType(typeof(Document))
.Properties
.First(metadata => metadata.PropertyName == nameof(Document.SubDocument));
var bindingContext = new MutableObjectBinderContext
{
ModelBindingContext = new ModelBindingContext
@ -121,7 +120,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
OperationBindingContext = new OperationBindingContext
{
ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
}
},
BindingSource = modelMetadata.BindingSource,
BinderModelName = modelMetadata.BinderModelName,
}
};
@ -265,12 +266,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return null;
});
var modelMetadata = GetMetadataForType(modelType);
var bindingContext = new MutableObjectBinderContext
{
ModelBindingContext = new ModelBindingContext
{
ModelMetadata = GetMetadataForType(modelType),
ModelMetadata = modelMetadata,
ValueProvider = mockValueProvider.Object,
OperationBindingContext = new OperationBindingContext
{
@ -280,7 +282,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
},
// Setting it to empty ensures that model does not get created becasue of no model name.
ModelName = "dummyName"
ModelName = "dummyName",
BindingSource = modelMetadata.BindingSource,
BinderModelName = modelMetadata.BinderModelName
}
};

View File

@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
}
[Fact]
public void GetBindingDetails_FindsModelName_IfEmpty()
public void GetBindingDetails_FindsModelName_IfNullFallsBack()
{
// Arrange
var attributes = new object[]
@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
provider.GetBindingMetadata(context);
// Assert
Assert.Null(context.BindingMetadata.BinderModelName);
Assert.Equal("Product", context.BindingMetadata.BinderModelName);
}
[Fact]

View File

@ -110,66 +110,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.Equal("OnPropertyType", Assert.IsType<ModelAttribute>(attributes[1]).Value);
}
[Fact]
public void GetMetadataForParameter_IncludesMergedAttributes()
{
// Arrange
var provider = CreateProvider();
var methodInfo = GetType().GetMethod(
"GetMetadataForParameterTestMethod",
BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "parameter").Single();
var additionalAttributes = new object[]
{
new ModelAttribute("Extra"),
};
// Act
var metadata = provider.GetMetadataForParameter(parameterInfo, additionalAttributes);
// Assert
var defaultMetadata = Assert.IsType<DefaultModelMetadata>(metadata);
var attributes = defaultMetadata.Attributes.ToArray();
Assert.Equal("Extra", Assert.IsType<ModelAttribute>(attributes[0]).Value);
Assert.Equal("OnParameter", Assert.IsType<ModelAttribute>(attributes[1]).Value);
Assert.Equal("OnType", Assert.IsType<ModelAttribute>(attributes[2]).Value);
}
// The 'attributes' are assumed to be the same every time. We can safely omit them after
// the first call.
[Fact]
public void GetMetadataForParameter_Cached()
{
// Arrange
var provider = CreateProvider();
var methodInfo = GetType().GetMethod(
"GetMetadataForParameterTestMethod",
BindingFlags.Instance | BindingFlags.NonPublic);
var additionalAttributes = new object[]
{
new ModelAttribute("Extra"),
};
// Act
var metadata1 = Assert.IsType<DefaultModelMetadata>(provider.GetMetadataForParameter(
methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(),
additionalAttributes));
var metadata2 = Assert.IsType<DefaultModelMetadata>(provider.GetMetadataForParameter(
methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(),
attributes: null));
// Assert
Assert.Same(metadata1.Attributes, metadata2.Attributes);
Assert.Same(metadata1.BindingMetadata, metadata2.BindingMetadata);
Assert.Same(metadata1.DisplayMetadata, metadata2.DisplayMetadata);
Assert.Same(metadata1.ValidationMetadata, metadata2.ValidationMetadata);
}
private static DefaultModelMetadataProvider CreateProvider()
{
return new DefaultModelMetadataProvider(new EmptyCompositeMetadataDetailsProvider());

View File

@ -43,103 +43,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.Equal<string>(expected, matched);
}
[Fact]
public void ModelMetadataProvider_UsesPredicateOnParameter()
{
// Arrange
var type = GetType();
var methodInfo = type.GetMethod(
"ActionWithoutBindAttribute",
BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "param").Single();
var provider = CreateProvider();
var context = new ModelBindingContext();
// Note it does an intersection for included -- only properties that
// pass both predicates will be bound.
var expected = new[] { "IsAdmin", "UserName" };
// Act
var metadata = provider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Assert
var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter;
Assert.NotNull(predicate);
var matched = new HashSet<string>();
foreach (var property in metadata.Properties)
{
if (predicate(context, property.PropertyName))
{
matched.Add(property.PropertyName);
}
}
Assert.Equal<string>(expected, matched);
}
[Fact]
public void ModelMetadataProvider_UsesPredicateOnParameter_Merge()
{
// Arrange
var type = GetType();
var methodInfo = type.GetMethod(
"ActionWithBindAttribute",
BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "param").Single();
var provider = CreateProvider();
var context = new ModelBindingContext();
// Note it does an intersection for included -- only properties that
// pass both predicates will be bound.
var expected = new[] { "IsAdmin" };
// Act
var metadata = provider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Assert
var predicate = metadata.PropertyBindingPredicateProvider.PropertyFilter;
Assert.NotNull(predicate);
var matched = new HashSet<string>();
foreach (var property in metadata.Properties)
{
if (predicate(context, property.PropertyName))
{
matched.Add(property.PropertyName);
}
}
Assert.Equal<string>(expected, matched);
}
[Fact]
public void ModelMetadataProvider_ReadsModelNameProperty_ForParameters()
{
// Arrange
var type = GetType();
var methodInfo = type.GetMethod(
"ActionWithBindAttribute",
BindingFlags.Instance | BindingFlags.NonPublic);
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "param").Single();
var provider = CreateProvider();
// Act
var metadata = provider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Assert
Assert.Equal("ParameterPrefix", metadata.BinderModelName);
}
[Fact]
public void ModelMetadataProvider_ReadsModelNameProperty_ForTypes()
{
@ -267,60 +170,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName);
}
[Fact]
public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType()
{
// Arrange
var provider = CreateProvider();
var methodInfo = typeof(Person).GetMethod("Update");
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single();
// Act
var parameterMetadata = provider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Assert
Assert.Equal("PersonType", parameterMetadata.BinderModelName);
}
[Fact]
public void GetMetadataForParameter_WithBinderDataOnParameterAndType_GetsMetadataFromParameter()
{
// Arrange
var provider = CreateProvider();
var methodInfo = typeof(Person).GetMethod("Save");
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single();
// Act
var parameterMetadata = provider.GetMetadataForParameter(
parameterInfo,
attributes: null);
// Assert
Assert.Equal("PersonParameter", parameterMetadata.BinderModelName);
}
[Fact]
public void GetMetadataForParameter_BinderModelNameOverride()
{
// Arrange
var provider = CreateProvider();
var methodInfo = typeof(Person).GetMethod("Save");
var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single();
// Act
var parameterMetadata = provider.GetMetadataForParameter(
parameterInfo,
attributes: new object[] { new ModelBinderAttribute() { Name = "Override" } });
// Assert
Assert.Equal("Override", parameterMetadata.BinderModelName);
}
public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeDataStrings
{
get
@ -780,12 +629,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
{
}
public class TypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider
public class TypeBasedBinderAttribute : Attribute, IModelNameProvider
{
public string Name { get; set; }
}
public class NonTypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider
public class NonTypeBasedBinderAttribute : Attribute, IModelNameProvider
{
public string Name { get; set; }
}

View File

@ -1,9 +0,0 @@
// 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
{
public class TestBinderMetadata : IBinderMetadata
{
}
}

View File

@ -26,6 +26,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
return string.Format(CultureInfo.CurrentCulture, GetString("CompareAttributeTestResource"), p0, p1);
}
/// <summary>
/// description from resources
/// </summary>
internal static string DisplayAttribute_Description
{
get { return GetString("DisplayAttribute_Description"); }
}
/// <summary>
/// description from resources
/// </summary>
internal static string FormatDisplayAttribute_Description()
{
return GetString("DisplayAttribute_Description");
}
/// <summary>
/// name from resources
/// </summary>
internal static string DisplayAttribute_Name
{
get { return GetString("DisplayAttribute_Name"); }
}
/// <summary>
/// name from resources
/// </summary>
internal static string FormatDisplayAttribute_Name()
{
return GetString("DisplayAttribute_Name");
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ApplicationModels;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.WebApiCompatShim;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
@ -276,8 +277,9 @@ namespace System.Web.Http
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
var metadata = Assert.IsType<FromUriAttribute>(parameter.BinderMetadata);
Assert.False(metadata.IsOptional);
Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource);
var optionalParameters = (HashSet<string>)action.Properties["OptionalParameters"];
Assert.False(optionalParameters.Contains(parameter.Name));
}
}
@ -304,12 +306,12 @@ namespace System.Web.Http
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
Assert.IsType<FromBodyAttribute>(parameter.BinderMetadata);
Assert.Equal(BindingSource.Body, parameter.BindingInfo.BindingSource);
}
}
[Fact]
public void GetActions_Parameters_BinderMetadata()
public void GetActions_Parameters_WithBindingSource()
{
// Arrange
var provider = CreateProvider();
@ -331,7 +333,7 @@ namespace System.Web.Http
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
Assert.IsType<ModelBinderAttribute>(parameter.BinderMetadata);
Assert.Null(parameter.BindingInfo.BindingSource);
}
}
@ -360,8 +362,9 @@ namespace System.Web.Http
foreach (var action in actions)
{
var parameter = Assert.Single(action.Parameters);
var metadata = Assert.IsType<FromUriAttribute>(parameter.BinderMetadata);
Assert.True(metadata.IsOptional);
Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource);
var optionalParameters = (HashSet<string>)action.Properties["OptionalParameters"];
Assert.True(optionalParameters.Contains(parameter.Name));
}
}

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Web.Http;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
using Xunit;
@ -21,7 +22,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
@ -51,13 +55,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
@ -87,13 +97,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
@ -123,13 +139,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
@ -159,18 +181,28 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute() { IsOptional = true },
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
};
var optionalParameters = new HashSet<string>();
optionalParameters.Add("quantity");
action.Properties.Add("OptionalParameters", optionalParameters);
var constraint = new OverloadActionConstraint();
var context = new ActionConstraintContext();
@ -195,13 +227,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
@ -212,13 +250,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity_ordered",
ParameterType = typeof(int),
},
@ -252,7 +296,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
@ -263,13 +310,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
@ -300,30 +353,46 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute() { IsOptional = true },
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
};
var optionalParameters = new HashSet<string>();
optionalParameters.Add("quantity");
action1.Properties.Add("OptionalParameters", optionalParameters);
var action2 = new ActionDescriptor();
action2.Parameters = new List<ParameterDescriptor>()
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
@ -354,13 +423,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
@ -371,13 +446,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "price",
ParameterType = typeof(decimal),
},
@ -411,7 +492,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
@ -422,18 +506,28 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute() { IsOptional = true },
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},
};
var optionalParameters = new HashSet<string>();
optionalParameters.Add("quantity");
action2.Properties.Add("OptionalParameters", optionalParameters);
var constraint = new OverloadActionConstraint();
var context = new ActionConstraintContext();
@ -459,7 +553,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
@ -470,13 +567,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{
new ParameterDescriptor()
{
BinderMetadata = new FromUriAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id",
ParameterType = typeof(int),
},
new ParameterDescriptor()
{
BinderMetadata = new FromBodyAttribute(),
BindingInfo = new BindingInfo()
{
BindingSource = (new FromBodyAttribute()).BindingSource,
},
Name = "quantity",
ParameterType = typeof(int),
},

View File

@ -16,19 +16,16 @@ namespace ApplicationModelWebSite
{
public string GetParameterMetadata([Cool] int? id)
{
return ActionContext.ActionDescriptor.Parameters[0].BinderMetadata.GetType().Name;
return ActionContext.ActionDescriptor.Parameters[0].BindingInfo.BinderModelName;
}
private class CoolAttribute : Attribute, IParameterModelConvention
{
public void Apply(ParameterModel model)
{
model.BinderMetadata = new CoolMetadata();
model.BindingInfo.BindingSource = BindingSource.Custom;
model.BindingInfo.BinderModelName = "CoolMetadata";
}
}
private class CoolMetadata : IBinderMetadata
{
}
}
}

View File

@ -6,7 +6,7 @@
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>94ba134d-04b3-48aa-ba55-5a4db8640f2d</ProjectGuid>
<ProjectGuid>b42d4844-fff8-4ec2-88d1-3ae95234d9eb</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>

View File

@ -15,7 +15,8 @@ namespace FormatterWebSite
{
var bodyParameter = context.ActionDescriptor
.Parameters
.FirstOrDefault(parameter => IsBodyBindingSource(parameter.BinderMetadata));
.FirstOrDefault(parameter => IsBodyBindingSource(
parameter.BindingInfo.BindingSource));
if (bodyParameter != null)
{
var parameterBindingErrors = context.ModelState[bodyParameter.Name].Errors;
@ -37,9 +38,8 @@ namespace FormatterWebSite
base.OnActionExecuting(context);
}
private bool IsBodyBindingSource(IBinderMetadata binderMetadata)
private bool IsBodyBindingSource(BindingSource bindingSource)
{
var bindingSource = (binderMetadata as IBindingSourceMetadata)?.BindingSource;
return bindingSource?.CanAcceptDataFrom(BindingSource.Body) ?? false;
}
}

View File

@ -65,8 +65,7 @@ namespace ModelBindingWebSite.Controllers
return param.Value;
}
// This will use param to try to bind and not the value specified at TypePrefix.
public string TypePrefixIsNeverUsed([Bind] TypePrefix param)
public string TypePrefixIsUsed([Bind] TypePrefix param)
{
return param.Value;
}

View File

@ -6,7 +6,14 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace ModelBindingWebSite
{
public class FromNonExistantBinderAttribute : Attribute, IBinderMetadata
public class FromNonExistantBinderAttribute : Attribute, IBindingSourceMetadata
{
public BindingSource BindingSource
{
get
{
return BindingSource.Custom;
}
}
}
}