diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs
index 31ec6028f3..14935089b7 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs
@@ -27,9 +27,9 @@ namespace Microsoft.AspNet.Mvc.Description
public ApiParameterRouteInfo RouteInfo { get; set; }
///
- /// Gets or sets the .
+ /// Gets or sets the .
///
- public ApiParameterSource Source { get; set; }
+ public BindingSource Source { get; set; }
///
/// Gets or sets the parameter type.
diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs
index 7379f42048..12caf8d4ad 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs
@@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.Description
/// An optional parameter is considered optional by the routing system. This does not imply
/// that the parameter is considered optional by the action.
///
- /// If the parameter uses for the value of
+ /// If the parameter uses for the value of
/// then the value may also come from the
/// URL query string or form data.
///
diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterSource.cs b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterSource.cs
deleted file mode 100644
index 8cd5f9886b..0000000000
--- a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterSource.cs
+++ /dev/null
@@ -1,130 +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.
-
-using System;
-using System.Diagnostics;
-using Microsoft.AspNet.Mvc.Core;
-
-namespace Microsoft.AspNet.Mvc.Description
-{
- ///
- /// A metadata description of the source of an for an HTTP request.
- ///
- [DebuggerDisplay("Source: {DisplayName}")]
- public class ApiParameterSource : IEquatable
- {
- ///
- /// An for the request body.
- ///
- public static readonly ApiParameterSource Body = new ApiParameterSource(
- "Body",
- Resources.ApiParameterSource_Body);
-
- ///
- /// An for a custom model binder (unknown data source).
- ///
- public static readonly ApiParameterSource Custom = new ApiParameterSource(
- "Custom",
- Resources.ApiParameterSource_Custom);
-
- ///
- /// An for the request form-data.
- ///
- public static readonly ApiParameterSource Form = new ApiParameterSource(
- "Form",
- Resources.ApiParameterSource_Form);
-
- ///
- /// An for the request headers.
- ///
- public static readonly ApiParameterSource Header = new ApiParameterSource(
- "Header",
- Resources.ApiParameterSource_Header);
-
- ///
- /// An for a parameter that should be hidden. Used when
- /// a parameter cannot be set with user input.
- ///
- public static readonly ApiParameterSource Hidden = new ApiParameterSource(
- "Hidden",
- Resources.ApiParameterSource_Hidden);
-
- ///
- /// An for model binding. Includes form-data, query-string
- /// and headers from the request.
- ///
- public static readonly ApiParameterSource ModelBinding = new ApiParameterSource(
- "ModelBinding",
- Resources.ApiParameterSource_ModelBinding);
-
- ///
- /// An for the request url path.
- ///
- public static readonly ApiParameterSource Path = new ApiParameterSource(
- "Path",
- Resources.ApiParameterSource_Path);
-
- ///
- /// An for the request query-string.
- ///
- public static readonly ApiParameterSource Query = new ApiParameterSource(
- "Query",
- Resources.ApiParameterSource_Query);
-
- ///
- /// Creates a new .
- ///
- /// The id. Used for comparison.
- /// The display name.
- public ApiParameterSource([NotNull] string id, string displayName)
- {
- Id = id;
- DisplayName = displayName;
- }
-
- ///
- /// Gets the display name.
- ///
- public string DisplayName { get; }
-
- ///
- /// Gets the id.
- ///
- public string Id { get; }
-
- ///
- public bool Equals(ApiParameterSource other)
- {
- return other == null ? false : string.Equals(other.Id, Id, StringComparison.Ordinal);
- }
-
- ///
- public override bool Equals(object obj)
- {
- return Equals(obj as ApiParameterSource);
- }
-
- ///
- public override int GetHashCode()
- {
- return Id.GetHashCode();
- }
-
- ///
- public static bool operator ==(ApiParameterSource s1, ApiParameterSource s2)
- {
- if (object.ReferenceEquals(s1, null))
- {
- return object.ReferenceEquals(s2, null); ;
- }
-
- return s1.Equals(s2);
- }
-
- ///
- public static bool operator !=(ApiParameterSource s1, ApiParameterSource s2)
- {
- return !(s1 == s2);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs
index 3f7e297942..6f60cfb80f 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs
@@ -140,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.Description
{
// Remove any 'hidden' parameters. These are things that can't come from user input,
// so they aren't worth showing.
- if (context.Results[i].Source == ApiParameterSource.Hidden)
+ if (!context.Results[i].Source.IsFromRequest)
{
context.Results.RemoveAt(i);
}
@@ -155,9 +155,9 @@ namespace Microsoft.AspNet.Mvc.Description
foreach (var parameter in context.Results)
{
- if (parameter.Source == ApiParameterSource.Path ||
- parameter.Source == ApiParameterSource.ModelBinding ||
- parameter.Source == ApiParameterSource.Custom)
+ if (parameter.Source == BindingSource.Path ||
+ parameter.Source == BindingSource.ModelBinding ||
+ parameter.Source == BindingSource.Custom)
{
ApiParameterRouteInfo routeInfo;
if (routeParameters.TryGetValue(parameter.Name, out routeInfo))
@@ -165,12 +165,12 @@ namespace Microsoft.AspNet.Mvc.Description
parameter.RouteInfo = routeInfo;
routeParameters.Remove(parameter.Name);
- if (parameter.Source == ApiParameterSource.ModelBinding &&
+ if (parameter.Source == BindingSource.ModelBinding &&
!parameter.RouteInfo.IsOptional)
{
// If we didn't see any information about the parameter, but we have
// a route parameter that matches, let's switch it to path.
- parameter.Source = ApiParameterSource.Path;
+ parameter.Source = BindingSource.Path;
}
}
}
@@ -184,7 +184,7 @@ namespace Microsoft.AspNet.Mvc.Description
{
Name = routeParameter.Key,
RouteInfo = routeParameter.Value,
- Source = ApiParameterSource.Path,
+ Source = BindingSource.Path,
});
}
@@ -449,7 +449,7 @@ namespace Microsoft.AspNet.Mvc.Description
// Attempt to find a binding source for the parameter
//
// The default is ModelBinding (aka all default value providers)
- var source = ApiParameterSource.ModelBinding;
+ var source = BindingSource.ModelBinding;
if (!Visit(modelMetadata, 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
@@ -466,7 +466,7 @@ namespace Microsoft.AspNet.Mvc.Description
/// model properties where we can definitely compute an answer.
///
/// The metadata for the model.
- /// The from the ambient context.
+ /// The from the ambient context.
/// The current name prefix (to prepend to property names).
///
/// true if the set of objects were created for the model.
@@ -477,10 +477,10 @@ 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, ApiParameterSource ambientSource, string containerName)
+ private bool Visit(ModelMetadata modelMetadata, BindingSource ambientSource, string containerName)
{
- ApiParameterSource source;
- if (GetSource(modelMetadata, out source))
+ var source = BindingSource.GetBindingSource(modelMetadata.BinderMetadata);
+ 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.
@@ -597,7 +597,7 @@ namespace Microsoft.AspNet.Mvc.Description
private ApiParameterDescription CreateResult(
ModelMetadata metadata,
- ApiParameterSource source,
+ BindingSource source,
string containerName)
{
return new ApiParameterDescription()
@@ -622,73 +622,15 @@ namespace Microsoft.AspNet.Mvc.Description
}
}
- // This isn't extensible right now.
- //
- // Returns true if the source is greedy (means to stop exploring the model)
- // Returns false if the source in unknown or known but not greedy (like [FromQuery])
- private static bool GetSource(ModelMetadata metadata, out ApiParameterSource source)
- {
- if (metadata.BinderMetadata == null)
- {
- // There's nothing we can figure out.
- source = null;
- return false;
- }
-
- if (metadata.BinderMetadata is IFormatterBinderMetadata)
- {
- source = ApiParameterSource.Body;
- return true;
- }
- else if (metadata.BinderMetadata is IHeaderBinderMetadata)
- {
- source = ApiParameterSource.Header;
- return true;
- }
- else if (metadata.BinderMetadata is IServiceActivatorBinderMetadata)
- {
- source = ApiParameterSource.Hidden;
- return true;
- }
- else if (metadata.BinderMetadata is IRouteDataValueProviderMetadata)
- {
- source = ApiParameterSource.Path;
- return false;
- }
- else if (metadata.BinderMetadata is IQueryValueProviderMetadata)
- {
- source = ApiParameterSource.Query;
- return false;
- }
- else if (metadata.BinderMetadata is IFormDataValueProviderMetadata)
- {
- source = ApiParameterSource.Form;
- return false;
- }
-
- var binderTypeMetadata = metadata.BinderMetadata as IBinderTypeProviderMetadata;
- if (binderTypeMetadata != null && binderTypeMetadata.BinderType != null)
- {
- // This provides it's own model binder, so we can't really make a good
- // estimate of where it comes from.
- source = ApiParameterSource.Custom;
- return true;
- }
-
- // We're out of cases we know how to handle.
- source = null;
- return false;
- }
-
private struct PropertyKey
{
public readonly Type ContainerType;
public readonly string PropertyName;
- public readonly ApiParameterSource Source;
+ public readonly BindingSource Source;
- public PropertyKey(ModelMetadata metadata, ApiParameterSource source)
+ public PropertyKey(ModelMetadata metadata, BindingSource source)
{
ContainerType = metadata.ContainerType;
PropertyName = metadata.PropertyName;
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
index aff83a06c5..e6f23cf57b 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs
@@ -1,19 +1,20 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
-namespace Microsoft.AspNet.Mvc
+namespace Microsoft.AspNet.Mvc.ModelBinding
{
///
- /// Represents a model binder which understands and uses
- /// InputFomatters to bind the model to request's body.
+ /// An which binds models from the request body using an
+ /// when a model has the binding source /
///
- public class BodyModelBinder : MetadataAwareBinder
+ public class BodyModelBinder : BindingSourceModelBinder
{
private readonly ActionContext _actionContext;
private readonly IScopedInstance _bindingContext;
@@ -21,11 +22,22 @@ namespace Microsoft.AspNet.Mvc
private readonly IBodyModelValidator _bodyModelValidator;
private readonly IValidationExcludeFiltersProvider _bodyValidationExcludeFiltersProvider;
+ ///
+ /// Creates a new .
+ ///
+ /// An accessor to the .
+ /// An accessor to the .
+ /// The .
+ /// The .
+ ///
+ /// The .
+ ///
public BodyModelBinder([NotNull] IScopedInstance context,
[NotNull] IScopedInstance bindingContext,
[NotNull] IInputFormatterSelector selector,
[NotNull] IBodyModelValidator bodyModelValidator,
[NotNull] IValidationExcludeFiltersProvider bodyValidationExcludeFiltersProvider)
+ : base(BindingSource.Body)
{
_actionContext = context.Value;
_bindingContext = bindingContext;
@@ -34,9 +46,8 @@ namespace Microsoft.AspNet.Mvc
_bodyValidationExcludeFiltersProvider = bodyValidationExcludeFiltersProvider;
}
- protected override async Task BindAsync(
- ModelBindingContext bindingContext,
- IFormatterBinderMetadata metadata)
+ ///
+ protected async override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
{
var formatters = _bindingContext.Value.InputFormatters;
@@ -48,9 +59,7 @@ namespace Microsoft.AspNet.Mvc
var unsupportedContentType = Resources.FormatUnsupportedContentType(
bindingContext.OperationBindingContext.HttpContext.Request.ContentType);
bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType);
-
- // Should always return true so that the model binding process ends here.
- return true;
+ return;
}
bindingContext.Model = await formatter.ReadAsync(formatterContext);
@@ -64,7 +73,6 @@ namespace Microsoft.AspNet.Mvc
containerMetadata: null,
excludeFromValidationFilters: _bodyValidationExcludeFiltersProvider.ExcludeFilters);
_bodyModelValidator.Validate(validationContext, bindingContext.ModelName);
- return true;
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
index d12a81c8ef..07f4f809b4 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
@@ -1,23 +1,27 @@
// 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 Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
-namespace Microsoft.AspNet.Mvc
+namespace Microsoft.AspNet.Mvc.ModelBinding
{
///
- /// An which understands
- /// and activates a given model using .
+ /// An which binds models from the request services when a model
+ /// has the binding source /
///
- public class ServicesModelBinder : MetadataAwareBinder
+ public class ServicesModelBinder : BindingSourceModelBinder
{
+ ///
+ /// Creates a new .
+ ///
+ public ServicesModelBinder()
+ : base(BindingSource.Services)
+ {
+ }
+
///
- protected override Task BindAsync(
- [NotNull] ModelBindingContext bindingContext,
- [NotNull] IServiceActivatorBinderMetadata metadata)
+ protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
{
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
bindingContext.Model = requestServices.GetRequiredService(bindingContext.ModelType);
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index ba86b70e93..2199f4decc 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -1594,134 +1594,6 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("ResponseCache_SpecifyDuration"), p0, p1);
}
- ///
- /// Body
- ///
- internal static string ApiParameterSource_Body
- {
- get { return GetString("ApiParameterSource_Body"); }
- }
-
- ///
- /// Body
- ///
- internal static string FormatApiParameterSource_Body()
- {
- return GetString("ApiParameterSource_Body");
- }
-
- ///
- /// Custom
- ///
- internal static string ApiParameterSource_Custom
- {
- get { return GetString("ApiParameterSource_Custom"); }
- }
-
- ///
- /// Custom
- ///
- internal static string FormatApiParameterSource_Custom()
- {
- return GetString("ApiParameterSource_Custom");
- }
-
- ///
- /// Header
- ///
- internal static string ApiParameterSource_Header
- {
- get { return GetString("ApiParameterSource_Header"); }
- }
-
- ///
- /// Header
- ///
- internal static string FormatApiParameterSource_Header()
- {
- return GetString("ApiParameterSource_Header");
- }
-
- ///
- /// Hidden
- ///
- internal static string ApiParameterSource_Hidden
- {
- get { return GetString("ApiParameterSource_Hidden"); }
- }
-
- ///
- /// Hidden
- ///
- internal static string FormatApiParameterSource_Hidden()
- {
- return GetString("ApiParameterSource_Hidden");
- }
-
- ///
- /// ModelBinding
- ///
- internal static string ApiParameterSource_ModelBinding
- {
- get { return GetString("ApiParameterSource_ModelBinding"); }
- }
-
- ///
- /// ModelBinding
- ///
- internal static string FormatApiParameterSource_ModelBinding()
- {
- return GetString("ApiParameterSource_ModelBinding");
- }
-
- ///
- /// Path
- ///
- internal static string ApiParameterSource_Path
- {
- get { return GetString("ApiParameterSource_Path"); }
- }
-
- ///
- /// Path
- ///
- internal static string FormatApiParameterSource_Path()
- {
- return GetString("ApiParameterSource_Path");
- }
-
- ///
- /// Query
- ///
- internal static string ApiParameterSource_Query
- {
- get { return GetString("ApiParameterSource_Query"); }
- }
-
- ///
- /// Query
- ///
- internal static string FormatApiParameterSource_Query()
- {
- return GetString("ApiParameterSource_Query");
- }
-
- ///
- /// Form
- ///
- internal static string ApiParameterSource_Form
- {
- get { return GetString("ApiParameterSource_Form"); }
- }
-
- ///
- /// Form
- ///
- internal static string FormatApiParameterSource_Form()
- {
- return GetString("ApiParameterSource_Form");
- }
-
///
/// The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.
///
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index 2c556ba58d..78a8b4faf5 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -424,30 +424,6 @@
If the '{0}' property is not set to true, '{1}' property must be specified.
-
- Body
-
-
- Custom
-
-
- Header
-
-
- Hidden
-
-
- ModelBinding
-
-
- Path
-
-
- Query
-
-
- Form
-
The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer.
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/BindingSource.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/BindingSource.cs
new file mode 100644
index 0000000000..caa895d429
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/BindingSource.cs
@@ -0,0 +1,213 @@
+// 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.Diagnostics;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ ///
+ /// A metadata object representing a source of data for model binding.
+ ///
+ [DebuggerDisplay("Source: {DisplayName}")]
+ public class BindingSource : IEquatable
+ {
+ ///
+ /// A for the request body.
+ ///
+ public static readonly BindingSource Body = new BindingSource(
+ "Body",
+ Resources.BindingSource_Body,
+ isGreedy: true,
+ isFromRequest: true);
+
+ ///
+ /// A for a custom model binder (unknown data source).
+ ///
+ public static readonly BindingSource Custom = new BindingSource(
+ "Custom",
+ Resources.BindingSource_Custom,
+ isGreedy: true,
+ isFromRequest: true);
+
+ ///
+ /// A for the request form-data.
+ ///
+ public static readonly BindingSource Form = new BindingSource(
+ "Form",
+ Resources.BindingSource_Form,
+ isGreedy: false,
+ isFromRequest: true);
+
+ ///
+ /// A for the request headers.
+ ///
+ public static readonly BindingSource Header = new BindingSource(
+ "Header",
+ Resources.BindingSource_Header,
+ isGreedy: true,
+ isFromRequest: true);
+
+ ///
+ /// A for model binding. Includes form-data, query-string
+ /// and route data from the request.
+ ///
+ public static readonly BindingSource ModelBinding = new BindingSource(
+ "ModelBinding",
+ Resources.BindingSource_ModelBinding,
+ isGreedy: false,
+ isFromRequest: true);
+
+ ///
+ /// A for the request url path.
+ ///
+ public static readonly BindingSource Path = new BindingSource(
+ "Path",
+ Resources.BindingSource_Path,
+ isGreedy: false,
+ isFromRequest: true);
+
+ ///
+ /// A for the request query-string.
+ ///
+ public static readonly BindingSource Query = new BindingSource(
+ "Query",
+ Resources.BindingSource_Query,
+ isGreedy: false,
+ isFromRequest: true);
+
+ ///
+ /// A for request services.
+ ///
+ public static readonly BindingSource Services = new BindingSource(
+ "Services",
+ Resources.BindingSource_Services,
+ isGreedy: true,
+ isFromRequest: false);
+
+ ///
+ /// Creates a new .
+ ///
+ /// The id, a unique identifier.
+ /// The display name.
+ /// A value indicating whether or not the source is greedy.
+ ///
+ /// A value indicating whether or not the data comes from the HTTP request.
+ ///
+ public BindingSource([NotNull] string id, string displayName, bool isGreedy, bool isFromRequest)
+ {
+ Id = id;
+ DisplayName = displayName;
+ IsGreedy = isGreedy;
+ IsFromRequest = isFromRequest;
+ }
+
+ ///
+ /// Gets the display name for the source.
+ ///
+ public string DisplayName { get; }
+
+ ///
+ /// Gets the unique identifier for the source. Sources are compared based on their Id.
+ ///
+ public string Id { get; }
+
+ ///
+ /// Gets a value indicating whether or not a source is greedy. A greedy source will bind a model in
+ /// a single operation, and will not decompose the model into sub-properties.
+ ///
+ ///
+ ///
+ /// For sources based on a , setting to false
+ /// will most closely describe the behavior. This value is used inside the default model binders to
+ /// determine whether or not to attempt to bind properties of a model.
+ ///
+ ///
+ /// Set to true for most custom implementations.
+ ///
+ ///
+ /// If a source represents an which will recursively traverse a model's properties
+ /// and bind them individually using , then set to
+ /// true.
+ ///
+ ///
+ public bool IsGreedy { get; }
+
+ ///
+ /// Gets a value indicating whether or not the binding source uses input from the current HTTP request.
+ ///
+ ///
+ /// Some sources (like ) are based on application state and not user
+ /// input. These are excluded by default from ApiExplorer diagnostics.
+ ///
+ public bool IsFromRequest { get; }
+
+ ///
+ /// Gets a value indicating whether or not the can accept
+ /// data from .
+ ///
+ /// The to consider as input.
+ /// True if the source is compatible, otherwise false.
+ ///
+ /// When using this method, it is expected that the left-hand-side is metadata specified
+ /// on a property or parameter for model binding, and the right hand side is a source of
+ /// data used by a model binder or value provider.
+ ///
+ /// This distinction is important as the left-hand-side may be a composite, but the right
+ /// may not.
+ ///
+ public virtual bool CanAcceptDataFrom([NotNull] BindingSource bindingSource)
+ {
+ if (bindingSource is CompositeBindingSource)
+ {
+ var message = Resources.FormatBindingSource_CannotBeComposite(
+ bindingSource.DisplayName,
+ nameof(CanAcceptDataFrom));
+ throw new ArgumentException(message, nameof(bindingSource));
+ }
+
+ return this == bindingSource;
+ }
+
+ ///
+ public bool Equals(BindingSource other)
+ {
+ return other == null ? false : string.Equals(other.Id, Id, StringComparison.Ordinal);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as BindingSource);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return Id.GetHashCode();
+ }
+
+ ///
+ public static bool operator ==(BindingSource s1, BindingSource s2)
+ {
+ if (object.ReferenceEquals(s1, null))
+ {
+ return object.ReferenceEquals(s2, null); ;
+ }
+
+ return s1.Equals(s2);
+ }
+
+ ///
+ public static bool operator !=(BindingSource s1, BindingSource s2)
+ {
+ return !(s1 == s2);
+ }
+
+ // THIS IS TEMP CODE, this will be moved to model metadata
+ public static BindingSource GetBindingSource(IBinderMetadata metadata)
+ {
+ return (metadata as IBindingSourceMetadata)?.BindingSource;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/CompositeBindingSource.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/CompositeBindingSource.cs
new file mode 100644
index 0000000000..4ef74c0702
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/CompositeBindingSource.cs
@@ -0,0 +1,95 @@
+// 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
+{
+ ///
+ /// A which can repesent multiple value-provider data sources.
+ ///
+ public class CompositeBindingSource : BindingSource
+ {
+ ///
+ /// Creates a new .
+ ///
+ ///
+ /// The set of entries.
+ /// Must be value-provider sources and user input.
+ ///
+ /// The display name for the composite source.
+ /// A .
+ public static CompositeBindingSource Create(
+ [NotNull] IEnumerable bindingSources,
+ string displayName)
+ {
+ foreach (var bindingSource in bindingSources)
+ {
+ if (bindingSource.IsGreedy)
+ {
+ var message = Resources.FormatBindingSource_CannotBeGreedy(
+ bindingSource.DisplayName,
+ nameof(CompositeBindingSource));
+ throw new ArgumentException(message, "bindingSources");
+ }
+
+ if (!bindingSource.IsFromRequest)
+ {
+ var message = Resources.FormatBindingSource_MustBeFromRequest(
+ bindingSource.DisplayName,
+ nameof(CompositeBindingSource));
+ throw new ArgumentException(message, "bindingSources");
+ }
+
+ if (bindingSource is CompositeBindingSource)
+ {
+ var message = Resources.FormatBindingSource_CannotBeComposite(
+ bindingSource.DisplayName,
+ nameof(CompositeBindingSource));
+ throw new ArgumentException(message, "bindingSources");
+ }
+ }
+
+ var id = string.Join("&", bindingSources.Select(s => s.Id).OrderBy(s => s, StringComparer.Ordinal));
+ return new CompositeBindingSource(id, displayName, bindingSources);
+ }
+
+ private CompositeBindingSource(
+ [NotNull] string id,
+ string displayName,
+ [NotNull] IEnumerable bindingSources)
+ : base(id, displayName, isGreedy: false, isFromRequest: true)
+ {
+ BindingSources = bindingSources;
+ }
+
+ ///
+ /// Gets the set of entries.
+ ///
+ public IEnumerable BindingSources { get; }
+
+ ///
+ public override bool CanAcceptDataFrom([NotNull] BindingSource bindingSource)
+ {
+ if (bindingSource is CompositeBindingSource)
+ {
+ var message = Resources.FormatBindingSource_CannotBeComposite(
+ bindingSource.DisplayName,
+ nameof(CanAcceptDataFrom));
+ throw new ArgumentException(message, nameof(bindingSource));
+ }
+
+ foreach (var source in BindingSources)
+ {
+ if (source.CanAcceptDataFrom(bindingSource))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromBodyAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromBodyAttribute.cs
index bd7c644df3..24b90cc6c1 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromBodyAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromBodyAttribute.cs
@@ -7,11 +7,12 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
///
- /// This attribute is used on action parameters to indicate
- /// they are bound from the body of the incoming request.
+ /// Specifies that a parameter or property should be bound using the request body.
///
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class FromBodyAttribute : Attribute, IFormatterBinderMetadata
+ public class FromBodyAttribute : Attribute, IBindingSourceMetadata
{
+ ///
+ public BindingSource BindingSource { get { return BindingSource.Body; } }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromFormAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromFormAttribute.cs
index 79191054fb..891a1af3de 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromFormAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromFormAttribute.cs
@@ -7,11 +7,12 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
///
- /// This attribute is used on action parameters to indicate that
- /// they will be bound using form data of the incoming request.
+ /// Specifies that a parameter or property should be bound using form-data in the request body.
///
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class FromFormAttribute : Attribute, IFormDataValueProviderMetadata
+ public class FromFormAttribute : Attribute, IBindingSourceMetadata
{
+ ///
+ public BindingSource BindingSource { get { return BindingSource.Form; } }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromHeaderAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromHeaderAttribute.cs
index b77d402fad..de30b842c7 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromHeaderAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromHeaderAttribute.cs
@@ -7,12 +7,14 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
///
- /// can be placed on an action parameter or model property to indicate
- /// that model binding should use a header value as the data source.
+ /// Specifies that a parameter or property should be bound using the request headers.
///
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class FromHeaderAttribute : Attribute, IHeaderBinderMetadata, IModelNameProvider
+ public class FromHeaderAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
+ ///
+ public BindingSource BindingSource { get { return BindingSource.Header; } }
+
///
public string Name { get; set; }
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromQueryAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromQueryAttribute.cs
index 8268a74ec2..3ae9be21ed 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromQueryAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromQueryAttribute.cs
@@ -7,11 +7,12 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
///
- /// This attribute is used on action parameters to indicate that
- /// they will be bound using query data of the incoming request.
+ /// Specifies that a parameter or property should be bound using the request query string.
///
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class FromQueryAttribute : Attribute, IQueryValueProviderMetadata
+ public class FromQueryAttribute : Attribute, IBindingSourceMetadata
{
+ ///
+ public BindingSource BindingSource { get { return BindingSource.Query; } }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromRouteAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromRouteAttribute.cs
index 613667dc1e..ecbcf923dd 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromRouteAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromRouteAttribute.cs
@@ -7,11 +7,12 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
///
- /// This attribute is used on action parameters to indicate that
- /// they will be bound using route data of the incoming request.
+ /// Specifies that a parameter or property should be bound using route-data from the current request.
///
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class FromRouteAttribute : Attribute, IRouteDataValueProviderMetadata
+ public class FromRouteAttribute : Attribute, IBindingSourceMetadata
{
+ ///
+ public BindingSource BindingSource { get { return BindingSource.Path; } }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromServicesAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromServicesAttribute.cs
index ba84b2fe81..b10f85771d 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromServicesAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromServicesAttribute.cs
@@ -7,11 +7,12 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
///
- /// This attribute is used on action parameters or model properties to indicate that
- /// they will be bound using service provider.
+ /// Specifies that a parameter or property should be bound using the request services.
///
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
- public class FromServicesAttribute : Attribute, IServiceActivatorBinderMetadata
+ public class FromServicesAttribute : Attribute, IBindingSourceMetadata
{
+ ///
+ public BindingSource BindingSource { get { return BindingSource.Services; } }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBinderTypeProviderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBinderTypeProviderMetadata.cs
index 4b1b680afa..43fad56e6e 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBinderTypeProviderMetadata.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBinderTypeProviderMetadata.cs
@@ -9,12 +9,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// Provides a which implements or
/// .
///
- public interface IBinderTypeProviderMetadata : IBinderMetadata
+ public interface IBinderTypeProviderMetadata : IBindingSourceMetadata
{
///
/// A which implements either or
/// .
///
- Type BinderType { get; set; }
+ Type BinderType { get; }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBindingSourceMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBindingSourceMetadata.cs
new file mode 100644
index 0000000000..271ae88f69
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IBindingSourceMetadata.cs
@@ -0,0 +1,22 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ ///
+ /// Metadata which specificies the data source for model binding.
+ ///
+ public interface IBindingSourceMetadata : IBinderMetadata
+ {
+ ///
+ /// Gets the .
+ ///
+ ///
+ /// The is metadata which can be used to determine which data
+ /// sources are valid for model binding of a property or parameter.
+ ///
+ BindingSource BindingSource { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IFormDataValueProviderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IFormDataValueProviderMetadata.cs
deleted file mode 100644
index f4ee5cbde7..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IFormDataValueProviderMetadata.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
-{
- ///
- /// Metadata interface that indicates model binding should use only form data value providers.
- ///
- public interface IFormDataValueProviderMetadata : IValueProviderMetadata
- {
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IFormatterBinderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IFormatterBinderMetadata.cs
deleted file mode 100644
index 3a5c8399ca..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IFormatterBinderMetadata.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
-{
- ///
- /// Metadata interface that indicates model binding should be performed by an input formatter.
- ///
- public interface IFormatterBinderMetadata : IBinderMetadata
- {
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IHeaderBinderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IHeaderBinderMetadata.cs
deleted file mode 100644
index 6954375006..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IHeaderBinderMetadata.cs
+++ /dev/null
@@ -1,13 +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
-{
- ///
- /// Metadata interface that indicates model binding should use a header value for
- /// the data source of a property or parameter.
- ///
- public interface IHeaderBinderMetadata : IBinderMetadata
- {
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IQueryValueProviderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IQueryValueProviderMetadata.cs
deleted file mode 100644
index 7e81a950f6..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IQueryValueProviderMetadata.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
-{
- ///
- /// Metadata interface that indicates model binding should use only query string value providers.
- ///
- public interface IQueryValueProviderMetadata : IValueProviderMetadata
- {
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IRouteDataValueProviderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IRouteDataValueProviderMetadata.cs
deleted file mode 100644
index 2ee89fcb17..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IRouteDataValueProviderMetadata.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
-{
- ///
- /// Metadata interface that indicates model binding should use only route data value providers.
- ///
- public interface IRouteDataValueProviderMetadata : IValueProviderMetadata
- {
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IServiceActivatorBinderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IServiceActivatorBinderMetadata.cs
deleted file mode 100644
index 5b18b12fc2..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IServiceActivatorBinderMetadata.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
-{
- ///
- /// Metadata interface that indicates model binding should use the service container to get the value for a model.
- ///
- public interface IServiceActivatorBinderMetadata : IBinderMetadata
- {
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IValueProviderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IValueProviderMetadata.cs
deleted file mode 100644
index 825387df99..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IValueProviderMetadata.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 value providers.
- ///
- public interface IValueProviderMetadata : IBinderMetadata
- {
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/ModelBinderAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/ModelBinderAttribute.cs
index f8a3f848e6..bd94966bdb 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/ModelBinderAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/ModelBinderAttribute.cs
@@ -28,6 +28,7 @@ namespace Microsoft.AspNet.Mvc
public class ModelBinderAttribute : Attribute, IModelNameProvider, IBinderTypeProviderMetadata
{
private Type _binderType;
+ private BindingSource _bindingSource;
///
public Type BinderType
@@ -55,6 +56,24 @@ namespace Microsoft.AspNet.Mvc
}
}
+ ///
+ public BindingSource BindingSource
+ {
+ get
+ {
+ if (_bindingSource == null && _binderType != null)
+ {
+ return BindingSource.Custom;
+ }
+
+ return _bindingSource;
+ }
+ set
+ {
+ _bindingSource = value;
+ }
+ }
+
///
public string Name { get; set; }
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs
new file mode 100644
index 0000000000..b89d479c98
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/BindingSourceModelBinder.cs
@@ -0,0 +1,83 @@
+// 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;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ ///
+ /// An which provides data from a specific .
+ ///
+ ///
+ ///
+ /// A is an base-implementation which
+ /// can provide data for all parameters and model properties which specify the corresponding
+ /// .
+ ///
+ ///
+ /// is greedy, meaning that a given instance expects to handle all
+ /// parameters and properties annotated with the corresponding and
+ /// will short-circuit the model binding process to prevent other binders from running.
+ /// of must be set to true.
+ ///
+ ///
+ public abstract class BindingSourceModelBinder : IModelBinder
+ {
+ ///
+ /// Creates a new .
+ ///
+ ///
+ /// The . Must be a single-source (non-composite) with
+ /// equal to true.
+ ///
+ protected BindingSourceModelBinder([NotNull] BindingSource bindingSource)
+ {
+ // This class implements a pattern that's only useful for greedy model binders. If you need
+ // to implement something non-greedy then don't use the base class.
+ if (!bindingSource.IsGreedy)
+ {
+ var message = Resources.FormatBindingSource_MustBeGreedy(
+ bindingSource.DisplayName,
+ nameof(BindingSourceModelBinder));
+ throw new ArgumentException(message, nameof(bindingSource));
+ }
+
+ BindingSource = bindingSource;
+ }
+
+ ///
+ /// Gets the corresponding .
+ ///
+ protected BindingSource BindingSource { get; }
+
+ ///
+ /// Binds the model. Called when the model's supported binding-source matches .
+ ///
+ /// The .
+ ///
+ /// A which will complete when model binding has completed.
+ ///
+ protected abstract Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext);
+
+ ///
+ public async Task BindModelAsync(ModelBindingContext context)
+ {
+ var bindingSourceMetadata = context.ModelMetadata.BinderMetadata as IBindingSourceMetadata;
+ var allowedBindingSource = bindingSourceMetadata?.BindingSource;
+
+ if (allowedBindingSource == null || !allowedBindingSource.CanAcceptDataFrom(BindingSource))
+ {
+ // Binding Sources are opt-in. This model either didn't specify one or specified something
+ // incompatible so let other binders run.
+ return false;
+ }
+
+ await BindModelCoreAsync(context);
+
+ // Prevent other model binders from running because this model binder is the only handler for
+ // its binding source.
+ return true;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
index 58607e18ae..a39638b1cc 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs
@@ -137,21 +137,32 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
newBindingContext.OperationBindingContext.BodyBindingState = GetBodyBindingState(oldBindingContext);
- // look at the value providers and see if they need to be restricted.
- var metadata = oldBindingContext.ModelMetadata.BinderMetadata as IValueProviderMetadata;
- if (metadata != null)
+ // If the property has a specified data binding sources, we need to filter the set of value providers
+ // to just those that match. We can skip filtering when IsGreedy == true, because that can't use
+ // value providers.
+ //
+ // We also want to base this filtering on the - top-level value profider in case the data source
+ // on this property doesn't intersect with the ambient data source.
+ //
+ // Ex:
+ //
+ // public class Person
+ // {
+ // [FromQuery]
+ // public int Id { get; set; }
+ // }
+ //
+ // public IActionResult UpdatePerson([FromForm] Person person) { }
+ //
+ // In this example, [FromQuery] overrides the ambient data source (form).
+ var bindingSource = BindingSource.GetBindingSource(oldBindingContext.ModelMetadata.BinderMetadata);
+ if (bindingSource != null && !bindingSource.IsGreedy)
{
- // ValueProvider property might contain a filtered list of value providers.
- // While deciding to bind a particular property which is annotated with a IValueProviderMetadata,
- // instead of refiltering an already filtered list, we need to filter value providers from a global
- // list of all value providers. This is so that every artifact that is explicitly marked using an
- // IValueProviderMetadata can restrict model binding to only use value providers which support this
- // IValueProviderMetadata.
var valueProvider =
- oldBindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
+ oldBindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
if (valueProvider != null)
{
- newBindingContext.ValueProvider = valueProvider.Filter(metadata);
+ newBindingContext.ValueProvider = valueProvider.Filter(bindingSource);
}
}
@@ -160,27 +171,29 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private static BodyBindingState GetBodyBindingState(ModelBindingContext oldBindingContext)
{
- var binderMetadata = oldBindingContext.ModelMetadata.BinderMetadata;
- var newIsFormatterBasedMetadataFound = binderMetadata is IFormatterBinderMetadata;
- var newIsFormBasedMetadataFound = binderMetadata is IFormDataValueProviderMetadata;
- var currentModelNeedsToReadBody = newIsFormatterBasedMetadataFound || newIsFormBasedMetadataFound;
+ var bindingSource = BindingSource.GetBindingSource(oldBindingContext.ModelMetadata.BinderMetadata);
+
+ var willReadBodyWithFormatter = bindingSource == BindingSource.Body;
+ var willReadBodyAsFormData = bindingSource == BindingSource.Form;
+
+ var currentModelNeedsToReadBody = willReadBodyWithFormatter || willReadBodyAsFormData;
var oldState = oldBindingContext.OperationBindingContext.BodyBindingState;
// We need to throw if there are multiple models which can cause body to be read multiple times.
// Reading form data multiple times is ok since we cache form data. For the models marked to read using
// formatters, multiple reads are not allowed.
if (oldState == BodyBindingState.FormatterBased && currentModelNeedsToReadBody ||
- oldState == BodyBindingState.FormBased && newIsFormatterBasedMetadataFound)
+ oldState == BodyBindingState.FormBased && willReadBodyWithFormatter)
{
throw new InvalidOperationException(Resources.MultipleBodyParametersOrPropertiesAreNotAllowed);
}
var state = oldBindingContext.OperationBindingContext.BodyBindingState;
- if (newIsFormatterBasedMetadataFound)
+ if (willReadBodyWithFormatter)
{
state = BodyBindingState.FormatterBased;
}
- else if (newIsFormBasedMetadataFound && oldState != BodyBindingState.FormatterBased)
+ else if (willReadBodyAsFormData && oldState != BodyBindingState.FormatterBased)
{
// Only update the model binding state if we have not discovered formatter based state already.
state = BodyBindingState.FormBased;
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
index 24c8a6dff5..89c0192458 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/HeaderModelBinder.cs
@@ -9,15 +9,21 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
///
- /// A which uses
- /// to bind the model.
+ /// An which binds models from the request headers when a model
+ /// has the binding source /
///
- public class HeaderModelBinder : MetadataAwareBinder
+ public class HeaderModelBinder : BindingSourceModelBinder
{
+ ///
+ /// Creates a new .
+ ///
+ public HeaderModelBinder()
+ : base(BindingSource.Header)
+ {
+ }
+
///
- protected override Task BindAsync(
- [NotNull] ModelBindingContext bindingContext,
- [NotNull] IHeaderBinderMetadata metadata)
+ protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
{
var request = bindingContext.OperationBindingContext.HttpContext.Request;
var modelMetadata = bindingContext.ModelMetadata;
@@ -38,8 +44,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var values = request.Headers.GetCommaSeparatedValues(headerName);
if (values != null)
{
- bindingContext.Model =
- ModelBindingHelper.ConvertValuesToCollectionType(bindingContext.ModelType, values);
+ bindingContext.Model = ModelBindingHelper.ConvertValuesToCollectionType(
+ bindingContext.ModelType,
+ values);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IMetadataAwareBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IMetadataAwareBinder.cs
deleted file mode 100644
index 15005ff4f8..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/IMetadataAwareBinder.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
-{
- ///
- /// An which is aware of .
- ///
- public interface IMetadataAwareBinder : IModelBinder
- {
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MetadataAwareBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MetadataAwareBinder.cs
deleted file mode 100644
index 0bf3c4cfbe..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MetadataAwareBinder.cs
+++ /dev/null
@@ -1,36 +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.
-
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNet.Mvc.ModelBinding
-{
- ///
- /// Represents an which can select itself based on the
- /// .
- ///
- /// Represents a type implementing
- public abstract class MetadataAwareBinder : IMetadataAwareBinder
- where TBinderMetadata : IBinderMetadata
- {
- ///
- /// Async function which does the actual binding to bind to a particular model.
- ///
- /// The binding context which has the object to be bound.
- /// The associated with the current binder.
- /// A Task with a bool implying the success or failure of the operation.
- protected abstract Task BindAsync([NotNull] ModelBindingContext bindingContext,
- [NotNull] TBinderMetadata metadata);
-
- public Task BindModelAsync(ModelBindingContext context)
- {
- if (context.ModelMetadata.BinderMetadata is TBinderMetadata)
- {
- var metadata = (TBinderMetadata)context.ModelMetadata.BinderMetadata;
- return BindAsync(context, metadata);
- }
-
- return Task.FromResult(false);
- }
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
index 9101233e1a..7c6a4458d8 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs
@@ -54,16 +54,22 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
var hasExplicitAlias = bindingContext.ModelMetadata.BinderModelName != null;
- // The fact that this has reached here,
- // it is a complex object which was not directly bound by any previous model binders.
- // Check if this was supposed to be handled by a non value provider based binder.
- // if it was then it should be not be bound using mutable object binder.
- // This check would prevent it from recursing in if a model contains a property of its own type.
+ // 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
+ // recursion.
+ //
+ // First, we want to make sure this object is allowed to come from a value provider source as this binder
+ // will always include value provider data. For instance if the model is marked with [FromBody], then we
+ // can just skip it. A greedy source cannot be a value provider.
+ //
+ // If the model isn't marked with ANY binding source, then we assume it's ok also.
+ //
// 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 = BindingSource.GetBindingSource(bindingContext.ModelMetadata.BinderMetadata);
if (!isTopLevelObject &&
- bindingContext.ModelMetadata.BinderMetadata != null &&
- !(bindingContext.ModelMetadata.BinderMetadata is IValueProviderMetadata))
+ bindingSource != null &&
+ bindingSource.IsGreedy)
{
return false;
}
@@ -103,18 +109,37 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private async Task CanValueBindAnyModelProperties(MutableObjectBinderContext context)
{
- // We need to enumerate the non marked properties and properties marked with IValueProviderMetadata
- // instead of checking bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName)
- // because there can be a case where a value provider might be willing to provide a marked property,
- // which might never be bound.
- // For example if person.Name is marked with FromQuery, and FormValueProvider has a key person.Name,
- // and the QueryValueProvider does not, we do not want to create Person.
+ // We want to check to see if any of the properties of the model can be bound using the value providers,
+ // because that's all that MutableObjectModelBinder can handle.
+ //
+ // However, because a property might specify a custom binding source ([FromForm]), it's not correct
+ // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName),
+ // because that may include ALL value providers - that would lead us to mistakenly create the model
+ // when the data is coming from a source we should use (ex: value found in query string, but the
+ // model has [FromForm]).
+ //
+ // To do this we need to enumerate the properties, and see which of them provide a binding source
+ // through metadata, then we decide what to do.
+ //
+ // If a property has a binding source, and it's a greedy source, then it's not
+ // allowed to come from a value provider, so we skip it.
+ //
+ // If a property has a binding source, and it's a non-greedy source, then we'll filter the
+ // the value providers to just that source, and see if we can find a matching prefix
+ // (see CanBindValue).
+ //
+ // If a property does not have a binding source, then it's fair game for any value provider.
+ //
+ // If any property meets the above conditions and has a value from valueproviders, then we'll
+ // create the model and try to bind it. OR if ALL properties of the model have a greedy source,
+ // then we go ahead and create it.
+ //
var isAnyPropertyEnabledForValueProviderBasedBinding = false;
foreach (var propertyMetadata in context.PropertyMetadata)
{
// This check will skip properties which are marked explicitly using a non value binder.
- if (propertyMetadata.BinderMetadata == null ||
- propertyMetadata.BinderMetadata is IValueProviderMetadata)
+ var bindingSource = BindingSource.GetBindingSource(propertyMetadata.BinderMetadata);
+ if (bindingSource == null || !bindingSource.IsGreedy)
{
isAnyPropertyEnabledForValueProviderBasedBinding = true;
@@ -141,15 +166,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private async Task CanBindValue(ModelBindingContext bindingContext, ModelMetadata metadata)
{
var valueProvider = bindingContext.ValueProvider;
- var valueProviderMetadata = metadata.BinderMetadata as IValueProviderMetadata;
- if (valueProviderMetadata != null)
+
+ var bindingSource = BindingSource.GetBindingSource(metadata.BinderMetadata);
+ if (bindingSource != null && !bindingSource.IsGreedy)
{
- // if there is a binder metadata and since the property can be bound using a value provider.
- var metadataAwareValueProvider =
- bindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider;
- if (metadataAwareValueProvider != null)
+ var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
+ if (rootValueProvider != null)
{
- valueProvider = metadataAwareValueProvider.Filter(valueProviderMetadata);
+ valueProvider = rootValueProvider.Filter(bindingSource);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BodyBindingState.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BodyBindingState.cs
index f621c8ebbd..35e19993bb 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/BodyBindingState.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BodyBindingState.cs
@@ -15,13 +15,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
NotBodyBased,
///
- /// Represents if there is a that
+ /// Represents if there is a that
/// has been found during the current model binding process.
///
FormatterBased,
///
- /// Represents if there is a that
+ /// Represents if there is a that
/// has been found during the current model binding process.
///
FormBased
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs
index 7a071820bf..ccb8a02331 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Properties/Resources.Designer.cs
@@ -474,6 +474,198 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderUtil_ValueInvalidGeneric"), p0);
}
+ ///
+ /// Body
+ ///
+ internal static string BindingSource_Body
+ {
+ get { return GetString("BindingSource_Body"); }
+ }
+
+ ///
+ /// Body
+ ///
+ internal static string FormatBindingSource_Body()
+ {
+ return GetString("BindingSource_Body");
+ }
+
+ ///
+ /// Custom
+ ///
+ internal static string BindingSource_Custom
+ {
+ get { return GetString("BindingSource_Custom"); }
+ }
+
+ ///
+ /// Custom
+ ///
+ internal static string FormatBindingSource_Custom()
+ {
+ return GetString("BindingSource_Custom");
+ }
+
+ ///
+ /// Form
+ ///
+ internal static string BindingSource_Form
+ {
+ get { return GetString("BindingSource_Form"); }
+ }
+
+ ///
+ /// Form
+ ///
+ internal static string FormatBindingSource_Form()
+ {
+ return GetString("BindingSource_Form");
+ }
+
+ ///
+ /// Header
+ ///
+ internal static string BindingSource_Header
+ {
+ get { return GetString("BindingSource_Header"); }
+ }
+
+ ///
+ /// Header
+ ///
+ internal static string FormatBindingSource_Header()
+ {
+ return GetString("BindingSource_Header");
+ }
+
+ ///
+ /// Services
+ ///
+ internal static string BindingSource_Services
+ {
+ get { return GetString("BindingSource_Services"); }
+ }
+
+ ///
+ /// Services
+ ///
+ internal static string FormatBindingSource_Services()
+ {
+ return GetString("BindingSource_Services");
+ }
+
+ ///
+ /// ModelBinding
+ ///
+ internal static string BindingSource_ModelBinding
+ {
+ get { return GetString("BindingSource_ModelBinding"); }
+ }
+
+ ///
+ /// ModelBinding
+ ///
+ internal static string FormatBindingSource_ModelBinding()
+ {
+ return GetString("BindingSource_ModelBinding");
+ }
+
+ ///
+ /// Path
+ ///
+ internal static string BindingSource_Path
+ {
+ get { return GetString("BindingSource_Path"); }
+ }
+
+ ///
+ /// Path
+ ///
+ internal static string FormatBindingSource_Path()
+ {
+ return GetString("BindingSource_Path");
+ }
+
+ ///
+ /// Query
+ ///
+ internal static string BindingSource_Query
+ {
+ get { return GetString("BindingSource_Query"); }
+ }
+
+ ///
+ /// Query
+ ///
+ internal static string FormatBindingSource_Query()
+ {
+ return GetString("BindingSource_Query");
+ }
+
+ ///
+ /// The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input.
+ ///
+ internal static string BindingSource_CannotBeComposite
+ {
+ get { return GetString("BindingSource_CannotBeComposite"); }
+ }
+
+ ///
+ /// The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input.
+ ///
+ internal static string FormatBindingSource_CannotBeComposite(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_CannotBeComposite"), p0, p1);
+ }
+
+ ///
+ /// The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request.
+ ///
+ internal static string BindingSource_MustBeFromRequest
+ {
+ get { return GetString("BindingSource_MustBeFromRequest"); }
+ }
+
+ ///
+ /// The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request.
+ ///
+ internal static string FormatBindingSource_MustBeFromRequest(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeFromRequest"), p0, p1);
+ }
+
+ ///
+ /// The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources.
+ ///
+ internal static string BindingSource_CannotBeGreedy
+ {
+ get { return GetString("BindingSource_CannotBeGreedy"); }
+ }
+
+ ///
+ /// The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources.
+ ///
+ internal static string FormatBindingSource_CannotBeGreedy(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_CannotBeGreedy"), p0, p1);
+ }
+
+ ///
+ /// The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.
+ ///
+ internal static string BindingSource_MustBeGreedy
+ {
+ get { return GetString("BindingSource_MustBeGreedy"); }
+ }
+
+ ///
+ /// The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.
+ ///
+ internal static string FormatBindingSource_MustBeGreedy(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeGreedy"), p0, p1);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx
index dbb8698d8f..fa74ff2293 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Resources.resx
@@ -204,4 +204,40 @@
The supplied value is invalid for {0}.
+
+ Body
+
+
+ Custom
+
+
+ Form
+
+
+ Header
+
+
+ Services
+
+
+ ModelBinding
+
+
+ Path
+
+
+ Query
+
+
+ The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input.
+
+
+ The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request.
+
+
+ The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources.
+
+
+ The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources.
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/BindingSourceValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/BindingSourceValueProvider.cs
new file mode 100644
index 0000000000..8416654d2d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/BindingSourceValueProvider.cs
@@ -0,0 +1,79 @@
+// 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;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ ///
+ /// A value provider which provides data from a specific .
+ ///
+ ///
+ ///
+ /// A is an base-implementation which
+ /// can provide data for all parameters and model properties which specify the corresponding
+ /// .
+ ///
+ ///
+ /// implements and will
+ /// include or exclude itself from the set of value providers based on the model's associated
+ /// . Value providers are by-default included; if a model does not
+ /// specify a then all value providers are valid.
+ ///
+ ///
+ public abstract class BindingSourceValueProvider : IBindingSourceValueProvider
+ {
+ ///
+ /// Creates a new .
+ ///
+ ///
+ /// The . Must be a single-source (non-composite) with
+ /// equal to false.
+ ///
+ public BindingSourceValueProvider([NotNull] BindingSource bindingSource)
+ {
+ if (bindingSource.IsGreedy)
+ {
+ var message = Resources.FormatBindingSource_CannotBeGreedy(
+ bindingSource.DisplayName,
+ nameof(BindingSourceValueProvider));
+ throw new ArgumentException(message, nameof(bindingSource));
+ }
+
+ if (bindingSource is CompositeBindingSource)
+ {
+ var message = Resources.FormatBindingSource_CannotBeComposite(
+ bindingSource.DisplayName,
+ nameof(BindingSourceValueProvider));
+ throw new ArgumentException(message, nameof(bindingSource));
+ }
+
+ BindingSource = bindingSource;
+ }
+
+ ///
+ /// Gets the corresponding .
+ ///
+ protected BindingSource BindingSource { get; }
+
+ ///
+ public abstract Task ContainsPrefixAsync(string prefix);
+
+ ///
+ public abstract Task GetValueAsync(string key);
+
+ ///
+ public virtual IValueProvider Filter(BindingSource bindingSource)
+ {
+ if (bindingSource.CanAcceptDataFrom(BindingSource))
+ {
+ return this;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs
index 71d8a14916..02a4fc3a29 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs
@@ -12,8 +12,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
/// Represents a whose values come from a collection of s.
///
- public class CompositeValueProvider
- : Collection, IEnumerableValueProvider, IMetadataAwareValueProvider
+ public class CompositeValueProvider :
+ Collection,
+ IEnumerableValueProvider,
+ IBindingSourceValueProvider
{
///
/// Initializes a new instance of .
@@ -122,12 +124,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
}
///
- public IValueProvider Filter(IValueProviderMetadata valueBinderMetadata)
+ public IValueProvider Filter(BindingSource bindingSource)
{
var filteredValueProviders = new List();
- foreach (var valueProvider in this.OfType())
+ foreach (var valueProvider in this.OfType())
{
- var result = valueProvider.Filter(valueBinderMetadata);
+ var result = valueProvider.Filter(bindingSource);
if (result != null)
{
filteredValueProviders.Add(result);
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs
index 22307ffad9..cf55d74584 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs
@@ -8,17 +8,29 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
- public class DictionaryBasedValueProvider : MetadataAwareValueProvider
- where TBinderMetadata : IValueProviderMetadata
+ ///
+ /// An adapter for data stored in an
+ /// .
+ ///
+ public class DictionaryBasedValueProvider: BindingSourceValueProvider
{
private readonly IDictionary _values;
private PrefixContainer _prefixContainer;
- public DictionaryBasedValueProvider(IDictionary values)
+ ///
+ /// Creates a new .
+ ///
+ /// The of the data.
+ /// The values.
+ public DictionaryBasedValueProvider(
+ [NotNull] BindingSource bindingSource,
+ [NotNull] IDictionary values)
+ : base(bindingSource)
{
_values = values;
}
+ ///
public override Task ContainsPrefixAsync(string key)
{
var prefixContainer = GetOrCreatePrefixContainer();
@@ -35,6 +47,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return _prefixContainer;
}
+ ///
public override Task GetValueAsync([NotNull] string key)
{
object value;
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs
index 84212ec7ad..7b3f372d0a 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs
@@ -16,7 +16,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
var culture = GetCultureInfo(request);
- return new ReadableStringCollectionValueProvider(
+ return new ReadableStringCollectionValueProvider(
+ BindingSource.Form,
async () => await request.ReadFormAsync(),
culture);
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IBindingSourceValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IBindingSourceValueProvider.cs
new file mode 100644
index 0000000000..cec9fadda7
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IBindingSourceValueProvider.cs
@@ -0,0 +1,25 @@
+// 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
+{
+ ///
+ /// A value provider which is which can filter its contents based on .
+ ///
+ ///
+ /// Value providers are by-default included. If a model does not specify a
+ /// then all value providers are valid.
+ ///
+ public interface IBindingSourceValueProvider : IValueProvider
+ {
+ ///
+ /// Filters the value provider based on .
+ ///
+ /// The associated with a model.
+ ///
+ /// The filtered value provider, or null if the value provider does not match
+ /// .
+ ///
+ IValueProvider Filter([NotNull] BindingSource bindingSource);
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IMetadataAwareValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IMetadataAwareValueProvider.cs
deleted file mode 100644
index bb3832d0e5..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IMetadataAwareValueProvider.cs
+++ /dev/null
@@ -1,18 +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
-{
- ///
- /// A value provider which is aware of .
- ///
- public interface IMetadataAwareValueProvider : IValueProvider
- {
- ///
- /// Filters the value provider based on .
- ///
- /// The associated with a model.
- /// The filtered value provider.
- IValueProvider Filter([NotNull] IValueProviderMetadata metadata);
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MetadataAwareValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MetadataAwareValueProvider.cs
deleted file mode 100644
index 6d27eb6768..0000000000
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MetadataAwareValueProvider.cs
+++ /dev/null
@@ -1,32 +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.
-
-using System.Threading.Tasks;
-
-namespace Microsoft.AspNet.Mvc.ModelBinding
-{
- ///
- /// A value provider which can filter
- /// based on .
- ///
- ///
- /// Represents a type implementing
- ///
- public abstract class MetadataAwareValueProvider : IMetadataAwareValueProvider
- where TBinderMetadata : IValueProviderMetadata
- {
- public abstract Task ContainsPrefixAsync(string prefix);
-
- public abstract Task GetValueAsync(string key);
-
- public virtual IValueProvider Filter(IValueProviderMetadata valueBinderMetadata)
- {
- if (valueBinderMetadata is TBinderMetadata)
- {
- return this;
- }
-
- return null;
- }
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs
index a55a97a33f..8afd649abb 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs
@@ -18,13 +18,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (!storage.TryGetValue(_cacheKey, out value))
{
var queryCollection = context.HttpContext.Request.Query;
- provider = new ReadableStringCollectionValueProvider(queryCollection,
- CultureInfo.InvariantCulture);
+ provider = new ReadableStringCollectionValueProvider(
+ BindingSource.Query,
+ queryCollection,
+ CultureInfo.InvariantCulture);
+
storage[_cacheKey] = provider;
}
else
{
- provider = (ReadableStringCollectionValueProvider)value;
+ provider = (ReadableStringCollectionValueProvider)value;
}
return provider;
}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs
index c6e6136120..ef4727b7cf 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs
@@ -11,9 +11,10 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
- public class ReadableStringCollectionValueProvider :
- MetadataAwareValueProvider, IEnumerableValueProvider
- where TBinderMetadata : IValueProviderMetadata
+ ///
+ /// An adapter for data stored in an .
+ ///
+ public class ReadableStringCollectionValueProvider : BindingSourceValueProvider, IEnumerableValueProvider
{
private readonly CultureInfo _culture;
private readonly Func> _valuesFactory;
@@ -23,9 +24,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
///
/// Creates a provider for wrapping an existing set of key value pairs.
///
+ /// The for the data.
/// The key value pairs to wrap.
/// The culture to return with ValueProviderResult instances.
- public ReadableStringCollectionValueProvider([NotNull] IReadableStringCollection values, CultureInfo culture)
+ public ReadableStringCollectionValueProvider(
+ [NotNull] BindingSource bindingSource,
+ [NotNull] IReadableStringCollection values,
+ CultureInfo culture)
+ : base(bindingSource)
{
_values = values;
_culture = culture;
@@ -35,10 +41,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// Creates a provider for wrapping an
/// existing set of key value pairs provided by the delegate.
///
+ /// The for the data.
/// The delegate that provides the key value pairs to wrap.
/// The culture to return with ValueProviderResult instances.
- public ReadableStringCollectionValueProvider([NotNull] Func> valuesFactory,
- CultureInfo culture)
+ public ReadableStringCollectionValueProvider(
+ [NotNull] BindingSource bindingSource,
+ [NotNull] Func> valuesFactory,
+ CultureInfo culture)
+ : base(bindingSource)
{
_valuesFactory = valuesFactory;
_culture = culture;
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs
index b70f6551f8..1ec0265785 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs
@@ -7,7 +7,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context)
{
- return new DictionaryBasedValueProvider(context.RouteValues);
+ return new DictionaryBasedValueProvider(BindingSource.Path, context.RouteValues);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs
index d8efebb3bf..c77a9994e7 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs
@@ -90,9 +90,15 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
foreach (var parameter in candidate.Action.Parameters)
{
- // We only consider parameters that are bound from the URL.
- if ((parameter.BinderMetadata is IRouteDataValueProviderMetadata ||
- parameter.BinderMetadata is IQueryValueProviderMetadata) &&
+ // We only consider parameters that are marked as bound from the URL.
+ var source = BindingSource.GetBindingSource(parameter.BinderMetadata);
+ if (source == null)
+ {
+ continue;
+ }
+
+ if ((source.CanAcceptDataFrom(BindingSource.Path) ||
+ source.CanAcceptDataFrom(BindingSource.Query)) &&
ValueProviderResult.CanConvertFromString(parameter.ParameterType))
{
var optionalMetadata = parameter.BinderMetadata as IOptionalBinderMetadata;
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs
index 9c78de7ce5..54928897fe 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs
@@ -3,6 +3,7 @@
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ModelBinding;
+using WebApiShimResources = Microsoft.AspNet.Mvc.WebApiCompatShim.Resources;
namespace System.Web.Http
{
@@ -13,10 +14,15 @@ namespace System.Web.Http
public class FromUriAttribute :
Attribute,
IOptionalBinderMetadata,
- IQueryValueProviderMetadata,
- IRouteDataValueProviderMetadata,
+ IBindingSourceMetadata,
IModelNameProvider
{
+ private static readonly BindingSource FromUriSource = CompositeBindingSource.Create(
+ new BindingSource[] { BindingSource.Path, BindingSource.Query },
+ WebApiShimResources.BindingSource_URL);
+
+ public BindingSource BindingSource { get { return FromUriSource; } }
+
public bool IsOptional { get; set; }
///
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs
index 986908598c..51d9fdff57 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs
@@ -170,6 +170,22 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
return string.Format(CultureInfo.CurrentCulture, GetString("CreatedAtRoute_RouteFailed"), p0);
}
+ ///
+ /// URL
+ ///
+ internal static string BindingSource_URL
+ {
+ get { return GetString("BindingSource_URL"); }
+ }
+
+ ///
+ /// URL
+ ///
+ internal static string FormatBindingSource_URL()
+ {
+ return GetString("BindingSource_URL");
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx
index 7ca73030e8..f4448a3035 100644
--- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -147,4 +147,7 @@
Failed to generate a URL using route '{0}'.
+
+ URL
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs
index a144a3f65e..29e86d034b 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/BodyModelBinderTests.cs
@@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc
.Verifiable();
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: mockInputFormatter.Object);
- bindingContext.ModelMetadata.BinderMetadata = Mock.Of();
+ bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute();
var binder = GetBodyBinder(mockInputFormatter.Object, mockValidator.Object);
@@ -47,7 +47,8 @@ namespace Microsoft.AspNet.Mvc
{
// Arrange
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
- bindingContext.ModelMetadata.BinderMetadata = Mock.Of();
+ bindingContext.ModelMetadata.BinderMetadata = new FromBodyAttribute();
+
var binder = bindingContext.OperationBindingContext.ModelBinder;
// Act
@@ -61,22 +62,61 @@ namespace Microsoft.AspNet.Mvc
Assert.True(bindingContext.ModelState.ContainsKey("someName"));
}
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task BindModel_IsMetadataAware(bool useBody)
+ [Fact]
+ public async Task BindModel_IsGreedy()
{
// Arrange
+ var metadata = new Mock();
+ metadata.SetupGet(m => m.BindingSource).Returns(BindingSource.Body);
+
var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
- bindingContext.ModelMetadata.BinderMetadata = useBody ? Mock.Of() :
- Mock.Of();
+ bindingContext.ModelMetadata.BinderMetadata = metadata.Object;
+
var binder = bindingContext.OperationBindingContext.ModelBinder;
// Act
var binderResult = await binder.BindModelAsync(bindingContext);
// Assert
- Assert.Equal(useBody, binderResult);
+ Assert.True(binderResult);
+ }
+
+ [Fact]
+ public async Task BindModel_IsGreedy_IgnoresWrongSource()
+ {
+ // Arrange
+ var metadata = new Mock();
+ metadata.SetupGet(m => m.BindingSource).Returns(BindingSource.Header);
+
+ var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
+ bindingContext.ModelMetadata.BinderMetadata = metadata.Object;
+
+ var binder = bindingContext.OperationBindingContext.ModelBinder;
+
+ // Act
+ var binderResult = await binder.BindModelAsync(bindingContext);
+
+ // Assert
+ Assert.False(binderResult);
+ }
+
+ [Fact]
+ public async Task BindModel_IsGreedy_IgnoresMetadataWithNoSource()
+ {
+ // Arrange
+ var metadata = new Mock();
+ metadata.SetupGet(m => m.BindingSource).Returns((BindingSource)null);
+
+ var bindingContext = GetBindingContext(typeof(Person), inputFormatter: null);
+ bindingContext.ModelMetadata.BinderMetadata = metadata.Object;
+
+ var binder = bindingContext.OperationBindingContext.ModelBinder;
+
+ // Act
+ var binderResult = await binder.BindModelAsync(bindingContext);
+
+ // Assert
+ Assert.False(binderResult);
}
private static ModelBindingContext GetBindingContext(Type modelType, IInputFormatter inputFormatter)
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs
index 5117d68c81..87f118a533 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs
@@ -138,7 +138,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var parameter = Assert.Single(description.ParameterDescriptions);
- Assert.Equal(ApiParameterSource.Path, parameter.Source);
+ Assert.Equal(BindingSource.Path, parameter.Source);
Assert.Equal(isOptional, parameter.RouteInfo.IsOptional);
Assert.Equal("id", parameter.Name);
@@ -186,7 +186,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var parameter = Assert.Single(description.ParameterDescriptions);
- Assert.Equal(ApiParameterSource.Path, parameter.Source);
+ Assert.Equal(BindingSource.Path, parameter.Source);
Assert.Equal(isOptional, parameter.RouteInfo.IsOptional);
Assert.Equal("id", parameter.Name);
@@ -219,6 +219,8 @@ namespace Microsoft.AspNet.Mvc.Description
var action = CreateActionDescriptor(methodName);
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
+ var expected = new BindingSource(source, displayName: null, isGreedy: false, isFromRequest: false);
+
// Act
var descriptions = GetApiDescriptions(action);
@@ -226,7 +228,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var parameters = description.ParameterDescriptions;
- var id = Assert.Single(parameters, p => p.Source == new ApiParameterSource(source, displayName: null));
+ var id = Assert.Single(parameters, p => p.Source == expected);
Assert.Null(id.RouteInfo);
}
@@ -248,6 +250,8 @@ namespace Microsoft.AspNet.Mvc.Description
var action = CreateActionDescriptor(methodName);
action.AttributeRouteInfo = new AttributeRouteInfo { Template = template };
+ var expected = new BindingSource(source, displayName: null, isGreedy: false, isFromRequest: false);
+
// Act
var descriptions = GetApiDescriptions(action);
@@ -255,7 +259,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var parameters = description.ParameterDescriptions;
- var id = Assert.Single(parameters, p => p.Source == new ApiParameterSource(source, displayName: null));
+ var id = Assert.Single(parameters, p => p.Source == expected);
Assert.NotNull(id.RouteInfo);
}
@@ -318,11 +322,11 @@ namespace Microsoft.AspNet.Mvc.Description
// Assert
var description = Assert.Single(descriptions);
var id1 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id1");
- Assert.Equal(ApiParameterSource.Path, id1.Source);
+ Assert.Equal(BindingSource.Path, id1.Source);
Assert.Empty(id1.RouteInfo.Constraints);
var id2 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id2");
- Assert.Equal(ApiParameterSource.Path, id2.Source);
+ Assert.Equal(BindingSource.Path, id2.Source);
Assert.IsType(Assert.Single(id2.RouteInfo.Constraints));
}
@@ -537,7 +541,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("product", parameter.Name);
- Assert.Same(ApiParameterSource.ModelBinding, parameter.Source);
+ Assert.Same(BindingSource.ModelBinding, parameter.Source);
}
[Fact]
@@ -554,7 +558,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("id", parameter.Name);
- Assert.Same(ApiParameterSource.Path, parameter.Source);
+ Assert.Same(BindingSource.Path, parameter.Source);
}
[Fact]
@@ -571,7 +575,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("id", parameter.Name);
- Assert.Same(ApiParameterSource.Query, parameter.Source);
+ Assert.Same(BindingSource.Query, parameter.Source);
}
[Fact]
@@ -588,7 +592,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("product", parameter.Name);
- Assert.Same(ApiParameterSource.Body, parameter.Source);
+ Assert.Same(BindingSource.Body, parameter.Source);
}
[Fact]
@@ -605,7 +609,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("product", parameter.Name);
- Assert.Same(ApiParameterSource.Form, parameter.Source);
+ Assert.Same(BindingSource.Form, parameter.Source);
}
[Fact]
@@ -622,7 +626,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("id", parameter.Name);
- Assert.Same(ApiParameterSource.Header, parameter.Source);
+ Assert.Same(BindingSource.Header, parameter.Source);
}
// 'Hidden' parameters are hidden (not returned).
@@ -654,7 +658,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("product", parameter.Name);
- Assert.Same(ApiParameterSource.Custom, parameter.Source);
+ Assert.Same(BindingSource.Custom, parameter.Source);
}
[Fact]
@@ -671,7 +675,7 @@ namespace Microsoft.AspNet.Mvc.Description
var parameter = Assert.Single(description.ParameterDescriptions);
Assert.Equal("product", parameter.Name);
- Assert.Same(ApiParameterSource.ModelBinding, parameter.Source);
+ Assert.Same(BindingSource.ModelBinding, parameter.Source);
}
[Fact]
@@ -689,19 +693,19 @@ namespace Microsoft.AspNet.Mvc.Description
Assert.Equal(4, description.ParameterDescriptions.Count);
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
- Assert.Same(ApiParameterSource.Path, id.Source);
+ Assert.Same(BindingSource.Path, id.Source);
Assert.Equal(typeof(int), id.Type);
var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product");
- Assert.Same(ApiParameterSource.Body, product.Source);
+ Assert.Same(BindingSource.Body, product.Source);
Assert.Equal(typeof(Product), product.Type);
var userId = Assert.Single(description.ParameterDescriptions, p => p.Name == "UserId");
- Assert.Same(ApiParameterSource.Header, userId.Source);
+ Assert.Same(BindingSource.Header, userId.Source);
Assert.Equal(typeof(string), userId.Type);
var comments = Assert.Single(description.ParameterDescriptions, p => p.Name == "Comments");
- Assert.Same(ApiParameterSource.ModelBinding, comments.Source);
+ Assert.Same(BindingSource.ModelBinding, comments.Source);
Assert.Equal(typeof(string), comments.Type);
}
@@ -721,19 +725,19 @@ namespace Microsoft.AspNet.Mvc.Description
Assert.Equal(4, description.ParameterDescriptions.Count);
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
- Assert.Same(ApiParameterSource.Path, id.Source);
+ Assert.Same(BindingSource.Path, id.Source);
Assert.Equal(typeof(int), id.Type);
var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product");
- Assert.Same(ApiParameterSource.Body, product.Source);
+ Assert.Same(BindingSource.Body, product.Source);
Assert.Equal(typeof(Product), product.Type);
var userId = Assert.Single(description.ParameterDescriptions, p => p.Name == "UserId");
- Assert.Same(ApiParameterSource.Header, userId.Source);
+ Assert.Same(BindingSource.Header, userId.Source);
Assert.Equal(typeof(string), userId.Type);
var comments = Assert.Single(description.ParameterDescriptions, p => p.Name == "Comments");
- Assert.Same(ApiParameterSource.Query, comments.Source);
+ Assert.Same(BindingSource.Query, comments.Source);
Assert.Equal(typeof(string), comments.Type);
}
@@ -752,19 +756,19 @@ namespace Microsoft.AspNet.Mvc.Description
Assert.Equal(4, description.ParameterDescriptions.Count);
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
- Assert.Same(ApiParameterSource.Path, id.Source);
+ Assert.Same(BindingSource.Path, id.Source);
Assert.Equal(typeof(int), id.Type);
var quantity = Assert.Single(description.ParameterDescriptions, p => p.Name == "Quantity");
- Assert.Same(ApiParameterSource.ModelBinding, quantity.Source);
+ Assert.Same(BindingSource.ModelBinding, quantity.Source);
Assert.Equal(typeof(int), quantity.Type);
var productId = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product.Id");
- Assert.Same(ApiParameterSource.ModelBinding, productId.Source);
+ Assert.Same(BindingSource.ModelBinding, productId.Source);
Assert.Equal(typeof(int), productId.Type);
var price = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product.Price");
- Assert.Same(ApiParameterSource.Query, price.Source);
+ Assert.Same(BindingSource.Query, price.Source);
Assert.Equal(typeof(decimal), price.Type);
}
@@ -784,15 +788,15 @@ namespace Microsoft.AspNet.Mvc.Description
Assert.Equal(3, description.ParameterDescriptions.Count);
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
- Assert.Same(ApiParameterSource.Path, id.Source);
+ Assert.Same(BindingSource.Path, id.Source);
Assert.Equal(typeof(int), id.Type);
var quantity = Assert.Single(description.ParameterDescriptions, p => p.Name == "Quantity");
- Assert.Same(ApiParameterSource.Query, quantity.Source);
+ Assert.Same(BindingSource.Query, quantity.Source);
Assert.Equal(typeof(int), quantity.Type);
var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product");
- Assert.Same(ApiParameterSource.Query, product.Source);
+ Assert.Same(BindingSource.Query, product.Source);
Assert.Equal(typeof(OrderProductDTO), product.Type);
}
@@ -810,7 +814,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var c = Assert.Single(description.ParameterDescriptions);
- Assert.Same(ApiParameterSource.Query, c.Source);
+ Assert.Same(BindingSource.Query, c.Source);
Assert.Equal("C.C", c.Name);
Assert.Equal(typeof(Cycle1), c.Type);
}
@@ -829,7 +833,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var products = Assert.Single(description.ParameterDescriptions);
- Assert.Same(ApiParameterSource.Query, products.Source);
+ Assert.Same(BindingSource.Query, products.Source);
Assert.Equal("Products", products.Name);
Assert.Equal(typeof(Product[]), products.Type);
}
@@ -849,7 +853,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var c = Assert.Single(description.ParameterDescriptions);
- Assert.Same(ApiParameterSource.ModelBinding, c.Source);
+ Assert.Same(BindingSource.ModelBinding, c.Source);
Assert.Equal("c", c.Name);
Assert.Equal(typeof(HasCollection_Complex), c.Type);
}
@@ -868,7 +872,7 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var r = Assert.Single(description.ParameterDescriptions);
- Assert.Same(ApiParameterSource.Query, r.Source);
+ Assert.Same(BindingSource.Query, r.Source);
Assert.Equal("r", r.Name);
Assert.Equal(typeof(RedundentMetadata), r.Type);
}
@@ -887,11 +891,11 @@ namespace Microsoft.AspNet.Mvc.Description
var description = Assert.Single(descriptions);
var name = Assert.Single(description.ParameterDescriptions, p => p.Name == "Name");
- Assert.Same(ApiParameterSource.Header, name.Source);
+ Assert.Same(BindingSource.Header, name.Source);
Assert.Equal(typeof(string), name.Type);
var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id");
- Assert.Same(ApiParameterSource.Form, id.Source);
+ Assert.Same(BindingSource.Form, id.Source);
Assert.Equal(typeof(int), id.Type);
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs
index 23c83e57f9..9eda87caa1 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs
@@ -393,12 +393,14 @@ namespace Microsoft.AspNet.Mvc.Core.Test
public string Name { get; set; }
}
- private class NonValueProviderBinderMetadataAttribute : Attribute, IBinderMetadata
+ private class NonValueProviderBinderMetadataAttribute : Attribute, IBindingSourceMetadata
{
+ public BindingSource BindingSource { get { return BindingSource.Body; } }
}
- private class ValueProviderMetadataAttribute : Attribute, IValueProviderMetadata
+ private class ValueProviderMetadataAttribute : Attribute, IBindingSourceMetadata
{
+ public BindingSource BindingSource { get { return BindingSource.Query; } }
}
[Bind(new string[] { nameof(IncludedExplicitly1), nameof(IncludedExplicitly2) })]
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs
index 293c6bb8e0..d918ec44d8 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ModelBindingHelperTest.cs
@@ -70,7 +70,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
{ "", null }
};
- var valueProvider = new DictionaryBasedValueProvider(values);
+ var valueProvider = new TestValueProvider(values);
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
@@ -108,7 +108,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ "", null },
{ "MyProperty", "MyPropertyValue" }
};
- var valueProvider = new DictionaryBasedValueProvider(values);
+ var valueProvider = new TestValueProvider(values);
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
@@ -193,7 +193,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
string.Equals(propertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) ||
string.Equals(propertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
- var valueProvider = new DictionaryBasedValueProvider(values);
+ var valueProvider = new TestValueProvider(values);
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
@@ -276,7 +276,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ "ExcludedProperty", "ExcludedPropertyValue" }
};
- var valueProvider = new DictionaryBasedValueProvider(values);
+ var valueProvider = new TestValueProvider(values);
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
@@ -326,7 +326,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{ "ExcludedProperty", "ExcludedPropertyValue" }
};
- var valueProvider = new DictionaryBasedValueProvider(values);
+ var valueProvider = new TestValueProvider(values);
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
@@ -509,10 +509,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test
public string ExcludedProperty { get; set; }
}
-
- private class TestValueBinderMetadata : IValueProviderMetadata
- {
- }
}
}
#endif
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/TestValueProvider.cs b/test/Microsoft.AspNet.Mvc.Core.Test/TestValueProvider.cs
new file mode 100644
index 0000000000..b63fce6502
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/TestValueProvider.cs
@@ -0,0 +1,26 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ public class TestValueProvider : DictionaryBasedValueProvider
+ {
+ public static readonly BindingSource TestBindingSource = new BindingSource(
+ id: "Test",
+ displayName: "Test",
+ isGreedy: false,
+ isFromRequest: true);
+
+ public TestValueProvider(IDictionary values)
+ : base(TestBindingSource, values)
+ {
+ }
+
+ public TestValueProvider(BindingSource bindingSource, IDictionary values)
+ : base(bindingSource, values)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs
index c832cfa5e0..7b5a102bc1 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
-using Microsoft.AspNet.Mvc.Description;
+using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Xml;
using Microsoft.AspNet.TestHost;
using Newtonsoft.Json;
@@ -690,11 +690,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(2, parameters.Count);
var i = Assert.Single(parameters, p => p.Name == "i");
- Assert.Equal(ApiParameterSource.ModelBinding.Id, i.Source);
+ Assert.Equal(BindingSource.ModelBinding.Id, i.Source);
Assert.Equal(typeof(int).FullName, i.Type);
var s = Assert.Single(parameters, p => p.Name == "s");
- Assert.Equal(ApiParameterSource.ModelBinding.Id, s.Source);
+ Assert.Equal(BindingSource.ModelBinding.Id, s.Source);
Assert.Equal(typeof(string).FullName, s.Type);
}
@@ -718,11 +718,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(2, parameters.Count);
var i = Assert.Single(parameters, p => p.Name == "i");
- Assert.Equal(ApiParameterSource.Query.Id, i.Source);
+ Assert.Equal(BindingSource.Query.Id, i.Source);
Assert.Equal(typeof(int).FullName, i.Type);
var s = Assert.Single(parameters, p => p.Name == "s");
- Assert.Equal(ApiParameterSource.Path.Id, s.Source);
+ Assert.Equal(BindingSource.Path.Id, s.Source);
Assert.Equal(typeof(string).FullName, s.Type);
}
@@ -746,7 +746,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(1, parameters.Count);
var product = Assert.Single(parameters, p => p.Name == "product");
- Assert.Equal(ApiParameterSource.ModelBinding.Id, product.Source);
+ Assert.Equal(BindingSource.ModelBinding.Id, product.Source);
Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type);
}
@@ -770,11 +770,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(2, parameters.Count);
var id = Assert.Single(parameters, p => p.Name == "id");
- Assert.Equal(ApiParameterSource.Path.Id, id.Source);
+ Assert.Equal(BindingSource.Path.Id, id.Source);
Assert.Equal(typeof(int).FullName, id.Type);
var product = Assert.Single(parameters, p => p.Name == "product");
- Assert.Equal(ApiParameterSource.Body.Id, product.Source);
+ Assert.Equal(BindingSource.Body.Id, product.Source);
Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type);
}
@@ -798,27 +798,27 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(6, parameters.Count);
var customerId = Assert.Single(parameters, p => p.Name == "CustomerId");
- Assert.Equal(ApiParameterSource.Query.Id, customerId.Source);
+ Assert.Equal(BindingSource.Query.Id, customerId.Source);
Assert.Equal(typeof(string).FullName, customerId.Type);
var referrer = Assert.Single(parameters, p => p.Name == "Referrer");
- Assert.Equal(ApiParameterSource.Header.Id, referrer.Source);
+ Assert.Equal(BindingSource.Header.Id, referrer.Source);
Assert.Equal(typeof(string).FullName, referrer.Type);
var quantity = Assert.Single(parameters, p => p.Name == "Details.Quantity");
- Assert.Equal(ApiParameterSource.Form.Id, quantity.Source);
+ Assert.Equal(BindingSource.Form.Id, quantity.Source);
Assert.Equal(typeof(int).FullName, quantity.Type);
var product = Assert.Single(parameters, p => p.Name == "Details.Product");
- Assert.Equal(ApiParameterSource.Form.Id, product.Source);
+ Assert.Equal(BindingSource.Form.Id, product.Source);
Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type);
var shippingInstructions = Assert.Single(parameters, p => p.Name == "Comments.ShippingInstructions");
- Assert.Equal(ApiParameterSource.Query.Id, shippingInstructions.Source);
+ Assert.Equal(BindingSource.Query.Id, shippingInstructions.Source);
Assert.Equal(typeof(string).FullName, shippingInstructions.Type);
var feedback = Assert.Single(parameters, p => p.Name == "Comments.Feedback");
- Assert.Equal(ApiParameterSource.Form.Id, feedback.Source);
+ Assert.Equal(BindingSource.Form.Id, feedback.Source);
Assert.Equal(typeof(string).FullName, feedback.Type);
}
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs
new file mode 100644
index 0000000000..df559821ab
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceModelBinderTest.cs
@@ -0,0 +1,116 @@
+// 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
+{
+ public class BindingSourceModelBinderTest
+ {
+ [Fact]
+ public void BindingSourceModelBinder_ThrowsOnNonGreedySource()
+ {
+ // Arrange
+ var expected =
+ "The provided binding source 'Test Source' is not a greedy data source. " +
+ "'BindingSourceModelBinder' only supports greedy data sources." + Environment.NewLine +
+ "Parameter name: bindingSource";
+
+ var bindingSource = new BindingSource(
+ "Test",
+ displayName: "Test Source",
+ isGreedy: false,
+ isFromRequest: true);
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => new TestableBindingSourceModelBinder(bindingSource));
+ Assert.Equal(expected, exception.Message);
+ }
+
+ [Fact]
+ public async Task BindingSourceModelBinder_ReturnsFalse_WithNoSource()
+ {
+ // Arrange
+ var context = new ModelBindingContext();
+ context.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(
+ modelAccessor: null,
+ modelType: typeof(string));
+
+ var binder = new TestableBindingSourceModelBinder(BindingSource.Body);
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.False(result);
+ Assert.False(binder.WasBindModelCoreCalled);
+ }
+
+ [Fact]
+ public async Task BindingSourceModelBinder_ReturnsFalse_NonMatchingSource()
+ {
+ // Arrange
+ var context = new ModelBindingContext();
+ context.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(
+ modelAccessor: null,
+ modelType: typeof(string));
+
+ context.ModelMetadata.BinderMetadata = new ModelBinderAttribute()
+ {
+ BindingSource = BindingSource.Query,
+ };
+
+ var binder = new TestableBindingSourceModelBinder(BindingSource.Body);
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.False(result);
+ Assert.False(binder.WasBindModelCoreCalled);
+ }
+
+ [Fact]
+ public async Task BindingSourceModelBinder_ReturnsTrue_MatchingSource()
+ {
+ // Arrange
+ var context = new ModelBindingContext();
+ context.ModelMetadata = new EmptyModelMetadataProvider().GetMetadataForType(
+ modelAccessor: null,
+ modelType: typeof(string));
+
+ context.ModelMetadata.BinderMetadata = new ModelBinderAttribute()
+ {
+ BindingSource = BindingSource.Body,
+ };
+
+ var binder = new TestableBindingSourceModelBinder(BindingSource.Body);
+
+ // Act
+ var result = await binder.BindModelAsync(context);
+
+ // Assert
+ Assert.True(result);
+ Assert.True(binder.WasBindModelCoreCalled);
+ }
+
+ private class TestableBindingSourceModelBinder : BindingSourceModelBinder
+ {
+ public bool WasBindModelCoreCalled { get; private set; }
+
+ public TestableBindingSourceModelBinder(BindingSource source)
+ : base(source)
+ {
+ }
+
+ protected override Task BindModelCoreAsync([NotNull] ModelBindingContext bindingContext)
+ {
+ WasBindModelCoreCalled = true;
+ return Task.FromResult(true);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceValueProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceValueProviderTest.cs
new file mode 100644
index 0000000000..bae6afb0ff
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/BindingSourceValueProviderTest.cs
@@ -0,0 +1,97 @@
+// 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
+{
+ public class BindingSourceValueProviderTest
+ {
+ [Fact]
+ public void BindingSourceValueProvider_ThrowsOnNonGreedySource()
+ {
+ // Arrange
+ var expected =
+ "The provided binding source 'Test Source' is a greedy data source. " +
+ "'BindingSourceValueProvider' does not support greedy data sources." + Environment.NewLine +
+ "Parameter name: bindingSource";
+
+ var bindingSource = new BindingSource(
+ "Test",
+ displayName: "Test Source",
+ isGreedy: true,
+ isFromRequest: true);
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => new TestableBindingSourceValueProvider(bindingSource));
+ Assert.Equal(expected, exception.Message);
+ }
+
+ [Fact]
+ public void BindingSourceValueProvider_ThrowsOnCompositeSource()
+ {
+ // Arrange
+ var expected =
+ "The provided binding source 'Test Source' is a composite. " +
+ "'BindingSourceValueProvider' requires that the source must represent a single type of input." +
+ Environment.NewLine +
+ "Parameter name: bindingSource";
+
+ var bindingSource = CompositeBindingSource.Create(
+ bindingSources: new BindingSource[] { BindingSource.Query, BindingSource.Form },
+ displayName: "Test Source");
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => new TestableBindingSourceValueProvider(bindingSource));
+ Assert.Equal(expected, exception.Message);
+ }
+
+ [Fact]
+ public void BindingSourceValueProvider_ReturnsNull_WithNonMatchingSource()
+ {
+ // Arrange
+ var valueProvider = new TestableBindingSourceValueProvider(BindingSource.Query);
+
+ // Act
+ var result = valueProvider.Filter(BindingSource.Body);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void BindingSourceValueProvider_ReturnsSelf_WithMatchingSource()
+ {
+ // Arrange
+ var valueProvider = new TestableBindingSourceValueProvider(BindingSource.Query);
+
+ // Act
+ var result = valueProvider.Filter(BindingSource.Query);
+
+ // Assert
+ Assert.Same(valueProvider, result);
+ }
+
+ private class TestableBindingSourceValueProvider : BindingSourceValueProvider
+ {
+ public TestableBindingSourceValueProvider(BindingSource source)
+ : base(source)
+ {
+ }
+
+ public override Task ContainsPrefixAsync(string prefix)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task GetValueAsync(string key)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs
index baa92719bf..aedc02d95d 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/HeaderModelBinderTests.cs
@@ -91,8 +91,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
return bindingContext;
}
- public class TestFromHeader : IHeaderBinderMetadata
+ public class TestFromHeader : IBindingSourceMetadata
{
+ public BindingSource BindingSource { get; } = BindingSource.Header;
}
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs
index 67c2d6cf97..a2cfb48043 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs
@@ -236,26 +236,30 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Theory]
[InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)]
[InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)]
- public async Task CanCreateModel_ForExplicitValueProviderMetadata_UsesOriginalValueProvider(Type modelType, bool originalValueProviderProvidesValue)
+ public async Task CanCreateModel_ForExplicitValueProviderMetadata_UsesOriginalValueProvider(
+ Type modelType,
+ bool originalValueProviderProvidesValue)
{
var mockValueProvider = new Mock();
mockValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny()))
.Returns(Task.FromResult(false));
- var mockOriginalValueProvider = new Mock();
- mockOriginalValueProvider.Setup(o => o.ContainsPrefixAsync(It.IsAny()))
- .Returns(Task.FromResult(originalValueProviderProvidesValue));
- mockOriginalValueProvider.Setup(o => o.Filter(It.IsAny()))
- .Returns(
- valueProviderMetadata =>
- {
- if (valueProviderMetadata is ValueBinderMetadataAttribute)
- {
- return mockOriginalValueProvider.Object;
- }
+ var mockOriginalValueProvider = new Mock();
+ mockOriginalValueProvider
+ .Setup(o => o.ContainsPrefixAsync(It.IsAny()))
+ .Returns(Task.FromResult(originalValueProviderProvidesValue));
- return null;
- });
+ mockOriginalValueProvider
+ .Setup(o => o.Filter(It.IsAny()))
+ .Returns(source =>
+ {
+ if (source == BindingSource.Query)
+ {
+ return mockOriginalValueProvider.Object;
+ }
+
+ return null;
+ });
var bindingContext = new MutableObjectBinderContext
{
@@ -1542,12 +1546,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public Document SubDocument { get; set; }
}
- private class NonValueBinderMetadataAttribute : Attribute, IBinderMetadata
+ private class NonValueBinderMetadataAttribute : Attribute, IBindingSourceMetadata
{
+ public BindingSource BindingSource { get { return BindingSource.Body; } }
}
- private class ValueBinderMetadataAttribute : Attribute, IValueProviderMetadata
+ private class ValueBinderMetadataAttribute : Attribute, IBindingSourceMetadata
{
+ public BindingSource BindingSource { get { return BindingSource.Query; } }
}
public class ExcludedProvider : IPropertyBindingPredicateProvider
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/BindingSourceTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/BindingSourceTest.cs
new file mode 100644
index 0000000000..a99d693190
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/BindingSourceTest.cs
@@ -0,0 +1,51 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ public class BindingSourceTest
+ {
+ [Fact]
+ public void BindingSource_CanAcceptDataFrom_ThrowsOnComposite()
+ {
+ // Arrange
+ var expected =
+ "The provided binding source 'Test Source' is a composite. " +
+ "'CanAcceptDataFrom' requires that the source must represent a single type of input." +
+ Environment.NewLine +
+ "Parameter name: bindingSource";
+
+ var bindingSource = CompositeBindingSource.Create(
+ bindingSources: new BindingSource[] { BindingSource.Query, BindingSource.Form },
+ displayName: "Test Source");
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => BindingSource.Query.CanAcceptDataFrom(bindingSource));
+ Assert.Equal(expected, exception.Message);
+ }
+
+ [Fact]
+ public void BindingSource_CanAcceptDataFrom_Match()
+ {
+ // Act
+ var result = BindingSource.Query.CanAcceptDataFrom(BindingSource.Query);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void BindingSource_CanAcceptDataFrom_NoMatch()
+ {
+ // Act
+ var result = BindingSource.Query.CanAcceptDataFrom(BindingSource.Path);
+
+ // Assert
+ Assert.False(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs
index b26ac03f34..fda9e0d1f0 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsMetadataAttributesTest.cs
@@ -145,6 +145,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private class TestBinderTypeProvider : IBinderTypeProviderMetadata
{
public Type BinderType { get; set; }
+
+ public BindingSource BindingSource { get; set; }
}
private class TestPredicateProvider : IPropertyBindingPredicateProvider
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs
index 9fe52fc77d..3adf5b448d 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CachedDataAnnotationsModelMetadataTest.cs
@@ -525,6 +525,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
private class TestBinderTypeProvider : IBinderTypeProviderMetadata
{
public Type BinderType { get; set; }
+
+ public BindingSource BindingSource { get; set; }
}
private class DataTypeWithCustomDisplayFormat : DataTypeAttribute
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CompositeBindingSourceTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CompositeBindingSourceTest.cs
new file mode 100644
index 0000000000..d183ddfbcd
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/CompositeBindingSourceTest.cs
@@ -0,0 +1,66 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ public class CompositeBindingSourceTest
+ {
+ [Fact]
+ public void CompositeBindingSourceTest_CanAcceptDataFrom_ThrowsOnComposite()
+ {
+ // Arrange
+ var expected =
+ "The provided binding source 'Test Source2' is a composite. " +
+ "'CanAcceptDataFrom' requires that the source must represent a single type of input." +
+ Environment.NewLine +
+ "Parameter name: bindingSource";
+
+ var composite1 = CompositeBindingSource.Create(
+ bindingSources: new BindingSource[] { BindingSource.Query, BindingSource.Form },
+ displayName: "Test Source1");
+
+ var composite2 = CompositeBindingSource.Create(
+ bindingSources: new BindingSource[] { BindingSource.Query, BindingSource.Form },
+ displayName: "Test Source2");
+
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => composite1.CanAcceptDataFrom(composite2));
+ Assert.Equal(expected, exception.Message);
+ }
+
+ [Fact]
+ public void CompositeBindingSourceTest_CanAcceptDataFrom_Match()
+ {
+ // Arrange
+ var composite = CompositeBindingSource.Create(
+ bindingSources: new BindingSource[] { BindingSource.Query, BindingSource.Form },
+ displayName: "Test Source1");
+
+ // Act
+ var result = composite.CanAcceptDataFrom(BindingSource.Query);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void CompositeBindingSourceTest_CanAcceptDataFrom_NoMatch()
+ {
+ // Arrange
+ var composite = CompositeBindingSource.Create(
+ bindingSources: new BindingSource[] { BindingSource.Query, BindingSource.Form },
+ displayName: "Test Source1");
+
+ // Act
+ var result = composite.CanAcceptDataFrom(BindingSource.Path);
+
+ // Assert
+ Assert.False(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelBinderAttributeTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelBinderAttributeTest.cs
index 612d9b3926..ee720736de 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelBinderAttributeTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelBinderAttributeTest.cs
@@ -25,5 +25,47 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Assert
Assert.Equal(expected, ex.Message);
}
+
+ [Fact]
+ public void NoBinderType_NoBindingSource()
+ {
+ // Arrange
+ var attribute = new ModelBinderAttribute();
+
+ // Act
+ var source = attribute.BindingSource;
+
+ // Assert
+ Assert.Null(source);
+ }
+
+ [Fact]
+ public void BinderType_DefaultCustomBindingSource()
+ {
+ // Arrange
+ var attribute = new ModelBinderAttribute();
+ attribute.BinderType = typeof(ByteArrayModelBinder);
+
+ // Act
+ var source = attribute.BindingSource;
+
+ // Assert
+ Assert.Equal(BindingSource.Custom, source);
+ }
+
+ [Fact]
+ public void BinderType_SettingBindingSource_OverridesDefaultCustomBindingSource()
+ {
+ // Arrange
+ var attribute = new ModelBinderAttribute();
+ attribute.BindingSource = BindingSource.Query;
+ attribute.BinderType = typeof(ByteArrayModelBinder);
+
+ // Act
+ var source = attribute.BindingSource;
+
+ // Assert
+ Assert.Equal(BindingSource.Query, source);
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/TestValueProvider.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/TestValueProvider.cs
new file mode 100644
index 0000000000..b63fce6502
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/TestValueProvider.cs
@@ -0,0 +1,26 @@
+// 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.Collections.Generic;
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ public class TestValueProvider : DictionaryBasedValueProvider
+ {
+ public static readonly BindingSource TestBindingSource = new BindingSource(
+ id: "Test",
+ displayName: "Test",
+ isGreedy: false,
+ isFromRequest: true);
+
+ public TestValueProvider(IDictionary values)
+ : base(TestBindingSource, values)
+ {
+ }
+
+ public TestValueProvider(BindingSource bindingSource, IDictionary values)
+ : base(bindingSource, values)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/CompositeValueProviderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/CompositeValueProviderTests.cs
index f59b3f2cef..a01133fc9e 100644
--- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/CompositeValueProviderTests.cs
+++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/CompositeValueProviderTests.cs
@@ -13,21 +13,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class CompositeValueProviderTests
{
- public static IEnumerable