diff --git a/Mvc.sln b/Mvc.sln index 3688935b62..c14e768c45 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22609.0 +VisualStudioVersion = 14.0.22711.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -154,6 +154,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TestCo EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsWebSite", "test\WebSites\CorsWebSite\CorsWebSite.xproj", "{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CorsMiddlewareWebSite", "test\WebSites\CorsMiddlewareWebSite\CorsMiddlewareWebSite.xproj", "{B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -908,6 +910,18 @@ Global {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Mixed Platforms.Build.0 = Release|Any CPU {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.ActiveCfg = Release|Any CPU {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.Build.0 = Release|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Debug|x86.Build.0 = Debug|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Any CPU.Build.0 = Release|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|x86.ActiveCfg = Release|Any CPU + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -983,5 +997,6 @@ Global {8AEB631E-AB74-4D2E-83FB-8931EE10D9D3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {94BA134D-04B3-48AA-BA55-5A4DB8640F2D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {B42D4844-FFF8-4EC2-88D1-3AE95234D9EB} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs index 0b2dc460eb..7bd851855b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Cors; using Microsoft.AspNet.Cors.Core; using Microsoft.AspNet.Mvc.Description; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.Routing; using Microsoft.Framework.Internal; using Microsoft.Framework.OptionsModel; @@ -343,7 +344,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType().ToArray(); var parameterModel = new ParameterModel(parameterInfo, attributes); - parameterModel.BinderMetadata = attributes.OfType().FirstOrDefault(); + var bindingInfo = BindingInfo.GetBindingInfo(attributes); + parameterModel.BindingInfo = bindingInfo; parameterModel.ParameterName = parameterInfo.Name; diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs index c8c9fe3e78..e596cd68c8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ApplicationModels @@ -24,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { Action = other.Action; Attributes = new List(other.Attributes); - BinderMetadata = other.BinderMetadata; + BindingInfo = other.BindingInfo; ParameterInfo = other.ParameterInfo; ParameterName = other.ParameterName; } @@ -33,10 +35,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public IReadOnlyList Attributes { get; } - public IBinderMetadata BinderMetadata { get; set; } - public ParameterInfo ParameterInfo { get; private set; } public string ParameterName { get; set; } + + public BindingInfo BindingInfo { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs index ce157a260f..8ae45aa5ac 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs @@ -276,9 +276,9 @@ namespace Microsoft.AspNet.Mvc { var parameterDescriptor = new ParameterDescriptor() { - BinderMetadata = parameter.BinderMetadata, Name = parameter.ParameterName, ParameterType = parameter.ParameterInfo.ParameterType, + BindingInfo = parameter.BindingInfo }; return parameterDescriptor; diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs index 552aa9da6e..ba0f384dd4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc @@ -44,29 +46,20 @@ namespace Microsoft.AspNet.Mvc nameof(actionContext)); } - - var methodParameters = actionDescriptor.MethodInfo.GetParameters(); - var parameterMetadata = new List(); - 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(StringComparer.Ordinal); - await PopulateArgumentAsync(actionContext, actionBindingContext, actionArguments, parameterMetadata); + await PopulateArgumentsAsync( + actionContext, + actionBindingContext, + actionArguments, + actionDescriptor.Parameters); return actionArguments; - } + } - private async Task PopulateArgumentAsync( + private async Task PopulateArgumentsAsync( ActionContext actionContext, ActionBindingContext bindingContext, IDictionary arguments, - IEnumerable parameterMetadata) + IEnumerable parameterMetadata) { var operationBindingContext = new OperationBindingContext { @@ -81,15 +74,24 @@ namespace Microsoft.AspNet.Mvc modelState.MaxAllowedErrors = _options.MaxModelValidationErrors; foreach (var parameter in parameterMetadata) { - var parameterType = parameter.ModelType; + var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType); + var parameterType = parameter.ParameterType; + var modelBindingContext = GetModelBindingContext( + parameter.Name, + metadata, + parameter.BindingInfo, + modelState, + operationBindingContext); - var modelBindingContext = GetModelBindingContext(parameter, modelState, operationBindingContext); var modelBindingResult = await bindingContext.ModelBinder.BindModelAsync(modelBindingContext); if (modelBindingResult != null && modelBindingResult.IsModelSet) { - var modelExplorer = new ModelExplorer(_modelMetadataProvider, parameter, modelBindingResult.Model); + var modelExplorer = new ModelExplorer( + _modelMetadataProvider, + metadata, + modelBindingResult.Model); - arguments[parameter.PropertyName] = modelBindingResult.Model; + arguments[parameter.Name] = modelBindingResult.Model; var validationContext = new ModelValidationContext( modelBindingResult.Key, bindingContext.ValidatorProvider, @@ -100,23 +102,21 @@ namespace Microsoft.AspNet.Mvc } } - // Internal for tests - internal static ModelBindingContext GetModelBindingContext( - ModelMetadata modelMetadata, + private static ModelBindingContext GetModelBindingContext( + string parameterName, + ModelMetadata metadata, + BindingInfo bindingInfo, ModelStateDictionary modelState, OperationBindingContext operationBindingContext) { - var modelBindingContext = new ModelBindingContext - { - ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName, - ModelMetadata = modelMetadata, - ModelState = modelState, + var modelBindingContext = ModelBindingContext.GetModelBindingContext( + metadata, + bindingInfo, + parameterName); - // Fallback only if there is no explicit model name set. - FallbackToEmptyPrefix = modelMetadata.BinderModelName == null, - ValueProvider = operationBindingContext.ValueProvider, - OperationBindingContext = operationBindingContext, - }; + modelBindingContext.ModelState = modelState; + modelBindingContext.ValueProvider = operationBindingContext.ValueProvider; + modelBindingContext.OperationBindingContext = operationBindingContext; return modelBindingContext; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs index 911a4728e8..82209af194 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Template; using Microsoft.Framework.Internal; @@ -127,7 +129,6 @@ namespace Microsoft.AspNet.Mvc.Description return apiDescription; } - private IList GetParameters(ApiParameterContext context) { // First, get parameters from the model-binding/parameter-binding side of the world. @@ -136,7 +137,13 @@ namespace Microsoft.AspNet.Mvc.Description foreach (var actionParameter in context.ActionDescriptor.Parameters) { var visitor = new PseudoModelBindingVisitor(context, actionParameter); - visitor.WalkParameter(); + var metadata = _modelMetadataProvider.GetMetadataForType(actionParameter.ParameterType); + + var bindingContext = ApiParameterDescriptionContext.GetContext( + metadata, + actionParameter.BindingInfo, + propertyName: actionParameter.Name); + visitor.WalkParameter(bindingContext); } } @@ -422,6 +429,32 @@ namespace Microsoft.AspNet.Mvc.Description public IReadOnlyList RouteParameters { get; } } + private class ApiParameterDescriptionContext + { + public ModelMetadata ModelMetadata { get; set; } + + public string BinderModelName { get; set; } + + public BindingSource BindingSource { get; set; } + + public string PropertyName { get; set; } + + public static ApiParameterDescriptionContext GetContext( + ModelMetadata metadata, + BindingInfo bindingInfo, + string propertyName) + { + // BindingMetadata can be null if the metadata represents properties. + return new ApiParameterDescriptionContext + { + ModelMetadata = metadata, + BinderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName, + BindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource, + PropertyName = propertyName ?? metadata.PropertyName + }; + } + } + private class PseudoModelBindingVisitor { public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter) @@ -439,29 +472,20 @@ namespace Microsoft.AspNet.Mvc.Description // Avoid infinite recursion by tracking properties. private HashSet Visited { get; } - public void WalkParameter() + public void WalkParameter(ApiParameterDescriptionContext context) { - var parameterInfo = - Context.ActionDescriptor.MethodInfo.GetParameters() - .Where(p => p.Name == Parameter.Name) - .Single(); - - var modelMetadata = Context.MetadataProvider.GetMetadataForParameter( - parameterInfo, - attributes: new object[] { Parameter.BinderMetadata }); - // Attempt to find a binding source for the parameter // // The default is ModelBinding (aka all default value providers) var source = BindingSource.ModelBinding; - if (!Visit(modelMetadata, source, containerName: string.Empty)) + if (!Visit(context, source, containerName: string.Empty)) { // If we get here, then it means we didn't find a match for any of the model. This means that it's // likely 'model-bound' in the traditional MVC sense (formdata + query string + route data) and // doesn't use any IBinderMetadata. // // Add a single 'default' parameter description for the model. - Context.Results.Add(CreateResult(modelMetadata, source, containerName: string.Empty)); + Context.Results.Add(CreateResult(context, source, containerName: string.Empty)); } } @@ -481,18 +505,23 @@ namespace Microsoft.AspNet.Mvc.Description /// or NONE of it. If a parameter description is created for ANY sub-properties of the model, then a parameter /// description will be created for ALL of them. /// - private bool Visit(ModelMetadata modelMetadata, BindingSource ambientSource, string containerName) + private bool Visit( + ApiParameterDescriptionContext bindingContext, + BindingSource ambientSource, + string containerName) { - var source = modelMetadata.BindingSource; + var source = bindingContext.BindingSource; if (source != null && source.IsGreedy) { // We have a definite answer for this model. This is a greedy source like // [FromBody] so there's no need to consider properties. - Context.Results.Add(CreateResult(modelMetadata, source, containerName)); + Context.Results.Add(CreateResult(bindingContext, source, containerName)); return true; } + var modelMetadata = bindingContext.ModelMetadata; + // For any property which is a leaf node, we don't want to keep traversing: // // 1) Collections - while it's possible to have binder attributes on the inside of a collection, @@ -517,7 +546,7 @@ namespace Microsoft.AspNet.Mvc.Description { // We found a new source, and this model has no properties. This is probabaly // a simple type with an attribute like [FromQuery]. - Context.Results.Add(CreateResult(modelMetadata, source, containerName)); + Context.Results.Add(CreateResult(bindingContext, source, containerName)); return true; } } @@ -545,29 +574,33 @@ namespace Microsoft.AspNet.Mvc.Description // Order - source: Body // - var unboundProperties = new HashSet(); + var unboundProperties = new HashSet(); // We don't want to append the **parameter** name when building a model name. var newContainerName = containerName; if (modelMetadata.ContainerType != null) { - newContainerName = GetName(containerName, modelMetadata); + newContainerName = GetName(containerName, bindingContext); } foreach (var propertyMetadata in modelMetadata.Properties) { var key = new PropertyKey(propertyMetadata, source); + var propertyContext = ApiParameterDescriptionContext.GetContext( + propertyMetadata, + bindingInfo: null, + propertyName: null); if (Visited.Add(key)) { - if (!Visit(propertyMetadata, source ?? ambientSource, newContainerName)) + if (!Visit(propertyContext, source ?? ambientSource, newContainerName)) { - unboundProperties.Add(propertyMetadata); + unboundProperties.Add(propertyContext); } } else { - unboundProperties.Add(propertyMetadata); + unboundProperties.Add(propertyContext); } } @@ -582,7 +615,7 @@ namespace Microsoft.AspNet.Mvc.Description { // We found a new source, and didn't create a result for any of the properties yet, // so create a result for the current object. - Context.Results.Add(CreateResult(modelMetadata, source, containerName)); + Context.Results.Add(CreateResult(bindingContext, source, containerName)); return true; } } @@ -600,20 +633,20 @@ namespace Microsoft.AspNet.Mvc.Description } private ApiParameterDescription CreateResult( - ModelMetadata metadata, + ApiParameterDescriptionContext bindingContext, BindingSource source, string containerName) { return new ApiParameterDescription() { - ModelMetadata = metadata, - Name = GetName(containerName, metadata), + ModelMetadata = bindingContext.ModelMetadata, + Name = GetName(containerName, bindingContext), Source = source, - Type = metadata.ModelType, + Type = bindingContext.ModelMetadata.ModelType, }; } - private static string GetName(string containerName, ModelMetadata metadata) + private static string GetName(string containerName, ApiParameterDescriptionContext metadata) { if (!string.IsNullOrEmpty(metadata.BinderModelName)) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterDescriptorValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterDescriptorValues.cs index ffe3866da4..009b1bad08 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterDescriptorValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterDescriptorValues.cs @@ -17,7 +17,6 @@ namespace Microsoft.AspNet.Mvc.Logging { ParameterName = inner.Name; ParameterType = inner.ParameterType; - BinderMetadataType = inner.BinderMetadata?.GetType(); } /// @@ -30,11 +29,6 @@ namespace Microsoft.AspNet.Mvc.Logging /// public Type ParameterType { get; } - /// - /// The of the . - /// - public Type BinderMetadataType { get; } - public override string Format() { return LogFormatter.FormatLogValues(this); diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs index 63f52b26e3..b08c6065f5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs @@ -19,7 +19,6 @@ namespace Microsoft.AspNet.Mvc.Logging { ParameterName = inner.ParameterName; ParameterType = inner.ParameterInfo.ParameterType; - BinderMetadata = inner.BinderMetadata?.GetType(); } /// @@ -32,11 +31,6 @@ namespace Microsoft.AspNet.Mvc.Logging /// public Type ParameterType { get; } - /// - /// The of the . - /// - public Type BinderMetadata { get; } - public override string Format() { return LogFormatter.FormatLogValues(this); diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs index 8d69b0f948..536ff201ba 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; namespace Microsoft.AspNet.Mvc { @@ -12,6 +13,6 @@ namespace Microsoft.AspNet.Mvc public Type ParameterType { get; set; } - public IBinderMetadata BinderMetadata { get; set; } + public BindingInfo BindingInfo { get; set; } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBinderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBinderMetadata.cs deleted file mode 100644 index 0f88e1ba2a..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBinderMetadata.cs +++ /dev/null @@ -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 -{ - /// - /// Interface for metadata related to model binders. - /// - public interface IBinderMetadata - { - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBindingSourceMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBindingSourceMetadata.cs index 271ae88f69..62c787b05a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBindingSourceMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBindingSourceMetadata.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// Metadata which specificies the data source for model binding. /// - public interface IBindingSourceMetadata : IBinderMetadata + public interface IBindingSourceMetadata { /// /// Gets the . diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs index 9d595443df..e9c5d11d0d 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BinderTypeBasedModelBinder.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public async Task BindModelAsync(ModelBindingContext bindingContext) { - if (bindingContext.ModelMetadata.BinderType == null) + if (bindingContext.BinderType == null) { // Return null so that we are able to continue with the default set of model binders, // if there is no specific model binder provided. @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices; - var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.ModelMetadata.BinderType, _createFactory); + var createFactory = _typeActivatorCache.GetOrAdd(bindingContext.BinderType, _createFactory); var instance = createFactory(requestServices, arguments: null); var modelBinder = instance as IModelBinder; @@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { throw new InvalidOperationException( Resources.FormatBinderType_MustBeIModelBinderOrIModelBinderProvider( - bindingContext.ModelMetadata.BinderType.FullName, + bindingContext.BinderType.FullName, typeof(IModelBinder).FullName, typeof(IModelBinderProvider).FullName)); } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs index cdcfcbd90f..3042b6179d 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs @@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public async Task BindModelAsync(ModelBindingContext context) { - var allowedBindingSource = context.ModelMetadata.BindingSource; + var allowedBindingSource = context.BindingSource; if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource)) { // Binding Sources are opt-in. This model either didn't specify one or specified something diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs index d0b4f1e32e..f5804f5a87 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs @@ -50,16 +50,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var rawValueArray = RawValueToObjectArray(rawValue); foreach (var rawValueElement in rawValueArray) { - var innerBindingContext = new ModelBindingContext(bindingContext, - bindingContext.ModelName, - elementMetadata) + var innerBindingContext = ModelBindingContext.GetChildModelBindingContext( + bindingContext, + bindingContext.ModelName, + elementMetadata); + innerBindingContext.ValueProvider = new CompositeValueProvider { - ValueProvider = new CompositeValueProvider - { - // our temporary provider goes at the front of the list - new ElementalValueProvider(bindingContext.ModelName, rawValueElement, culture), - bindingContext.ValueProvider - } + // our temporary provider goes at the front of the list + new ElementalValueProvider(bindingContext.ModelName, rawValueElement, culture), + bindingContext.ValueProvider }; object boundValue = null; @@ -105,7 +104,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding foreach (var indexName in indexNames) { var fullChildName = ModelBindingHelper.CreateIndexModelName(bindingContext.ModelName, indexName); - var childBindingContext = new ModelBindingContext(bindingContext, fullChildName, elementMetadata); + var childBindingContext = ModelBindingContext.GetChildModelBindingContext( + bindingContext, + fullChildName, + elementMetadata); var didBind = false; object boundValue = null; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs index 5bb57432e2..78dc815269 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs @@ -22,13 +22,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var dto = (ComplexModelDto)bindingContext.Model; foreach (var propertyMetadata in dto.PropertyMetadata) { - var propertyModelName = ModelBindingHelper.CreatePropertyModelName( - bindingContext.ModelName, - propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName); + var propertyModelName = ModelBindingHelper.CreatePropertyModelName( + bindingContext.ModelName, + propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName); - var propertyBindingContext = new ModelBindingContext(bindingContext, - propertyModelName, - propertyMetadata); + var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext( + bindingContext, + propertyModelName, + propertyMetadata); // bind and propagate the values // If we can't bind then leave the result missing (don't add a null). diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs index 32c3872dd8..4a1d765bfe 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs @@ -117,6 +117,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ValueProvider = oldBindingContext.ValueProvider, OperationBindingContext = oldBindingContext.OperationBindingContext, PropertyFilter = oldBindingContext.PropertyFilter, + BinderModelName = oldBindingContext.BinderModelName, + BindingSource = oldBindingContext.BindingSource, + BinderType = oldBindingContext.BinderType, }; newBindingContext.OperationBindingContext.BodyBindingState = GetBodyBindingState(oldBindingContext); @@ -139,7 +142,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // public IActionResult UpdatePerson([FromForm] Person person) { } // // In this example, [FromQuery] overrides the ambient data source (form). - var bindingSource = oldBindingContext.ModelMetadata.BindingSource; + var bindingSource = oldBindingContext.BindingSource; if (bindingSource != null && !bindingSource.IsGreedy) { var valueProvider = @@ -155,7 +158,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private static BodyBindingState GetBodyBindingState(ModelBindingContext oldBindingContext) { - var bindingSource = oldBindingContext.ModelMetadata.BindingSource; + var bindingSource = oldBindingContext.BindingSource; var willReadBodyWithFormatter = bindingSource == BindingSource.Body; var willReadBodyAsFormData = bindingSource == BindingSource.Form; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs index 197d7c922a..b8a842d05b 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var modelMetadata = bindingContext.ModelMetadata; // Property name can be null if the model metadata represents a type (rather than a property or parameter). - var headerName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName; + var headerName = bindingContext.BinderModelName ?? modelMetadata.PropertyName ?? bindingContext.ModelName; object model = null; if (bindingContext.ModelType == typeof(string)) { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs index 889994503d..99df8ed86f 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs @@ -54,8 +54,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(typeof(TModel)); var propertyModelName = ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName); - var propertyBindingContext = - new ModelBindingContext(parentBindingContext, propertyModelName, propertyModelMetadata); + var propertyBindingContext = ModelBindingContext.GetChildModelBindingContext( + parentBindingContext, + propertyModelName, + propertyModelMetadata); + + propertyBindingContext.BinderModelName = propertyModelMetadata.BinderModelName; + var modelBindingResult = await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext); if (modelBindingResult != null) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index 08377a802c..f76c6002c1 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding.Internal; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -53,7 +54,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { var bindingContext = context.ModelBindingContext; var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null; - var hasExplicitAlias = bindingContext.ModelMetadata.BinderModelName != null; + var hasExplicitAlias = bindingContext.BinderModelName != null; // If we get here the model is a complex object which was not directly bound by any previous model binder, // so we want to decide if we want to continue binding. This is important to get right to avoid infinite @@ -67,7 +68,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // // We skip this check if it is a top level object because we want to always evaluate // the creation of top level object (this is also required for ModelBinderAttribute to work.) - var bindingSource = bindingContext.ModelMetadata.BindingSource; + var bindingSource = bindingContext.BindingSource; if (!isTopLevelObject && bindingSource != null && bindingSource.IsGreedy) @@ -144,8 +145,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { isAnyPropertyEnabledForValueProviderBasedBinding = true; + var propertyModelName = ModelBindingHelper.CreatePropertyModelName( + context.ModelBindingContext.ModelName, + propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName); + + var propertyModelBindingContext = ModelBindingContext.GetChildModelBindingContext( + context.ModelBindingContext, + propertyModelName, + propertyMetadata); + // If any property can return a true value. - if (await CanBindValue(context.ModelBindingContext, propertyMetadata)) + if (await CanBindValue(propertyModelBindingContext)) { return true; } @@ -164,11 +174,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return false; } - private async Task CanBindValue(ModelBindingContext bindingContext, ModelMetadata metadata) + private async Task CanBindValue(ModelBindingContext bindingContext) { var valueProvider = bindingContext.ValueProvider; - var bindingSource = metadata.BindingSource; + var bindingSource = bindingContext.BindingSource; if (bindingSource != null && !bindingSource.IsGreedy) { var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider; @@ -178,11 +188,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } - var propertyModelName = ModelBindingHelper.CreatePropertyModelName( - bindingContext.ModelName, - metadata.BinderModelName ?? metadata.PropertyName); - - if (await valueProvider.ContainsPrefixAsync(propertyModelName)) + if (await valueProvider.ContainsPrefixAsync(bindingContext.ModelName)) { return true; } @@ -248,13 +254,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider; var dtoMetadata = metadataProvider.GetMetadataForType(typeof(ComplexModelDto)); - var childContext = new ModelBindingContext( + var childContext = ModelBindingContext.GetChildModelBindingContext( bindingContext, bindingContext.ModelName, - dtoMetadata) - { - Model = dto, - }; + dtoMetadata); + + childContext.Model = dto; return await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext); } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BindingInfo.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BindingInfo.cs new file mode 100644 index 0000000000..833388b186 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BindingInfo.cs @@ -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 +{ + /// + /// Binding info which represents metadata associated to an action parameter. + /// + public class BindingInfo + { + /// + /// Gets or sets the . + /// + public BindingSource BindingSource { get; set; } + + /// + /// Gets or sets the binder model name. + /// + public string BinderModelName { get; set; } + + /// + /// Gets or sets the of the model binder used to bind the model. + /// + public Type BinderType { get; set; } + + /// + /// Gets or sets the . + /// + public IPropertyBindingPredicateProvider PropertyBindingPredicateProvider { get; set; } + + /// + /// Constructs a new instance of from the given . + /// + /// A collection of attributes which are used to construct + /// + /// A new instance of . + public static BindingInfo GetBindingInfo(IEnumerable attributes) + { + var bindingInfo = new BindingInfo(); + + // BinderModelName + foreach (var binderModelNameAttribute in attributes.OfType()) + { + if (binderModelNameAttribute?.Name != null) + { + bindingInfo.BinderModelName = binderModelNameAttribute.Name; + break; + } + } + + // BinderType + foreach (var binderTypeAttribute in attributes.OfType()) + { + if (binderTypeAttribute.BinderType != null) + { + bindingInfo.BinderType = binderTypeAttribute.BinderType; + break; + } + } + + // BindingSource + foreach (var bindingSourceAttribute in attributes.OfType()) + { + if (bindingSourceAttribute.BindingSource != null) + { + bindingInfo.BindingSource = bindingSourceAttribute.BindingSource; + break; + } + } + + // PropertyBindingPredicateProvider + var predicateProviders = attributes.OfType().ToArray(); + if (predicateProviders.Length > 0) + { + bindingInfo.PropertyBindingPredicateProvider = new CompositePredicateProvider( + predicateProviders); + } + + return bindingInfo; + } + + private class CompositePredicateProvider : IPropertyBindingPredicateProvider + { + private readonly IEnumerable _providers; + + public CompositePredicateProvider(IEnumerable providers) + { + _providers = providers; + } + + public Func PropertyFilter + { + get + { + return CreatePredicate(); + } + } + + private Func 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; + }; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/IModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/IModelMetadataProvider.cs index 1fb1db663c..8f92ce8d7b 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/IModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/IModelMetadataProvider.cs @@ -13,7 +13,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding ModelMetadata GetMetadataForType([NotNull] Type modelType); IEnumerable GetMetadataForProperties([NotNull] Type modelType); - - ModelMetadata GetMetadataForParameter([NotNull] ParameterInfo parameter, IEnumerable attributes); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs index b9dcbe416f..05c18d3dd0 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultBindingMetadataProvider.cs @@ -16,17 +16,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// public void GetBindingMetadata([NotNull] BindingMetadataProviderContext context) { - // For Model Name - we only use the first attribute we find. An attribute on the parameter - // is considered an override of an attribute on the type. This is for compatibility with [Bind] - // from MVC 5. - // - // BinderType and BindingSource fall back to the first attribute to provide a value. - // BinderModelName - var binderModelNameAttribute = context.Attributes.OfType().FirstOrDefault(); - if (binderModelNameAttribute?.Name != null) + foreach (var binderModelNameAttribute in context.Attributes.OfType()) { - context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name; + if (binderModelNameAttribute?.Name != null) + { + context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name; + break; + } } // BinderType diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs index d1f9255dff..ed7f748980 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -15,7 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata public class DefaultModelMetadataProvider : IModelMetadataProvider { private readonly TypeCache _typeCache = new TypeCache(); - private readonly ParameterCache _parameterCache = new ParameterCache(); private readonly PropertiesCache _propertiesCache = new PropertiesCache(); /// @@ -32,23 +31,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// protected ICompositeMetadataDetailsProvider DetailsProvider { get; } - /// - public virtual ModelMetadata GetMetadataForParameter( - [NotNull] ParameterInfo parameterInfo, - [NotNull] IEnumerable 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); - } - /// public virtual IEnumerable GetMetadataForProperties([NotNull]Type modelType) { @@ -152,37 +134,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata return new DefaultMetadataDetailsCache(key, attributes); } - /// - /// Creates the entry for a parameter. - /// - /// - /// The identifying the model parameter. - /// - /// - /// The set of model attributes applied to the parameter. - /// - /// A cache object for the model parameter. - /// - /// The results of this method will be cached and used to satisfy calls to - /// . - /// Override this method to provide a different set of attributes. - /// - protected virtual DefaultMetadataDetailsCache CreateParameterCacheEntry( - [NotNull] ModelMetadataIdentity key, - [NotNull] IEnumerable attributes) - { - var allAttributes = new List(); - - if (attributes != null) - { - allAttributes.AddRange(attributes); - } - - allAttributes.AddRange(ModelAttributes.GetAttributesForParameter(key.ParameterInfo)); - - return new DefaultMetadataDetailsCache(key, allAttributes); - } - private class TypeCache : ConcurrentDictionary { } @@ -190,9 +141,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata private class PropertiesCache : ConcurrentDictionary { } - - private class ParameterCache : ConcurrentDictionary - { - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelAttributes.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelAttributes.cs index 543c8c6dbb..25f8a64225 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelAttributes.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelAttributes.cs @@ -15,22 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public static class ModelAttributes { - /// - /// Gets the attributes for the given . - /// - /// A for which attributes need to be resolved. - /// - /// An containing the attributes on the - /// before the attributes on the type. - public static IEnumerable GetAttributesForParameter(ParameterInfo parameter) - { - // Return the parameter attributes first. - var parameterAttributes = parameter.GetCustomAttributes(); - var typeAttributes = parameter.ParameterType.GetTypeInfo().GetCustomAttributes(); - - return parameterAttributes.Concat(typeAttributes); - } - /// /// Gets the attributes for the given . /// diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs index e595e0f2b3..ddbeefdd26 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataIdentity.cs @@ -89,11 +89,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { get { - if (ParameterInfo != null) - { - return ModelMetadataKind.Parameter; - } - else if (ContainerType != null && Name != null) + if (ContainerType != null && Name != null) { return ModelMetadataKind.Property; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs index 5cab259568..bffe3288c9 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadataKind.cs @@ -17,10 +17,5 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata /// Used for for a property. /// Property, - - /// - /// Used for for a parameter. - /// - Parameter, } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs index fa9b0267a9..42ec3527ea 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -28,26 +29,57 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } /// - /// Initializes a new instance of the class using the + /// Constructs a new instance of the class using the /// . /// /// Existing . /// Model name of associated with the new . /// Model metadata of associated with the new . /// - /// - /// This constructor copies certain values that won't change between parent and child objects, - /// e.g. ValueProvider, ModelState - /// - public ModelBindingContext([NotNull] ModelBindingContext bindingContext, - [NotNull] string modelName, - [NotNull] ModelMetadata modelMetadata) + public static ModelBindingContext GetChildModelBindingContext( + [NotNull] ModelBindingContext bindingContext, + [NotNull] string modelName, + [NotNull] ModelMetadata modelMetadata) { - ModelName = modelName; - ModelMetadata = modelMetadata; - ModelState = bindingContext.ModelState; - ValueProvider = bindingContext.ValueProvider; - OperationBindingContext = bindingContext.OperationBindingContext; + var modelBindingContext = new ModelBindingContext(); + modelBindingContext.ModelName = modelName; + modelBindingContext.ModelMetadata = modelMetadata; + modelBindingContext.ModelState = bindingContext.ModelState; + modelBindingContext.ValueProvider = bindingContext.ValueProvider; + modelBindingContext.OperationBindingContext = bindingContext.OperationBindingContext; + + modelBindingContext.BindingSource = modelMetadata.BindingSource; + modelBindingContext.BinderModelName = modelMetadata.BinderModelName; + modelBindingContext.BinderType = modelMetadata.BinderType; + return modelBindingContext; + } + + /// + /// Constructs a new instance of from given + /// and . + /// + /// associated with the model. + /// associated with the model. + /// An optional name of the model to be used. + /// A new instance of . + 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, + }; } /// @@ -118,6 +150,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + /// + /// Gets or sets a model name which is explicitly set using an . + /// . + /// + public string BinderModelName { get; set; } + + /// + /// Gets or sets a value which represents the associated with the + /// . + /// + public BindingSource BindingSource { get; set; } + + /// + /// Gets the of an associated with the + /// . + /// + public Type BinderType { get; set; } + /// /// Gets or sets a value that indicates whether the binder should use an empty prefix to look up /// values in when no values are found using the diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs index 2e6631c2e4..9a1ccd8939 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Web.Http; using Microsoft.AspNet.Mvc.ApplicationModels; @@ -15,30 +16,42 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { if (IsConventionApplicable(action.Controller)) { + var optionalParameters = new HashSet(); + var uriBindingSource = (new FromUriAttribute()).BindingSource; foreach (var parameter in action.Parameters) { - if (parameter.BinderMetadata is IBinderMetadata) + // Some IBindingSourceMetadata attributes like ModelBinder attribute return null + // as their binding source. Special case to ensure we do not ignore them. + if (parameter.BindingInfo.BindingSource != null || + parameter.Attributes.OfType().Any()) { // This has a binding behavior configured, just leave it alone. } else if (ValueProviderResult.CanConvertFromString(parameter.ParameterInfo.ParameterType)) { // Simple types are by-default from the URI. - parameter.BinderMetadata = new FromUriAttribute(); + + parameter.BindingInfo.BindingSource = uriBindingSource; } else { // Complex types are by-default from the body. - parameter.BinderMetadata = new FromBodyAttribute(); + parameter.BindingInfo.BindingSource = BindingSource.Body; } - // If the parameter has a default value, we want to consider it as optional parameter by default. - var optionalMetadata = parameter.BinderMetadata as FromUriAttribute; - if (parameter.ParameterInfo.HasDefaultValue && optionalMetadata != null) + // For all non IOptionalBinderMetadata, which are not URL source (like FromQuery etc.) do not + // participate in overload selection and hence are added to the hashset so that they can be + // ignored in OverloadActionConstraint. + var optionalMetadata = parameter.Attributes.OfType().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); } } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs index 1de7079f47..792f90220e 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs @@ -87,12 +87,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim } var parameters = new List(); - + object optionalParametersObject; + candidate.Action.Properties.TryGetValue("OptionalParameters", out optionalParametersObject); + var optionalParameters = (HashSet)optionalParametersObject; foreach (var parameter in candidate.Action.Parameters) { // We only consider parameters that are marked as bound from the URL. - var bindingSourceMetadata = parameter?.BinderMetadata as IBindingSourceMetadata; - var source = bindingSourceMetadata?.BindingSource; + var source = parameter.BindingInfo.BindingSource; if (source == null) { continue; @@ -102,17 +103,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim source.CanAcceptDataFrom(BindingSource.Query)) && ValueProviderResult.CanConvertFromString(parameter.ParameterType)) { - var optionalMetadata = parameter.BinderMetadata as IOptionalBinderMetadata; - if (optionalMetadata == null || optionalMetadata.IsOptional) + if (optionalParameters != null) { - // Optional parameters are ignored in overloading. If a parameter doesn't specify that it's - // required then treat it as optional (MVC default). WebAPI parameters will all by-default - // specify themselves as required unless they have a default value. - continue; + var isOptional = optionalParameters.Contains(parameter.Name); + if (isOptional) + { + // Optional parameters are ignored in overloading. If a parameter doesn't specify that it's + // required then treat it as optional (MVC default). WebAPI parameters will all by-default + // specify themselves as required unless they have a default value. + continue; + } } - var nameProvider = parameter.BinderMetadata as IModelNameProvider; - var prefix = nameProvider?.Name ?? parameter.Name; + var prefix = parameter.BindingInfo.BinderModelName ?? parameter.Name; parameters.Add(new OverloadedParameter() { diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs index d764f75a18..7cb362989a 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs @@ -4,13 +4,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { /// - /// An that designates an optional parameter for the purposes + /// An type that designates an optional parameter for the purposes /// of WebAPI action overloading. Optional parameters do not participate in overloading, and /// do not have to have a value for the action to be selected. /// /// This has no impact when used without WebAPI action overloading. /// - public interface IOptionalBinderMetadata : IBinderMetadata + public interface IOptionalBinderMetadata { bool IsOptional { get; } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs index cadec76d99..9e209af80b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Xunit; namespace Microsoft.AspNet.Mvc.ApplicationModels @@ -18,7 +19,11 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels new List() { new FromBodyAttribute() }); parameter.Action = new ActionModel(typeof(TestController).GetMethod("Edit"), new List()); - parameter.BinderMetadata = (IBinderMetadata)parameter.Attributes[0]; + parameter.BindingInfo = new BindingInfo() + { + BindingSource = BindingSource.Body + }; + parameter.ParameterName = "id"; // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs index 248574f964..cc7482add9 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs @@ -95,6 +95,7 @@ namespace Microsoft.AspNet.Mvc provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Header); var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider); + bindingContext.BindingSource = BindingSource.Header; var binder = bindingContext.OperationBindingContext.ModelBinder; @@ -113,6 +114,7 @@ namespace Microsoft.AspNet.Mvc provider.ForType().BindingDetails(d => d.BindingSource = null); var bindingContext = GetBindingContext(typeof(Person), metadataProvider: provider); + bindingContext.BindingSource = null; var binder = bindingContext.OperationBindingContext.ModelBinder; @@ -186,6 +188,7 @@ namespace Microsoft.AspNet.Mvc ValueProvider = Mock.Of(), ModelState = new ModelStateDictionary(), OperationBindingContext = operationBindingContext, + BindingSource = BindingSource.Body, }; return bindingContext; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs index 78844d9182..573215e5f2 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs @@ -8,6 +8,7 @@ using System.Reflection; using Microsoft.AspNet.Mvc.ApplicationModels; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Description; +using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Routing; using Microsoft.Framework.Internal; using Moq; @@ -110,7 +111,7 @@ namespace Microsoft.AspNet.Mvc.Test var id = Assert.Single(main.Parameters); Assert.Equal("id", id.Name); - Assert.Null(id.BinderMetadata); + Assert.Null(id.BindingInfo.BindingSource); Assert.Equal(typeof(int), id.ParameterType); } @@ -129,13 +130,13 @@ namespace Microsoft.AspNet.Mvc.Test var id = Assert.Single(main.Parameters, p => p.Name == "id"); Assert.Equal("id", id.Name); - Assert.Null(id.BinderMetadata); + Assert.Null(id.BindingInfo.BindingSource); Assert.Equal(typeof(int), id.ParameterType); var entity = Assert.Single(main.Parameters, p => p.Name == "entity"); Assert.Equal("entity", entity.Name); - Assert.IsType(entity.BinderMetadata); + Assert.Equal(entity.BindingInfo.BindingSource, BindingSource.Body); Assert.Equal(typeof(TestActionParameter), entity.ParameterType); } @@ -154,19 +155,19 @@ namespace Microsoft.AspNet.Mvc.Test var id = Assert.Single(main.Parameters, p => p.Name == "id"); Assert.Equal("id", id.Name); - Assert.Null(id.BinderMetadata); + Assert.Null(id.BindingInfo.BindingSource); Assert.Equal(typeof(int), id.ParameterType); var upperCaseId = Assert.Single(main.Parameters, p => p.Name == "ID"); Assert.Equal("ID", upperCaseId.Name); - Assert.Null(upperCaseId.BinderMetadata); + Assert.Null(upperCaseId.BindingInfo.BindingSource); Assert.Equal(typeof(int), upperCaseId.ParameterType); var pascalCaseId = Assert.Single(main.Parameters, p => p.Name == "Id"); Assert.Equal("Id", pascalCaseId.Name); - Assert.Null(id.BinderMetadata); + Assert.Null(id.BindingInfo.BindingSource); Assert.Equal(typeof(int), pascalCaseId.ParameterType); } @@ -187,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Test var entity = Assert.Single(fromBody.Parameters); Assert.Equal("entity", entity.Name); - Assert.IsType(entity.BinderMetadata); + Assert.Equal(entity.BindingInfo.BindingSource, BindingSource.Body); Assert.Equal(typeof(TestActionParameter), entity.ParameterType); } @@ -208,7 +209,7 @@ namespace Microsoft.AspNet.Mvc.Test var entity = Assert.Single(notFromBody.Parameters); Assert.Equal("entity", entity.Name); - Assert.Null(entity.BinderMetadata); + Assert.Null(entity.BindingInfo.BindingSource); Assert.Equal(typeof(TestActionParameter), entity.ParameterType); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs index b8e794f540..02993b33c3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Testing; using Microsoft.Framework.DependencyInjection; @@ -2073,6 +2074,7 @@ namespace Microsoft.AspNet.Mvc { Name = "value", ParameterType = typeof(int), + BindingInfo = new BindingInfo(), } }, FilterDescriptors = new List() @@ -2106,11 +2108,11 @@ namespace Microsoft.AspNet.Mvc metadataProvider, new DefaultObjectValidator(Mock.Of(), metadataProvider), new MockMvcOptionsAccessor()), - new MockModelBinderProvider() { ModelBinders = new List() { binder.Object } }, - new MockModelValidatorProviderProvider(), - new MockValueProviderFactoryProvider(), - new MockScopedInstance(), - Mock.Of()); + new MockModelBinderProvider() { ModelBinders = new List() { binder.Object } }, + new MockModelValidatorProviderProvider(), + new MockValueProviderFactoryProvider(), + new MockScopedInstance(), + Mock.Of()); // Act await invoker.InvokeAsync(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs index 4d751c859e..0c1469438f 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Constraints; @@ -964,9 +965,9 @@ namespace Microsoft.AspNet.Mvc.Description { action.Parameters.Add(new ParameterDescriptor() { - BinderMetadata = parameter.GetCustomAttributes().OfType().FirstOrDefault(), Name = parameter.Name, ParameterType = parameter.ParameterType, + BindingInfo = BindingInfo.GetBindingInfo(parameter.GetCustomAttributes().OfType()) }); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs index 2d27a2dfa5..0c8b04b043 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Routing; using Moq; using Xunit; @@ -48,109 +49,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test { } - [Fact] - public void GetModelBindingContext_ReturnsOnlyIncludedProperties_UsingBindAttributeInclude() - { - // Arrange - var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var modelMetadata = metadataProvider.GetMetadataForType( - typeof(TypeWithIncludedPropertiesUsingBindAttribute)); - - // Act - var context = DefaultControllerActionArgumentBinder.GetModelBindingContext( - modelMetadata, - new ModelStateDictionary(), - Mock.Of()); - - // 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()); - - // 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()); - - // 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()); - - // Assert - Assert.Equal(expectedFallToEmptyPrefix, context.FallbackToEmptyPrefix); - Assert.Equal(expectedModelName, context.ModelName); - } - [Fact] public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderReturnsFalse() { @@ -165,6 +63,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { Name = "foo", ParameterType = typeof(object), + BindingInfo = new BindingInfo(), } } }; @@ -215,6 +114,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { Name = "foo", ParameterType = typeof(object), + BindingInfo = new BindingInfo(), } } }; @@ -266,6 +166,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { Name = "foo", ParameterType = typeof(string), + BindingInfo = new BindingInfo(), } }, }; @@ -322,6 +223,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { Name = "foo", ParameterType = typeof(object), + BindingInfo = new BindingInfo(), } } }; @@ -375,6 +277,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { Name = "foo", ParameterType = typeof(object), + BindingInfo = new BindingInfo(), } } }; @@ -423,6 +326,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test { Name = "foo", ParameterType = typeof(object), + BindingInfo = new BindingInfo(), } } }; diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs index 5e0b962984..eac307abe7 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTest.cs @@ -898,7 +898,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } [Fact] - public async Task BindAttribute_DoesNotUseTypePrefix() + public async Task BindAttribute_FallsBackOnTypePrefixIfNoParameterPrefixIsProvided() { // Arrange var server = TestHelper.CreateServer(_app, SiteName, _configureServices); @@ -906,24 +906,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.GetStringAsync("http://localhost/BindAttribute/" + - "TypePrefixIsNeverUsed" + - "?param.Value=someValue"); - - // Assert - Assert.Equal("someValue", response); - } - - [Fact] - public async Task BindAttribute_FallsBackOnEmptyPrefixIfNoParameterPrefixIsProvided() - { - // Arrange - var server = TestHelper.CreateServer(_app, SiteName, _configureServices); - var client = server.CreateClient(); - - // Act - var response = await client.GetStringAsync("http://localhost/BindAttribute/" + - "TypePrefixIsNeverUsed" + - "?Value=someValue"); + "TypePrefixIsUsed" + + "?TypePrefix.Value=someValue"); // Assert Assert.Equal("someValue", response); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs index 638bc2eb25..45beefa362 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BinderTypeBasedModelBinderModelBinderTest.cs @@ -133,6 +133,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test ValueProvider = Mock.Of(), ModelState = new ModelStateDictionary(), OperationBindingContext = operationBindingContext, + BinderType = binderType }; return bindingContext; diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs index 8a075f4ec4..4e1bcdff5a 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs @@ -74,9 +74,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var provider = new TestModelMetadataProvider(); provider.ForType().BindingDetails(d => d.BindingSource = BindingSource.Body); - - var context = new ModelBindingContext(); - context.ModelMetadata = provider.GetMetadataForType(typeof(string)); + var modelMetadata = provider.GetMetadataForType(typeof(string)); + var context = new ModelBindingContext() + { + ModelMetadata = modelMetadata, + BindingSource = modelMetadata.BindingSource, + BinderModelName = modelMetadata.BinderModelName + }; var binder = new TestableBindingSourceModelBinder(BindingSource.Body); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs index eab187aee4..40045bbb95 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs @@ -76,17 +76,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { var metadataProvider = new TestModelMetadataProvider(); metadataProvider.ForType(modelType).BindingDetails(d => d.BindingSource = BindingSource.Header); - + var modelMetadata = metadataProvider.GetMetadataForType(modelType); var bindingContext = new ModelBindingContext { - ModelMetadata = metadataProvider.GetMetadataForType(modelType), + ModelMetadata = modelMetadata, ModelName = "modelName", OperationBindingContext = new OperationBindingContext { ModelBinder = new HeaderModelBinder(), MetadataProvider = metadataProvider, HttpContext = new DefaultHttpContext() - } + }, + BinderModelName = modelMetadata.BinderModelName, + BindingSource = modelMetadata.BindingSource, }; return bindingContext; diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ModelBindingContextTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ModelBindingContextTest.cs index 8d02cfa677..2d027f588d 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ModelBindingContextTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ModelBindingContextTest.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Threading.Tasks; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Test @@ -8,24 +10,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public class ModelBindingContextTest { [Fact] - public void CopyConstructor() + public void GetChildModelBindingContext() { // Arrange var originalBindingContext = new ModelBindingContext { - ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)), + ModelMetadata = new TestModelMetadataProvider().GetMetadataForType(typeof(object)), ModelName = "theName", ModelState = new ModelStateDictionary(), ValueProvider = new SimpleHttpValueProvider() }; - var newModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(typeof(object)); + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider.ForType().BindingDetails(d => + { + d.BindingSource = BindingSource.Custom; + d.BinderType = typeof(TestModelBinder); + d.BinderModelName = "custom"; + }); + var newModelMetadata = metadataProvider.GetMetadataForType(typeof(object)); + // Act - var newBindingContext = new ModelBindingContext(originalBindingContext, string.Empty, newModelMetadata); + var newBindingContext = ModelBindingContext.GetChildModelBindingContext( + originalBindingContext, + string.Empty, + newModelMetadata); // Assert Assert.Same(newModelMetadata, newBindingContext.ModelMetadata); + Assert.Same(newModelMetadata.BindingSource, newBindingContext.BindingSource); + Assert.Same(newModelMetadata.BinderModelName, newBindingContext.BinderModelName); + Assert.Same(newModelMetadata.BinderType, newBindingContext.BinderType); Assert.Equal("", newBindingContext.ModelName); Assert.Equal(originalBindingContext.ModelState, newBindingContext.ModelState); Assert.Equal(originalBindingContext.ValueProvider, newBindingContext.ValueProvider); @@ -43,5 +59,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test // Assert Assert.Equal(typeof(int), bindingContext.ModelType); } + + private class TestModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + throw new NotImplementedException(); + } + } } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs index aae83fb56b..297f717c32 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs @@ -32,11 +32,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding .Returns(Task.FromResult(false)); var metadataProvider = new TestModelMetadataProvider(); - if (isPrefixProvided) - { - metadataProvider.ForType().BindingDetails(bd => bd.BinderModelName = "prefix"); - } - var bindingContext = new MutableObjectBinderContext { ModelBindingContext = new ModelBindingContext @@ -53,6 +48,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Setting it to empty ensures that model does not get created becasue of no model name. ModelName = "dummyModelName", + BinderModelName = isPrefixProvided ? "prefix" : null, } }; @@ -110,6 +106,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding [Fact] public async Task CanCreateModel_ReturnsFalse_ForNonTopLevelModel_IfModelIsMarkedWithBinderMetadata() { + var modelMetadata = GetMetadataForType(typeof(Document)) + .Properties + .First(metadata => metadata.PropertyName == nameof(Document.SubDocument)); var bindingContext = new MutableObjectBinderContext { ModelBindingContext = new ModelBindingContext @@ -121,7 +120,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding OperationBindingContext = new OperationBindingContext { ValidatorProvider = Mock.Of(), - } + }, + BindingSource = modelMetadata.BindingSource, + BinderModelName = modelMetadata.BinderModelName, } }; @@ -265,12 +266,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return null; }); - + + var modelMetadata = GetMetadataForType(modelType); var bindingContext = new MutableObjectBinderContext { ModelBindingContext = new ModelBindingContext { - ModelMetadata = GetMetadataForType(modelType), + ModelMetadata = modelMetadata, ValueProvider = mockValueProvider.Object, OperationBindingContext = new OperationBindingContext { @@ -280,7 +282,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding }, // Setting it to empty ensures that model does not get created becasue of no model name. - ModelName = "dummyName" + ModelName = "dummyName", + BindingSource = modelMetadata.BindingSource, + BinderModelName = modelMetadata.BinderModelName } }; diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs index 8fdae63905..f954b67582 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultBindingMetadataProviderTest.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata } [Fact] - public void GetBindingDetails_FindsModelName_IfEmpty() + public void GetBindingDetails_FindsModelName_IfNullFallsBack() { // Arrange var attributes = new object[] @@ -98,7 +98,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata provider.GetBindingMetadata(context); // Assert - Assert.Null(context.BindingMetadata.BinderModelName); + Assert.Equal("Product", context.BindingMetadata.BinderModelName); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs index 96b213aa49..24fc89db0b 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/DefaultModelMetadataProviderTest.cs @@ -110,66 +110,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Equal("OnPropertyType", Assert.IsType(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(metadata); - - var attributes = defaultMetadata.Attributes.ToArray(); - Assert.Equal("Extra", Assert.IsType(attributes[0]).Value); - Assert.Equal("OnParameter", Assert.IsType(attributes[1]).Value); - Assert.Equal("OnType", Assert.IsType(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(provider.GetMetadataForParameter( - methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(), - additionalAttributes)); - var metadata2 = Assert.IsType(provider.GetMetadataForParameter( - methodInfo.GetParameters().Where(p => p.Name == "parameter").Single(), - attributes: null)); - - // Assert - Assert.Same(metadata1.Attributes, metadata2.Attributes); - Assert.Same(metadata1.BindingMetadata, metadata2.BindingMetadata); - Assert.Same(metadata1.DisplayMetadata, metadata2.DisplayMetadata); - Assert.Same(metadata1.ValidationMetadata, metadata2.ValidationMetadata); - } - private static DefaultModelMetadataProvider CreateProvider() { return new DefaultModelMetadataProvider(new EmptyCompositeMetadataDetailsProvider()); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs index 72bbba3bd5..5f11ea44c3 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataProviderTest.cs @@ -43,103 +43,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Equal(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(); - foreach (var property in metadata.Properties) - { - if (predicate(context, property.PropertyName)) - { - matched.Add(property.PropertyName); - } - } - - Assert.Equal(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(); - foreach (var property in metadata.Properties) - { - if (predicate(context, property.PropertyName)) - { - matched.Add(property.PropertyName); - } - } - - Assert.Equal(expected, matched); - } - - [Fact] - public void ModelMetadataProvider_ReadsModelNameProperty_ForParameters() - { - // Arrange - var type = GetType(); - var methodInfo = type.GetMethod( - "ActionWithBindAttribute", - BindingFlags.Instance | BindingFlags.NonPublic); - var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "param").Single(); - - var provider = CreateProvider(); - - // Act - var metadata = provider.GetMetadataForParameter( - parameterInfo, - attributes: null); - - // Assert - Assert.Equal("ParameterPrefix", metadata.BinderModelName); - } - [Fact] public void ModelMetadataProvider_ReadsModelNameProperty_ForTypes() { @@ -267,60 +170,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata Assert.Equal("GrandParentProperty", propertyMetadata.BinderModelName); } - [Fact] - public void GetMetadataForParameter_WithNoBinderMetadata_GetsItFromType() - { - // Arrange - var provider = CreateProvider(); - - var methodInfo = typeof(Person).GetMethod("Update"); - var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single(); - - // Act - var parameterMetadata = provider.GetMetadataForParameter( - parameterInfo, - attributes: null); - - // Assert - Assert.Equal("PersonType", parameterMetadata.BinderModelName); - } - - [Fact] - public void GetMetadataForParameter_WithBinderDataOnParameterAndType_GetsMetadataFromParameter() - { - // Arrange - var provider = CreateProvider(); - - var methodInfo = typeof(Person).GetMethod("Save"); - var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single(); - - // Act - var parameterMetadata = provider.GetMetadataForParameter( - parameterInfo, - attributes: null); - - // Assert - Assert.Equal("PersonParameter", parameterMetadata.BinderModelName); - } - - [Fact] - public void GetMetadataForParameter_BinderModelNameOverride() - { - // Arrange - var provider = CreateProvider(); - - var methodInfo = typeof(Person).GetMethod("Save"); - var parameterInfo = methodInfo.GetParameters().Where(p => p.Name == "person").Single(); - - // Act - var parameterMetadata = provider.GetMetadataForParameter( - parameterInfo, - attributes: new object[] { new ModelBinderAttribute() { Name = "Override" } }); - - // Assert - Assert.Equal("Override", parameterMetadata.BinderModelName); - } - public static TheoryData> ExpectedAttributeDataStrings { get @@ -780,12 +629,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Metadata { } - public class TypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider + public class TypeBasedBinderAttribute : Attribute, IModelNameProvider { public string Name { get; set; } } - public class NonTypeBasedBinderAttribute : Attribute, IBinderMetadata, IModelNameProvider + public class NonTypeBasedBinderAttribute : Attribute, IModelNameProvider { public string Name { get; set; } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestBinderMetadata.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestBinderMetadata.cs deleted file mode 100644 index 3fc8528321..0000000000 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/TestBinderMetadata.cs +++ /dev/null @@ -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 - { - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs index 35e49f5cc1..97e895f9f0 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Properties/Resources.Designer.cs @@ -26,6 +26,38 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test return string.Format(CultureInfo.CurrentCulture, GetString("CompareAttributeTestResource"), p0, p1); } + /// + /// description from resources + /// + internal static string DisplayAttribute_Description + { + get { return GetString("DisplayAttribute_Description"); } + } + + /// + /// description from resources + /// + internal static string FormatDisplayAttribute_Description() + { + return GetString("DisplayAttribute_Description"); + } + + /// + /// name from resources + /// + internal static string DisplayAttribute_Name + { + get { return GetString("DisplayAttribute_Name"); } + } + + /// + /// name from resources + /// + internal static string FormatDisplayAttribute_Name() + { + return GetString("DisplayAttribute_Name"); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs index 42059eb0d9..f30dbe6540 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.ApplicationModels; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Filters; +using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.WebApiCompatShim; using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; @@ -276,8 +277,9 @@ namespace System.Web.Http foreach (var action in actions) { var parameter = Assert.Single(action.Parameters); - var metadata = Assert.IsType(parameter.BinderMetadata); - Assert.False(metadata.IsOptional); + Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource); + var optionalParameters = (HashSet)action.Properties["OptionalParameters"]; + Assert.False(optionalParameters.Contains(parameter.Name)); } } @@ -304,12 +306,12 @@ namespace System.Web.Http foreach (var action in actions) { var parameter = Assert.Single(action.Parameters); - Assert.IsType(parameter.BinderMetadata); + Assert.Equal(BindingSource.Body, parameter.BindingInfo.BindingSource); } } [Fact] - public void GetActions_Parameters_BinderMetadata() + public void GetActions_Parameters_WithBindingSource() { // Arrange var provider = CreateProvider(); @@ -331,7 +333,7 @@ namespace System.Web.Http foreach (var action in actions) { var parameter = Assert.Single(action.Parameters); - Assert.IsType(parameter.BinderMetadata); + Assert.Null(parameter.BindingInfo.BindingSource); } } @@ -360,8 +362,9 @@ namespace System.Web.Http foreach (var action in actions) { var parameter = Assert.Single(action.Parameters); - var metadata = Assert.IsType(parameter.BinderMetadata); - Assert.True(metadata.IsOptional); + Assert.Equal((new FromUriAttribute()).BindingSource, parameter.BindingInfo.BindingSource); + var optionalParameters = (HashSet)action.Properties["OptionalParameters"]; + Assert.True(optionalParameters.Contains(parameter.Name)); } } diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs index 3516fab14b..9c14289449 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Web.Http; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Core; +using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Routing; using Xunit; @@ -21,7 +22,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, @@ -51,13 +55,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, @@ -87,13 +97,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, @@ -123,13 +139,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, @@ -159,18 +181,28 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute() { IsOptional = true }, + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, }; + var optionalParameters = new HashSet(); + optionalParameters.Add("quantity"); + + action.Properties.Add("OptionalParameters", optionalParameters); var constraint = new OverloadActionConstraint(); var context = new ActionConstraintContext(); @@ -195,13 +227,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, @@ -212,13 +250,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity_ordered", ParameterType = typeof(int), }, @@ -252,7 +296,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, @@ -263,13 +310,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, @@ -300,30 +353,46 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute() { IsOptional = true }, + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, }; + var optionalParameters = new HashSet(); + optionalParameters.Add("quantity"); + action1.Properties.Add("OptionalParameters", optionalParameters); + var action2 = new ActionDescriptor(); action2.Parameters = new List() { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, @@ -354,13 +423,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, @@ -371,13 +446,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "price", ParameterType = typeof(decimal), }, @@ -411,7 +492,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, @@ -422,18 +506,28 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute() { IsOptional = true }, + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, }; + var optionalParameters = new HashSet(); + optionalParameters.Add("quantity"); + action2.Properties.Add("OptionalParameters", optionalParameters); + var constraint = new OverloadActionConstraint(); var context = new ActionConstraintContext(); @@ -459,7 +553,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, @@ -470,13 +567,19 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromUriAttribute()).BindingSource, + }, Name = "id", ParameterType = typeof(int), }, new ParameterDescriptor() { - BinderMetadata = new FromBodyAttribute(), + BindingInfo = new BindingInfo() + { + BindingSource = (new FromBodyAttribute()).BindingSource, + }, Name = "quantity", ParameterType = typeof(int), }, diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs b/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs index 4c579d3e36..305a98ffb6 100644 --- a/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs +++ b/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs @@ -16,19 +16,16 @@ namespace ApplicationModelWebSite { public string GetParameterMetadata([Cool] int? id) { - return ActionContext.ActionDescriptor.Parameters[0].BinderMetadata.GetType().Name; + return ActionContext.ActionDescriptor.Parameters[0].BindingInfo.BinderModelName; } private class CoolAttribute : Attribute, IParameterModelConvention { public void Apply(ParameterModel model) { - model.BinderMetadata = new CoolMetadata(); + model.BindingInfo.BindingSource = BindingSource.Custom; + model.BindingInfo.BinderModelName = "CoolMetadata"; } } - - private class CoolMetadata : IBinderMetadata - { - } } } \ No newline at end of file diff --git a/test/WebSites/CorsMiddlewareWebSite/CorsMiddlewareWebSite.xproj b/test/WebSites/CorsMiddlewareWebSite/CorsMiddlewareWebSite.xproj index 01e9f9db63..7c73909466 100644 --- a/test/WebSites/CorsMiddlewareWebSite/CorsMiddlewareWebSite.xproj +++ b/test/WebSites/CorsMiddlewareWebSite/CorsMiddlewareWebSite.xproj @@ -6,7 +6,7 @@ - 94ba134d-04b3-48aa-ba55-5a4db8640f2d + b42d4844-fff8-4ec2-88d1-3ae95234d9eb ..\..\..\artifacts\obj\$(MSBuildProjectName) ..\..\..\artifacts\bin\$(MSBuildProjectName)\ diff --git a/test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs b/test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs index 5bf33467e1..85ac86d2e0 100644 --- a/test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs +++ b/test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs @@ -15,7 +15,8 @@ namespace FormatterWebSite { var bodyParameter = context.ActionDescriptor .Parameters - .FirstOrDefault(parameter => IsBodyBindingSource(parameter.BinderMetadata)); + .FirstOrDefault(parameter => IsBodyBindingSource( + parameter.BindingInfo.BindingSource)); if (bodyParameter != null) { var parameterBindingErrors = context.ModelState[bodyParameter.Name].Errors; @@ -37,9 +38,8 @@ namespace FormatterWebSite base.OnActionExecuting(context); } - private bool IsBodyBindingSource(IBinderMetadata binderMetadata) + private bool IsBodyBindingSource(BindingSource bindingSource) { - var bindingSource = (binderMetadata as IBindingSourceMetadata)?.BindingSource; return bindingSource?.CanAcceptDataFrom(BindingSource.Body) ?? false; } } diff --git a/test/WebSites/ModelBindingWebSite/Controllers/BindAttributeController.cs b/test/WebSites/ModelBindingWebSite/Controllers/BindAttributeController.cs index 1610f5fbd5..ad9baec617 100644 --- a/test/WebSites/ModelBindingWebSite/Controllers/BindAttributeController.cs +++ b/test/WebSites/ModelBindingWebSite/Controllers/BindAttributeController.cs @@ -65,8 +65,7 @@ namespace ModelBindingWebSite.Controllers return param.Value; } - // This will use param to try to bind and not the value specified at TypePrefix. - public string TypePrefixIsNeverUsed([Bind] TypePrefix param) + public string TypePrefixIsUsed([Bind] TypePrefix param) { return param.Value; } diff --git a/test/WebSites/ModelBindingWebSite/FromNonExistantBinderAttribute.cs b/test/WebSites/ModelBindingWebSite/FromNonExistantBinderAttribute.cs index 63357fa801..d890e92534 100644 --- a/test/WebSites/ModelBindingWebSite/FromNonExistantBinderAttribute.cs +++ b/test/WebSites/ModelBindingWebSite/FromNonExistantBinderAttribute.cs @@ -6,7 +6,14 @@ using Microsoft.AspNet.Mvc.ModelBinding; namespace ModelBindingWebSite { - public class FromNonExistantBinderAttribute : Attribute, IBinderMetadata + public class FromNonExistantBinderAttribute : Attribute, IBindingSourceMetadata { + public BindingSource BindingSource + { + get + { + return BindingSource.Custom; + } + } } } \ No newline at end of file