diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs
index a824e171d6..782b84b2cd 100644
--- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.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.Reflection;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.Extensions.Internal;
@@ -65,6 +66,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
};
}
+ public static ModelMetadataIdentity ForParameter(ParameterInfo parameter)
+ {
+ if (parameter == null)
+ {
+ throw new ArgumentNullException(nameof(parameter));
+ }
+
+ return new ModelMetadataIdentity()
+ {
+ Name = parameter.Name,
+ ModelType = parameter.ParameterType,
+ ParameterInfo = parameter,
+ };
+ }
+
///
/// Gets the defining the model property represented by the current
/// instance, or null if the current instance does not represent a property.
@@ -83,7 +99,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{
get
{
- if (ContainerType != null && Name != null)
+ if (ParameterInfo != null)
+ {
+ return ModelMetadataKind.Parameter;
+ }
+ else if (ContainerType != null && Name != null)
{
return ModelMetadataKind.Property;
}
@@ -100,13 +120,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
///
public string Name { get; private set; }
+ ///
+ /// Gets a descriptor for the parameter, or null if this instance
+ /// does not represent a parameter.
+ ///
+ public ParameterInfo ParameterInfo { get; private set; }
+
///
public bool Equals(ModelMetadataIdentity other)
{
return
ContainerType == other.ContainerType &&
ModelType == other.ModelType &&
- Name == other.Name;
+ Name == other.Name &&
+ ParameterInfo == other.ParameterInfo;
}
///
@@ -123,6 +150,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
hash.Add(ContainerType);
hash.Add(ModelType);
hash.Add(Name, StringComparer.Ordinal);
+ hash.Add(ParameterInfo);
return hash;
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs
index 3eb836cc18..4756f85226 100644
--- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs
@@ -17,5 +17,10 @@ namespace Microsoft.AspNetCore.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.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs
new file mode 100644
index 0000000000..f32bcf00e2
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs
@@ -0,0 +1,37 @@
+// Copyright (c) .NET Foundation. 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.Reflection;
+using Microsoft.AspNetCore.Mvc.Abstractions;
+
+namespace Microsoft.AspNetCore.Mvc.ModelBinding
+{
+ ///
+ /// A provider that can supply instances of .
+ ///
+ public abstract class ModelMetadataProvider : IModelMetadataProvider
+ {
+ ///
+ /// Supplies metadata describing the properties of a .
+ ///
+ /// The .
+ /// A set of instances describing properties of the .
+ public abstract IEnumerable GetMetadataForProperties(Type modelType);
+
+ ///
+ /// Supplies metadata describing a .
+ ///
+ /// The .
+ /// A instance describing the .
+ public abstract ModelMetadata GetMetadataForType(Type modelType);
+
+ ///
+ /// Supplies metadata describing a parameter.
+ ///
+ /// The .
+ /// A instance describing properties of the .
+ public abstract ModelMetadata GetMetadataForParameter(ParameterInfo parameter);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
index a6dfebfc1e..223ada2cdd 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
@@ -231,7 +231,14 @@ namespace Microsoft.Extensions.DependencyInjection
return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders);
});
services.TryAddSingleton();
- services.TryAddSingleton();
+ services.TryAddSingleton(s =>
+ {
+ var options = s.GetRequiredService>().Value;
+ var metadataProvider = s.GetRequiredService();
+ var modelBinderFactory = s.GetRequiredService();
+ var modelValidatorProvider = new CompositeModelValidatorProvider(options.ModelValidatorProviders);
+ return new ParameterBinder(metadataProvider, modelBinderFactory, modelValidatorProvider);
+ });
//
// Random Infrastructure
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs
index 16bfca522b..06c493b79c 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs
@@ -51,13 +51,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
var parameter = parameters[i];
var bindingInfo = parameterBindingInfo[i];
+ var modelMetadata = bindingInfo.ModelMetadata;
+
+ if (!modelMetadata.IsBindingAllowed)
+ {
+ continue;
+ }
var result = await parameterBinder.BindModelAsync(
controllerContext,
bindingInfo.ModelBinder,
valueProvider,
parameter,
- bindingInfo.ModelMetadata,
+ modelMetadata,
value: null);
if (result.IsModelSet)
@@ -71,13 +77,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
var property = properties[i];
var bindingInfo = propertyBindingInfo[i];
+ var modelMetadata = bindingInfo.ModelMetadata;
+
+ if (!modelMetadata.IsBindingAllowed)
+ {
+ continue;
+ }
var result = await parameterBinder.BindModelAsync(
controllerContext,
bindingInfo.ModelBinder,
valueProvider,
property,
- bindingInfo.ModelMetadata,
+ modelMetadata,
value: null);
if (result.IsModelSet)
@@ -103,7 +115,24 @@ namespace Microsoft.AspNetCore.Mvc.Internal
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
- var metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
+
+ ModelMetadata metadata;
+ if (modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase
+ && parameter is ControllerParameterDescriptor controllerParameterDescriptor)
+ {
+ // The default model metadata provider derives from ModelMetadataProvider
+ // and can therefore supply information about attributes applied to parameters.
+ metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo);
+ }
+ else
+ {
+ // For backward compatibility, if there's a custom model metadata provider that
+ // only implements the older IModelMetadataProvider interface, access the more
+ // limited metadata information it supplies. In this scenario, validation attributes
+ // are not supported on parameters.
+ metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
+ }
+
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
BindingInfo = parameter.BindingInfo,
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs
index 7d034e1200..8e0546b992 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs
@@ -68,25 +68,31 @@ namespace Microsoft.AspNetCore.Mvc.Internal
context.BindingMetadata.PropertyFilterProvider = composite;
}
- if (context.Key.MetadataKind == ModelMetadataKind.Property)
+ var bindingBehavior = FindBindingBehavior(context);
+ if (bindingBehavior != null)
{
- // BindingBehavior can fall back to attributes on the Container Type, but we should ignore
- // attributes on the Property Type.
- var bindingBehavior = context.PropertyAttributes.OfType().FirstOrDefault();
- if (bindingBehavior == null)
- {
- bindingBehavior =
- context.Key.ContainerType.GetTypeInfo()
- .GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true)
- .OfType()
- .FirstOrDefault();
- }
+ context.BindingMetadata.IsBindingAllowed = bindingBehavior.Behavior != BindingBehavior.Never;
+ context.BindingMetadata.IsBindingRequired = bindingBehavior.Behavior == BindingBehavior.Required;
+ }
+ }
- if (bindingBehavior != null)
- {
- context.BindingMetadata.IsBindingAllowed = bindingBehavior.Behavior != BindingBehavior.Never;
- context.BindingMetadata.IsBindingRequired = bindingBehavior.Behavior == BindingBehavior.Required;
- }
+ private static BindingBehaviorAttribute FindBindingBehavior(BindingMetadataProviderContext context)
+ {
+ switch (context.Key.MetadataKind)
+ {
+ case ModelMetadataKind.Property:
+ // BindingBehavior can fall back to attributes on the Container Type, but we should ignore
+ // attributes on the Property Type.
+ var matchingAttributes = context.PropertyAttributes.OfType();
+ return matchingAttributes.FirstOrDefault()
+ ?? context.Key.ContainerType.GetTypeInfo()
+ .GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true)
+ .OfType()
+ .FirstOrDefault();
+ case ModelMetadataKind.Parameter:
+ return context.ParameterAttributes.OfType().FirstOrDefault();
+ default:
+ return null;
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs
index 3bcdfab9ff..12a5077d41 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// system excludes that property. When applied to a type, the model binding system excludes all properties that
/// type defines.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public sealed class BindNeverAttribute : BindingBehaviorAttribute
{
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs
index b98f120e12..247dde0e26 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// requires a value for that property. When applied to a type, the model binding system requires values for all
/// properties that type defines.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public sealed class BindRequiredAttribute : BindingBehaviorAttribute
{
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs
index 84492d5806..b27c8451d4 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs
@@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
///
/// Specifies the that should be applied.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class BindingBehaviorAttribute : Attribute
{
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs
index 24fdc2475f..587cf6fb69 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs
@@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
Key = key;
Attributes = attributes.Attributes;
+ ParameterAttributes = attributes.ParameterAttributes;
PropertyAttributes = attributes.PropertyAttributes;
TypeAttributes = attributes.TypeAttributes;
@@ -43,6 +44,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
///
public ModelMetadataIdentity Key { get; }
+ ///
+ /// Gets the parameter attributes.
+ ///
+ public IReadOnlyList