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

View File

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

View File

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

View File

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

View File

@ -4,8 +4,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Template; using Microsoft.AspNet.Routing.Template;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
@ -127,7 +129,6 @@ namespace Microsoft.AspNet.Mvc.Description
return apiDescription; return apiDescription;
} }
private IList<ApiParameterDescription> GetParameters(ApiParameterContext context) private IList<ApiParameterDescription> GetParameters(ApiParameterContext context)
{ {
// First, get parameters from the model-binding/parameter-binding side of the world. // 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) foreach (var actionParameter in context.ActionDescriptor.Parameters)
{ {
var visitor = new PseudoModelBindingVisitor(context, actionParameter); 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; } 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 private class PseudoModelBindingVisitor
{ {
public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter) public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter)
@ -439,29 +472,20 @@ namespace Microsoft.AspNet.Mvc.Description
// Avoid infinite recursion by tracking properties. // Avoid infinite recursion by tracking properties.
private HashSet<PropertyKey> Visited { get; } 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 // Attempt to find a binding source for the parameter
// //
// The default is ModelBinding (aka all default value providers) // The default is ModelBinding (aka all default value providers)
var source = BindingSource.ModelBinding; 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 // 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 // likely 'model-bound' in the traditional MVC sense (formdata + query string + route data) and
// doesn't use any IBinderMetadata. // doesn't use any IBinderMetadata.
// //
// Add a single 'default' parameter description for the model. // 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 /// 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. /// description will be created for ALL of them.
/// </remarks> /// </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) if (source != null && source.IsGreedy)
{ {
// We have a definite answer for this model. This is a greedy source like // We have a definite answer for this model. This is a greedy source like
// [FromBody] so there's no need to consider properties. // [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; return true;
} }
var modelMetadata = bindingContext.ModelMetadata;
// For any property which is a leaf node, we don't want to keep traversing: // 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, // 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 // We found a new source, and this model has no properties. This is probabaly
// a simple type with an attribute like [FromQuery]. // a simple type with an attribute like [FromQuery].
Context.Results.Add(CreateResult(modelMetadata, source, containerName)); Context.Results.Add(CreateResult(bindingContext, source, containerName));
return true; return true;
} }
} }
@ -545,29 +574,33 @@ namespace Microsoft.AspNet.Mvc.Description
// Order - source: Body // 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. // We don't want to append the **parameter** name when building a model name.
var newContainerName = containerName; var newContainerName = containerName;
if (modelMetadata.ContainerType != null) if (modelMetadata.ContainerType != null)
{ {
newContainerName = GetName(containerName, modelMetadata); newContainerName = GetName(containerName, bindingContext);
} }
foreach (var propertyMetadata in modelMetadata.Properties) foreach (var propertyMetadata in modelMetadata.Properties)
{ {
var key = new PropertyKey(propertyMetadata, source); var key = new PropertyKey(propertyMetadata, source);
var propertyContext = ApiParameterDescriptionContext.GetContext(
propertyMetadata,
bindingInfo: null,
propertyName: null);
if (Visited.Add(key)) if (Visited.Add(key))
{ {
if (!Visit(propertyMetadata, source ?? ambientSource, newContainerName)) if (!Visit(propertyContext, source ?? ambientSource, newContainerName))
{ {
unboundProperties.Add(propertyMetadata); unboundProperties.Add(propertyContext);
} }
} }
else 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, // 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. // so create a result for the current object.
Context.Results.Add(CreateResult(modelMetadata, source, containerName)); Context.Results.Add(CreateResult(bindingContext, source, containerName));
return true; return true;
} }
} }
@ -600,20 +633,20 @@ namespace Microsoft.AspNet.Mvc.Description
} }
private ApiParameterDescription CreateResult( private ApiParameterDescription CreateResult(
ModelMetadata metadata, ApiParameterDescriptionContext bindingContext,
BindingSource source, BindingSource source,
string containerName) string containerName)
{ {
return new ApiParameterDescription() return new ApiParameterDescription()
{ {
ModelMetadata = metadata, ModelMetadata = bindingContext.ModelMetadata,
Name = GetName(containerName, metadata), Name = GetName(containerName, bindingContext),
Source = source, 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)) if (!string.IsNullOrEmpty(metadata.BinderModelName))
{ {

View File

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

View File

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

View File

@ -3,6 +3,7 @@
using System; using System;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -12,6 +13,6 @@ namespace Microsoft.AspNet.Mvc
public Type ParameterType { get; set; } 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> /// <summary>
/// Metadata which specificies the data source for model binding. /// Metadata which specificies the data source for model binding.
/// </summary> /// </summary>
public interface IBindingSourceMetadata : IBinderMetadata public interface IBindingSourceMetadata
{ {
/// <summary> /// <summary>
/// Gets the <see cref="BindingSource"/>. /// Gets the <see cref="BindingSource"/>.

View File

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

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <inheritdoc /> /// <inheritdoc />
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext context) public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext context)
{ {
var allowedBindingSource = context.ModelMetadata.BindingSource; var allowedBindingSource = context.BindingSource;
if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource)) if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource))
{ {
// Binding Sources are opt-in. This model either didn't specify one or specified something // 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); var rawValueArray = RawValueToObjectArray(rawValue);
foreach (var rawValueElement in rawValueArray) foreach (var rawValueElement in rawValueArray)
{ {
var innerBindingContext = new ModelBindingContext(bindingContext, var innerBindingContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext.ModelName, bindingContext,
elementMetadata) 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),
// our temporary provider goes at the front of the list bindingContext.ValueProvider
new ElementalValueProvider(bindingContext.ModelName, rawValueElement, culture),
bindingContext.ValueProvider
}
}; };
object boundValue = null; object boundValue = null;
@ -105,7 +104,10 @@ 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, fullChildName, elementMetadata); var childBindingContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext,
fullChildName,
elementMetadata);
var didBind = false; var didBind = false;
object boundValue = null; object boundValue = null;

View File

@ -22,13 +22,14 @@ 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 propertyModelName = ModelBindingHelper.CreatePropertyModelName( var propertyModelName = ModelBindingHelper.CreatePropertyModelName(
bindingContext.ModelName, bindingContext.ModelName,
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName); propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
var propertyBindingContext = new ModelBindingContext(bindingContext, var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext(
propertyModelName, bindingContext,
propertyMetadata); 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).

View File

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

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var modelMetadata = bindingContext.ModelMetadata; var modelMetadata = bindingContext.ModelMetadata;
// Property name can be null if the model metadata represents a type (rather than a property or parameter). // 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; object model = null;
if (bindingContext.ModelType == typeof(string)) if (bindingContext.ModelType == typeof(string))
{ {

View File

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

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal; using Microsoft.AspNet.Mvc.ModelBinding.Internal;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
@ -53,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
var bindingContext = context.ModelBindingContext; var bindingContext = context.ModelBindingContext;
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null; 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, // 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 // 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 // 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.) // 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 && if (!isTopLevelObject &&
bindingSource != null && bindingSource != null &&
bindingSource.IsGreedy) bindingSource.IsGreedy)
@ -144,8 +145,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
isAnyPropertyEnabledForValueProviderBasedBinding = true; 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 any property can return a true value.
if (await CanBindValue(context.ModelBindingContext, propertyMetadata)) if (await CanBindValue(propertyModelBindingContext))
{ {
return true; return true;
} }
@ -164,11 +174,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return false; return false;
} }
private async Task<bool> CanBindValue(ModelBindingContext bindingContext, ModelMetadata metadata) private async Task<bool> CanBindValue(ModelBindingContext bindingContext)
{ {
var valueProvider = bindingContext.ValueProvider; var valueProvider = bindingContext.ValueProvider;
var bindingSource = metadata.BindingSource; var bindingSource = bindingContext.BindingSource;
if (bindingSource != null && !bindingSource.IsGreedy) if (bindingSource != null && !bindingSource.IsGreedy)
{ {
var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider; var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
@ -178,11 +188,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
} }
var propertyModelName = ModelBindingHelper.CreatePropertyModelName( if (await valueProvider.ContainsPrefixAsync(bindingContext.ModelName))
bindingContext.ModelName,
metadata.BinderModelName ?? metadata.PropertyName);
if (await valueProvider.ContainsPrefixAsync(propertyModelName))
{ {
return true; return true;
} }
@ -248,13 +254,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
var dtoMetadata = metadataProvider.GetMetadataForType(typeof(ComplexModelDto)); var dtoMetadata = metadataProvider.GetMetadataForType(typeof(ComplexModelDto));
var childContext = new ModelBindingContext( var childContext = ModelBindingContext.GetChildModelBindingContext(
bindingContext, bindingContext,
bindingContext.ModelName, bindingContext.ModelName,
dtoMetadata) dtoMetadata);
{
Model = dto, childContext.Model = dto;
};
return await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext); 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); ModelMetadata GetMetadataForType([NotNull] Type modelType);
IEnumerable<ModelMetadata> GetMetadataForProperties([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 /> /// <inheritdoc />
public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) 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 // BinderModelName
var binderModelNameAttribute = context.Attributes.OfType<IModelNameProvider>().FirstOrDefault(); foreach (var binderModelNameAttribute in context.Attributes.OfType<IModelNameProvider>())
if (binderModelNameAttribute?.Name != null)
{ {
context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name; if (binderModelNameAttribute?.Name != null)
{
context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name;
break;
}
} }
// BinderType // BinderType

View File

@ -15,7 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
public class DefaultModelMetadataProvider : IModelMetadataProvider public class DefaultModelMetadataProvider : IModelMetadataProvider
{ {
private readonly TypeCache _typeCache = new TypeCache(); private readonly TypeCache _typeCache = new TypeCache();
private readonly ParameterCache _parameterCache = new ParameterCache();
private readonly PropertiesCache _propertiesCache = new PropertiesCache(); private readonly PropertiesCache _propertiesCache = new PropertiesCache();
/// <summary> /// <summary>
@ -32,23 +31,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
/// </summary> /// </summary>
protected ICompositeMetadataDetailsProvider DetailsProvider { get; } 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 /> /// <inheritdoc />
public virtual IEnumerable<ModelMetadata> GetMetadataForProperties([NotNull]Type modelType) public virtual IEnumerable<ModelMetadata> GetMetadataForProperties([NotNull]Type modelType)
{ {
@ -152,37 +134,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
return new DefaultMetadataDetailsCache(key, attributes); 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> private class TypeCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache>
{ {
} }
@ -190,9 +141,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
private class PropertiesCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache[]> private class PropertiesCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache[]>
{ {
} }
private class ParameterCache : ConcurrentDictionary<ModelMetadataIdentity, DefaultMetadataDetailsCache>
{
}
} }
} }

View File

@ -15,22 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary> /// </summary>
public static class ModelAttributes 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> /// <summary>
/// Gets the attributes for the given <paramref name="property"/>. /// Gets the attributes for the given <paramref name="property"/>.
/// </summary> /// </summary>

View File

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

View File

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

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
@ -28,26 +29,57 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
/// <summary> /// <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" />. /// <paramref name="bindingContext" />.
/// </summary> /// </summary>
/// <param name="bindingContext">Existing <see cref="ModelBindingContext"/>.</param> /// <param name="bindingContext">Existing <see cref="ModelBindingContext"/>.</param>
/// <param name="modelName">Model name of associated with the new <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 name="modelMetadata">Model metadata of associated with the new <see cref="ModelBindingContext"/>.
/// </param> /// </param>
/// <remarks> public static ModelBindingContext GetChildModelBindingContext(
/// This constructor copies certain values that won't change between parent and child objects, [NotNull] ModelBindingContext bindingContext,
/// e.g. ValueProvider, ModelState [NotNull] string modelName,
/// </remarks> [NotNull] ModelMetadata modelMetadata)
public ModelBindingContext([NotNull] ModelBindingContext bindingContext,
[NotNull] string modelName,
[NotNull] ModelMetadata modelMetadata)
{ {
ModelName = modelName; var modelBindingContext = new ModelBindingContext();
ModelMetadata = modelMetadata; modelBindingContext.ModelName = modelName;
ModelState = bindingContext.ModelState; modelBindingContext.ModelMetadata = modelMetadata;
ValueProvider = bindingContext.ValueProvider; modelBindingContext.ModelState = bindingContext.ModelState;
OperationBindingContext = bindingContext.OperationBindingContext; 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> /// <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> /// <summary>
/// Gets or sets a value that indicates whether the binder should use an empty prefix to look up /// 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 /// 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Web.Http; using System.Web.Http;
using Microsoft.AspNet.Mvc.ApplicationModels; using Microsoft.AspNet.Mvc.ApplicationModels;
@ -15,30 +16,42 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
if (IsConventionApplicable(action.Controller)) if (IsConventionApplicable(action.Controller))
{ {
var optionalParameters = new HashSet<string>();
var uriBindingSource = (new FromUriAttribute()).BindingSource;
foreach (var parameter in action.Parameters) 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. // This has a binding behavior configured, just leave it alone.
} }
else if (ValueProviderResult.CanConvertFromString(parameter.ParameterInfo.ParameterType)) else if (ValueProviderResult.CanConvertFromString(parameter.ParameterInfo.ParameterType))
{ {
// Simple types are by-default from the URI. // Simple types are by-default from the URI.
parameter.BinderMetadata = new FromUriAttribute();
parameter.BindingInfo.BindingSource = uriBindingSource;
} }
else else
{ {
// Complex types are by-default from the body. // 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. // For all non IOptionalBinderMetadata, which are not URL source (like FromQuery etc.) do not
var optionalMetadata = parameter.BinderMetadata as FromUriAttribute; // participate in overload selection and hence are added to the hashset so that they can be
if (parameter.ParameterInfo.HasDefaultValue && optionalMetadata != null) // 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>(); 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) foreach (var parameter in candidate.Action.Parameters)
{ {
// We only consider parameters that are marked as bound from the URL. // We only consider parameters that are marked as bound from the URL.
var bindingSourceMetadata = parameter?.BinderMetadata as IBindingSourceMetadata; var source = parameter.BindingInfo.BindingSource;
var source = bindingSourceMetadata?.BindingSource;
if (source == null) if (source == null)
{ {
continue; continue;
@ -102,17 +103,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
source.CanAcceptDataFrom(BindingSource.Query)) && source.CanAcceptDataFrom(BindingSource.Query)) &&
ValueProviderResult.CanConvertFromString(parameter.ParameterType)) ValueProviderResult.CanConvertFromString(parameter.ParameterType))
{ {
var optionalMetadata = parameter.BinderMetadata as IOptionalBinderMetadata; if (optionalParameters != null)
if (optionalMetadata == null || optionalMetadata.IsOptional)
{ {
// Optional parameters are ignored in overloading. If a parameter doesn't specify that it's var isOptional = optionalParameters.Contains(parameter.Name);
// required then treat it as optional (MVC default). WebAPI parameters will all by-default if (isOptional)
// specify themselves as required unless they have a default value. {
continue; // 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 = parameter.BindingInfo.BinderModelName ?? parameter.Name;
var prefix = nameProvider?.Name ?? parameter.Name;
parameters.Add(new OverloadedParameter() parameters.Add(new OverloadedParameter()
{ {

View File

@ -4,13 +4,13 @@
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
/// <summary> /// <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 /// 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. /// do not have to have a value for the action to be selected.
/// ///
/// This has no impact when used without WebAPI action overloading. /// This has no impact when used without WebAPI action overloading.
/// </summary> /// </summary>
public interface IOptionalBinderMetadata : IBinderMetadata public interface IOptionalBinderMetadata
{ {
bool IsOptional { get; } bool IsOptional { get; }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.ApplicationModels namespace Microsoft.AspNet.Mvc.ApplicationModels
@ -18,7 +19,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
new List<object>() { new FromBodyAttribute() }); new List<object>() { new FromBodyAttribute() });
parameter.Action = new ActionModel(typeof(TestController).GetMethod("Edit"), new List<object>()); 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"; parameter.ParameterName = "id";
// Act // Act

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Moq; using Moq;
using Xunit; 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] [Fact]
public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsFalse() public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsFalse()
{ {
@ -165,6 +63,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
Name = "foo", Name = "foo",
ParameterType = typeof(object), ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
} }
} }
}; };
@ -215,6 +114,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
Name = "foo", Name = "foo",
ParameterType = typeof(object), ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
} }
} }
}; };
@ -266,6 +166,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
Name = "foo", Name = "foo",
ParameterType = typeof(string), ParameterType = typeof(string),
BindingInfo = new BindingInfo(),
} }
}, },
}; };
@ -322,6 +223,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
Name = "foo", Name = "foo",
ParameterType = typeof(object), ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
} }
} }
}; };
@ -375,6 +277,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
Name = "foo", Name = "foo",
ParameterType = typeof(object), ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
} }
} }
}; };
@ -423,6 +326,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ {
Name = "foo", Name = "foo",
ParameterType = typeof(object), ParameterType = typeof(object),
BindingInfo = new BindingInfo(),
} }
} }
}; };

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
// 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.Threading.Tasks;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test namespace Microsoft.AspNet.Mvc.ModelBinding.Test
@ -8,24 +10,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public class ModelBindingContextTest public class ModelBindingContextTest
{ {
[Fact] [Fact]
public void CopyConstructor() public void GetChildModelBindingContext()
{ {
// Arrange // Arrange
var originalBindingContext = new ModelBindingContext var originalBindingContext = new ModelBindingContext
{ {
ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)), ModelMetadata = new TestModelMetadataProvider().GetMetadataForType(typeof(object)),
ModelName = "theName", ModelName = "theName",
ModelState = new ModelStateDictionary(), ModelState = new ModelStateDictionary(),
ValueProvider = new SimpleHttpValueProvider() 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 // Act
var newBindingContext = new ModelBindingContext(originalBindingContext, string.Empty, newModelMetadata); var newBindingContext = ModelBindingContext.GetChildModelBindingContext(
originalBindingContext,
string.Empty,
newModelMetadata);
// Assert // Assert
Assert.Same(newModelMetadata, newBindingContext.ModelMetadata); 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("", 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);
@ -43,5 +59,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
// Assert // Assert
Assert.Equal(typeof(int), bindingContext.ModelType); 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)); .Returns(Task.FromResult(false));
var metadataProvider = new TestModelMetadataProvider(); var metadataProvider = new TestModelMetadataProvider();
if (isPrefixProvided)
{
metadataProvider.ForType<Person>().BindingDetails(bd => bd.BinderModelName = "prefix");
}
var bindingContext = new MutableObjectBinderContext var bindingContext = new MutableObjectBinderContext
{ {
ModelBindingContext = new ModelBindingContext 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. // Setting it to empty ensures that model does not get created becasue of no model name.
ModelName = "dummyModelName", ModelName = "dummyModelName",
BinderModelName = isPrefixProvided ? "prefix" : null,
} }
}; };
@ -110,6 +106,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Fact] [Fact]
public async Task CanCreateModel_ReturnsFalse_ForNonTopLevelModel_IfModelIsMarkedWithBinderMetadata() public async Task CanCreateModel_ReturnsFalse_ForNonTopLevelModel_IfModelIsMarkedWithBinderMetadata()
{ {
var modelMetadata = GetMetadataForType(typeof(Document))
.Properties
.First(metadata => metadata.PropertyName == nameof(Document.SubDocument));
var bindingContext = new MutableObjectBinderContext var bindingContext = new MutableObjectBinderContext
{ {
ModelBindingContext = new ModelBindingContext ModelBindingContext = new ModelBindingContext
@ -121,7 +120,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
OperationBindingContext = new OperationBindingContext OperationBindingContext = new OperationBindingContext
{ {
ValidatorProvider = Mock.Of<IModelValidatorProvider>(), ValidatorProvider = Mock.Of<IModelValidatorProvider>(),
} },
BindingSource = modelMetadata.BindingSource,
BinderModelName = modelMetadata.BinderModelName,
} }
}; };
@ -266,11 +267,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return null; return null;
}); });
var modelMetadata = GetMetadataForType(modelType);
var bindingContext = new MutableObjectBinderContext var bindingContext = new MutableObjectBinderContext
{ {
ModelBindingContext = new ModelBindingContext ModelBindingContext = new ModelBindingContext
{ {
ModelMetadata = GetMetadataForType(modelType), ModelMetadata = modelMetadata,
ValueProvider = mockValueProvider.Object, ValueProvider = mockValueProvider.Object,
OperationBindingContext = new OperationBindingContext 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. // 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] [Fact]
public void GetBindingDetails_FindsModelName_IfEmpty() public void GetBindingDetails_FindsModelName_IfNullFallsBack()
{ {
// Arrange // Arrange
var attributes = new object[] var attributes = new object[]
@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
provider.GetBindingMetadata(context); provider.GetBindingMetadata(context);
// Assert // Assert
Assert.Null(context.BindingMetadata.BinderModelName); Assert.Equal("Product", context.BindingMetadata.BinderModelName);
} }
[Fact] [Fact]

View File

@ -110,66 +110,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.Equal("OnPropertyType", Assert.IsType<ModelAttribute>(attributes[1]).Value); 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() private static DefaultModelMetadataProvider CreateProvider()
{ {
return new DefaultModelMetadataProvider(new EmptyCompositeMetadataDetailsProvider()); return new DefaultModelMetadataProvider(new EmptyCompositeMetadataDetailsProvider());

View File

@ -43,103 +43,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.Equal<string>(expected, matched); 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] [Fact]
public void ModelMetadataProvider_ReadsModelNameProperty_ForTypes() public void ModelMetadataProvider_ReadsModelNameProperty_ForTypes()
{ {
@ -267,60 +170,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata
Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName); 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 public static TheoryData<object, Func<ModelMetadata, string>> ExpectedAttributeDataStrings
{ {
get 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 string Name { get; set; }
} }
public class NonTypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider public class NonTypeBasedBinderAttribute : Attribute, IModelNameProvider
{ {
public string Name { get; set; } 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); 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) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ApplicationModels; using Microsoft.AspNet.Mvc.ApplicationModels;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.WebApiCompatShim; using Microsoft.AspNet.Mvc.WebApiCompatShim;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel; using Microsoft.Framework.OptionsModel;
@ -276,8 +277,9 @@ namespace System.Web.Http
foreach (var action in actions) foreach (var action in actions)
{ {
var parameter = Assert.Single(action.Parameters); var parameter = Assert.Single(action.Parameters);
var metadata = Assert.IsType<FromUriAttribute>(parameter.BinderMetadata); Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource);
Assert.False(metadata.IsOptional); 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) foreach (var action in actions)
{ {
var parameter = Assert.Single(action.Parameters); var parameter = Assert.Single(action.Parameters);
Assert.IsType<FromBodyAttribute>(parameter.BinderMetadata); Assert.Equal(BindingSource.Body, parameter.BindingInfo.BindingSource);
} }
} }
[Fact] [Fact]
public void GetActions_Parameters_BinderMetadata() public void GetActions_Parameters_WithBindingSource()
{ {
// Arrange // Arrange
var provider = CreateProvider(); var provider = CreateProvider();
@ -331,7 +333,7 @@ namespace System.Web.Http
foreach (var action in actions) foreach (var action in actions)
{ {
var parameter = Assert.Single(action.Parameters); 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) foreach (var action in actions)
{ {
var parameter = Assert.Single(action.Parameters); var parameter = Assert.Single(action.Parameters);
var metadata = Assert.IsType<FromUriAttribute>(parameter.BinderMetadata); Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource);
Assert.True(metadata.IsOptional); 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 System.Web.Http;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Xunit; using Xunit;
@ -21,7 +22,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -51,13 +55,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -87,13 +97,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -123,13 +139,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -159,18 +181,28 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute() { IsOptional = true }, BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
}; };
var optionalParameters = new HashSet<string>();
optionalParameters.Add("quantity");
action.Properties.Add("OptionalParameters", optionalParameters);
var constraint = new OverloadActionConstraint(); var constraint = new OverloadActionConstraint();
var context = new ActionConstraintContext(); var context = new ActionConstraintContext();
@ -195,13 +227,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -212,13 +250,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity_ordered", Name = "quantity_ordered",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -252,7 +296,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -263,13 +310,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -300,30 +353,46 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute() { IsOptional = true }, BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
}; };
var optionalParameters = new HashSet<string>();
optionalParameters.Add("quantity");
action1.Properties.Add("OptionalParameters", optionalParameters);
var action2 = new ActionDescriptor(); var action2 = new ActionDescriptor();
action2.Parameters = new List<ParameterDescriptor>() action2.Parameters = new List<ParameterDescriptor>()
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -354,13 +423,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -371,13 +446,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "price", Name = "price",
ParameterType = typeof(decimal), ParameterType = typeof(decimal),
}, },
@ -411,7 +492,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -422,18 +506,28 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute() { IsOptional = true }, BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
}; };
var optionalParameters = new HashSet<string>();
optionalParameters.Add("quantity");
action2.Properties.Add("OptionalParameters", optionalParameters);
var constraint = new OverloadActionConstraint(); var constraint = new OverloadActionConstraint();
var context = new ActionConstraintContext(); var context = new ActionConstraintContext();
@ -459,7 +553,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
@ -470,13 +567,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
{ {
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromUriAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromUriAttribute()).BindingSource,
},
Name = "id", Name = "id",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },
new ParameterDescriptor() new ParameterDescriptor()
{ {
BinderMetadata = new FromBodyAttribute(), BindingInfo = new BindingInfo()
{
BindingSource = (new FromBodyAttribute()).BindingSource,
},
Name = "quantity", Name = "quantity",
ParameterType = typeof(int), ParameterType = typeof(int),
}, },

View File

@ -16,19 +16,16 @@ namespace ApplicationModelWebSite
{ {
public string GetParameterMetadata([Cool] int? id) 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 private class CoolAttribute : Attribute, IParameterModelConvention
{ {
public void Apply(ParameterModel model) 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> </PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<ProjectGuid>94ba134d-04b3-48aa-ba55-5a4db8640f2d</ProjectGuid> <ProjectGuid>b42d4844-fff8-4ec2-88d1-3ae95234d9eb</ProjectGuid>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> <OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup> </PropertyGroup>

View File

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

View File

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

View File

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