Removing ModelMetadataProvider.GetModelMetadataForParameters
This commit is contained in:
parent
d166517dd6
commit
ac908d405e
17
Mvc.sln
17
Mvc.sln
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"/>.
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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>())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue