Fix #1287 - Port WebAPI parameter binding behavior
This change modifies the default parameter binding behavior for an ApiController to use the WebAPI rules. 'simple types' default to use route data or query string 'complex types' default to use the body (formatters) Adds ModelBindingAttribute to enabled model binding
This commit is contained in:
parent
5b1bcb6079
commit
df8f84b772
|
|
@ -1,9 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
using MvcSample.Web.Models;
|
||||
|
||||
namespace MvcSample.Web
|
||||
|
|
@ -61,74 +58,11 @@ namespace MvcSample.Web
|
|||
return result;
|
||||
}
|
||||
|
||||
private class OverloadAttribute : Attribute, IActionConstraint
|
||||
private class OverloadAttribute : Attribute, IActionConstraintFactory
|
||||
{
|
||||
public int Order { get; } = Int32.MaxValue;
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
public IActionConstraint CreateInstance(IServiceProvider services)
|
||||
{
|
||||
var candidates = context.Candidates.Select(a => new
|
||||
{
|
||||
Action = a,
|
||||
Parameters = GetOverloadableParameters(a.Action),
|
||||
});
|
||||
|
||||
var valueProviderFactory = context.RouteContext.HttpContext.RequestServices
|
||||
.GetRequiredService<ICompositeValueProviderFactory>();
|
||||
|
||||
var factoryContext = new ValueProviderFactoryContext(
|
||||
context.RouteContext.HttpContext,
|
||||
context.RouteContext.RouteData.Values);
|
||||
var valueProvider = valueProviderFactory.GetValueProvider(factoryContext);
|
||||
|
||||
foreach (var group in candidates.GroupBy(c => c.Parameters.Count).OrderByDescending(g => g.Key))
|
||||
{
|
||||
var foundMatch = false;
|
||||
foreach (var candidate in group)
|
||||
{
|
||||
var allFound = true;
|
||||
foreach (var parameter in candidate.Parameters)
|
||||
{
|
||||
if (!(valueProvider.ContainsPrefixAsync(parameter.ParameterBindingInfo.Prefix).Result))
|
||||
{
|
||||
if (candidate.Action.Action == context.CurrentCandidate.Action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
allFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allFound)
|
||||
{
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundMatch)
|
||||
{
|
||||
return group.Any(c => c.Action.Action == context.CurrentCandidate.Action);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ParameterDescriptor> GetOverloadableParameters(ActionDescriptor action)
|
||||
{
|
||||
if (action.Parameters == null)
|
||||
{
|
||||
return new List<ParameterDescriptor>();
|
||||
}
|
||||
|
||||
return action.Parameters.Where(
|
||||
p =>
|
||||
p.ParameterBindingInfo != null &&
|
||||
!p.IsOptional &&
|
||||
ValueProviderResult.CanConvertFromString(p.ParameterBindingInfo.ParameterType))
|
||||
.ToList();
|
||||
return new OverloadActionConstraint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"dependencies": {
|
||||
"Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Mvc": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ApplicationModel
|
||||
{
|
||||
|
|
@ -19,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModel
|
|||
{
|
||||
Action = other.Action;
|
||||
Attributes = new List<object>(other.Attributes);
|
||||
BinderMarker = other.BinderMarker;
|
||||
IsOptional = other.IsOptional;
|
||||
ParameterInfo = other.ParameterInfo;
|
||||
ParameterName = other.ParameterName;
|
||||
|
|
@ -28,6 +30,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModel
|
|||
|
||||
public List<object> Attributes { get; private set; }
|
||||
|
||||
public IBinderMarker BinderMarker { get; set; }
|
||||
|
||||
public bool IsOptional { get; set; }
|
||||
|
||||
public ParameterInfo ParameterInfo { get; private set; }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
var actionBindingContext = await _bindingContextProvider.GetActionBindingContextAsync(actionContext);
|
||||
var metadataProvider = actionBindingContext.MetadataProvider;
|
||||
var parameters = actionContext.ActionDescriptor.Parameters;
|
||||
|
||||
var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor;
|
||||
if (actionDescriptor == null)
|
||||
{
|
||||
|
|
@ -37,33 +37,49 @@ namespace Microsoft.AspNet.Mvc
|
|||
nameof(actionContext));
|
||||
}
|
||||
|
||||
var actionMethodInfo = actionDescriptor.MethodInfo;
|
||||
var parameterMetadatas = metadataProvider.GetMetadataForParameters(actionMethodInfo);
|
||||
var parameterMetadata = new List<ModelMetadata>();
|
||||
foreach (var parameter in actionDescriptor.Parameters)
|
||||
{
|
||||
var metadata = metadataProvider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: actionDescriptor.MethodInfo,
|
||||
parameterName: parameter.Name,
|
||||
binderMarker: parameter.BinderMarker);
|
||||
|
||||
var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
await PopulateActionArgumentsAsync(parameterMetadatas, actionBindingContext, actionArguments);
|
||||
return actionArguments;
|
||||
}
|
||||
if (metadata != null)
|
||||
{
|
||||
parameterMetadata.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PopulateActionArgumentsAsync(IEnumerable<ModelMetadata> modelMetadatas,
|
||||
ActionBindingContext actionBindingContext,
|
||||
IDictionary<string, object> invocationInfo)
|
||||
{
|
||||
var bodyBoundParameterCount = modelMetadatas.Count(
|
||||
modelMetadata => modelMetadata.Marker is IBodyBinderMarker);
|
||||
var bodyBoundParameterCount = parameterMetadata.Count(
|
||||
modelMetadata => modelMetadata.Marker is IBodyBinderMarker);
|
||||
if (bodyBoundParameterCount > 1)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.MultipleBodyParametersAreNotAllowed);
|
||||
}
|
||||
|
||||
foreach (var modelMetadata in modelMetadatas)
|
||||
var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
foreach (var parameter in parameterMetadata)
|
||||
{
|
||||
var modelBindingContext = GetModelBindingContext(modelMetadata, actionBindingContext);
|
||||
await PopulateArgumentAsync(actionBindingContext, actionArguments, parameter);
|
||||
}
|
||||
|
||||
if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
|
||||
{
|
||||
invocationInfo[modelMetadata.PropertyName] = modelBindingContext.Model;
|
||||
}
|
||||
return actionArguments;
|
||||
}
|
||||
|
||||
private async Task PopulateArgumentAsync(
|
||||
ActionBindingContext actionBindingContext,
|
||||
IDictionary<string, object> arguments,
|
||||
ModelMetadata modelMetadata)
|
||||
{
|
||||
|
||||
var parameterType = modelMetadata.ModelType;
|
||||
var modelBindingContext = GetModelBindingContext(modelMetadata, actionBindingContext);
|
||||
|
||||
if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
|
||||
{
|
||||
arguments[modelMetadata.PropertyName] = modelBindingContext.Model;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
using Microsoft.AspNet.Mvc.Core;
|
||||
using Microsoft.AspNet.Mvc.Description;
|
||||
using Microsoft.AspNet.Mvc.Filters;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Routing;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -191,6 +191,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
var attributes = parameterInfo.GetCustomAttributes(inherit: true).OfType<object>().ToList();
|
||||
parameterModel.Attributes.AddRange(attributes);
|
||||
|
||||
parameterModel.BinderMarker = attributes.OfType<IBinderMarker>().FirstOrDefault();
|
||||
|
||||
parameterModel.ParameterName = parameterInfo.Name;
|
||||
parameterModel.IsOptional = parameterInfo.HasDefaultValue;
|
||||
|
||||
|
|
@ -508,8 +510,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
var parameterDescriptor = new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = parameter.BinderMarker,
|
||||
IsOptional = parameter.IsOptional,
|
||||
Name = parameter.ParameterName,
|
||||
IsOptional = parameter.IsOptional
|
||||
};
|
||||
|
||||
var isFromBody = parameter.Attributes.OfType<FromBodyAttribute>().Any();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -16,6 +17,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
|
||||
public ParameterBindingInfo ParameterBindingInfo { get; set; }
|
||||
|
||||
public IBinderMarker BinderMarker { get; set; }
|
||||
|
||||
public BodyParameterInfo BodyParameterInfo { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,24 +46,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
return CreateMetadataFromPrototype(prototype, modelAccessor);
|
||||
}
|
||||
|
||||
public IEnumerable<ModelMetadata> GetMetadataForParameters([NotNull] MethodInfo methodInfo)
|
||||
{
|
||||
var parameters = methodInfo.GetParameters();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
// Name can be null if the methodinfo represents an open delegate.
|
||||
if (!string.IsNullOrEmpty(parameter.Name))
|
||||
{
|
||||
yield return GetMetadataForParameterCore(
|
||||
modelAccessor: null, parameterName: parameter.Name, parameter: parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ModelMetadata GetMetadataForParameter(
|
||||
Func<object> modelAccessor,
|
||||
[NotNull] MethodInfo methodInfo,
|
||||
[NotNull] string parameterName)
|
||||
[NotNull] string parameterName,
|
||||
IBinderMarker binderMarker)
|
||||
{
|
||||
var parameter = methodInfo.GetParameters().FirstOrDefault(
|
||||
param => StringComparer.Ordinal.Equals(param.Name, parameterName));
|
||||
|
|
@ -73,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
throw new ArgumentException(message, nameof(parameterName));
|
||||
}
|
||||
|
||||
return GetMetadataForParameterCore(modelAccessor, parameterName, parameter);
|
||||
return GetMetadataForParameterCore(modelAccessor, parameterName, parameter, binderMarker);
|
||||
}
|
||||
|
||||
// Override for creating the prototype metadata (without the accessor)
|
||||
|
|
@ -87,12 +74,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
Func<object> modelAccessor);
|
||||
private ModelMetadata GetMetadataForParameterCore(Func<object> modelAccessor,
|
||||
string parameterName,
|
||||
ParameterInfo parameter)
|
||||
ParameterInfo parameter,
|
||||
IBinderMarker binderMarker)
|
||||
{
|
||||
var parameterInfo =
|
||||
CreateParameterInfo(parameter.ParameterType,
|
||||
parameter.GetCustomAttributes(),
|
||||
parameterName);
|
||||
parameterName,
|
||||
binderMarker);
|
||||
|
||||
var typeInfo = GetTypeInformation(parameter.ParameterType);
|
||||
UpdateMetadataWithTypeInfo(parameterInfo.Prototype, typeInfo);
|
||||
|
|
@ -244,13 +233,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
};
|
||||
}
|
||||
|
||||
private ParameterInformation CreateParameterInfo(Type parameterType, IEnumerable<Attribute> attributes, string parameterName)
|
||||
private ParameterInformation CreateParameterInfo(
|
||||
Type parameterType,
|
||||
IEnumerable<Attribute> attributes,
|
||||
string parameterName,
|
||||
IBinderMarker binderMarker)
|
||||
{
|
||||
var metadataProtoType = CreateMetadataPrototype(attributes: attributes,
|
||||
containerType: null,
|
||||
modelType: parameterType,
|
||||
propertyName: parameterName);
|
||||
|
||||
if (binderMarker != null)
|
||||
{
|
||||
metadataProtoType.Marker = binderMarker;
|
||||
}
|
||||
|
||||
var nameProvider = binderMarker as IModelNameProvider;
|
||||
if (nameProvider != null && nameProvider.Name != null)
|
||||
{
|
||||
metadataProtoType.ModelName = nameProvider.Name;
|
||||
}
|
||||
|
||||
return new ParameterInformation
|
||||
{
|
||||
Prototype = metadataProtoType
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
ModelMetadata GetMetadataForType(Func<object> modelAccessor, [NotNull] Type modelType);
|
||||
|
||||
IEnumerable<ModelMetadata> GetMetadataForParameters([NotNull] MethodInfo methodInfo);
|
||||
|
||||
ModelMetadata GetMetadataForParameter(Func<object> modelAccessor, [NotNull] MethodInfo methodInfo, [NotNull] string parameterName);
|
||||
ModelMetadata GetMetadataForParameter(
|
||||
Func<object> modelAccessor,
|
||||
[NotNull] MethodInfo methodInfo,
|
||||
[NotNull] string parameterName,
|
||||
IBinderMarker binderMarker);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace System.Web.Http
|
|||
{
|
||||
[UseWebApiRoutes]
|
||||
[UseWebApiActionConventions]
|
||||
[UseWebApiParameterConventions]
|
||||
[UseWebApiOverloading]
|
||||
public abstract class ApiController : IDisposable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.WebApiCompatShim
|
||||
{
|
||||
public interface IUseWebApiParameterConventions
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class UseWebApiParameterConventionsAttribute : Attribute, IUseWebApiParameterConventions
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.Linq;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class WebApiParameterConventionsGlobalModelConvention : IGlobalModelConvention
|
||||
{
|
||||
public void Apply(GlobalModel model)
|
||||
{
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
if (IsConventionApplicable(controller))
|
||||
{
|
||||
Apply(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConventionApplicable(ControllerModel controller)
|
||||
{
|
||||
return controller.Attributes.OfType<IUseWebApiParameterConventions>().Any();
|
||||
}
|
||||
|
||||
private void Apply(ControllerModel model)
|
||||
{
|
||||
foreach (var action in model.Actions)
|
||||
{
|
||||
foreach (var parameter in action.Parameters)
|
||||
{
|
||||
if (parameter.BinderMarker != null)
|
||||
{
|
||||
// This has a binding behavior configured, just leave it alone.
|
||||
}
|
||||
else if (ValueProviderResult.CanConvertFromString(parameter.ParameterInfo.ParameterType))
|
||||
{
|
||||
// Simple types are by-default from the URI.
|
||||
parameter.BinderMarker = new FromUriAttribute();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Complex types are by-default from the body.
|
||||
parameter.BinderMarker = new FromBodyAttribute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +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 Microsoft.AspNet.Mvc.ApplicationModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class FromUriAttribute : Attribute, IParameterModelConvention
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public void Apply(ParameterModel model)
|
||||
{
|
||||
if (Name != null)
|
||||
{
|
||||
model.ParameterName = Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
foreach (var parameter in candidate.Parameters)
|
||||
{
|
||||
if (!requestKeys.Contains(parameter.ParameterBindingInfo.Prefix))
|
||||
if (!requestKeys.Contains(parameter.Prefix))
|
||||
{
|
||||
if (candidate.Action.Action == context.CurrentCandidate.Action)
|
||||
{
|
||||
|
|
@ -65,7 +65,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
return false;
|
||||
}
|
||||
|
||||
private List<ParameterDescriptor> GetOverloadableParameters(ActionSelectorCandidate candidate)
|
||||
private List<OverloadedParameter> GetOverloadableParameters(ActionSelectorCandidate candidate)
|
||||
{
|
||||
if (candidate.Action.Parameters == null)
|
||||
{
|
||||
|
|
@ -86,13 +86,26 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
return null;
|
||||
}
|
||||
|
||||
// We only consider parameters that are bound from the URL.
|
||||
return candidate.Action.Parameters.Where(
|
||||
p =>
|
||||
p.ParameterBindingInfo != null &&
|
||||
!p.IsOptional &&
|
||||
ValueProviderResult.CanConvertFromString(p.ParameterBindingInfo.ParameterType))
|
||||
.ToList();
|
||||
var parameters = new List<OverloadedParameter>();
|
||||
|
||||
foreach (var parameter in candidate.Action.Parameters)
|
||||
{
|
||||
// We only consider parameters that are bound from the URL.
|
||||
if ((parameter.BinderMarker is IRouteDataMarker || parameter.BinderMarker is IQueryBinderMarker) &&
|
||||
!parameter.IsOptional &&
|
||||
ValueProviderResult.CanConvertFromString(parameter.ParameterBindingInfo.ParameterType))
|
||||
{
|
||||
var prefix = (parameter.BinderMarker as IModelNameProvider).Name ?? parameter.Name;
|
||||
|
||||
parameters.Add(new OverloadedParameter()
|
||||
{
|
||||
ParameterDescriptor = parameter,
|
||||
Prefix = prefix,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static ISet<string> GetCombinedKeys(RouteContext routeContext)
|
||||
|
|
@ -121,5 +134,12 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
|
||||
return keys;
|
||||
}
|
||||
|
||||
private class OverloadedParameter
|
||||
{
|
||||
public ParameterDescriptor ParameterDescriptor { get; set; }
|
||||
|
||||
public string Prefix { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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 Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute that specifies that the value can be bound from the query string or route data.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class FromUriAttribute : Attribute, IQueryBinderMarker, IRouteDataMarker, IModelNameProvider
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// 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 Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute that specifies that the value can be bound by a model binder.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
|
||||
public class ModelBinderAttribute : Attribute, IBinderMarker
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
// Add webapi behaviors to controllers with the appropriate attributes
|
||||
options.ApplicationModelConventions.Add(new WebApiActionConventionsGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiParameterConventionsGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiOverloadingGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiRoutesGlobalModelConvention(area: DefaultAreaName));
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"dependencies": {
|
||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
||||
"Microsoft.AspNet.Mvc.ModelBinding": "6.0.0-*",
|
||||
"Microsoft.AspNet.WebApi.Client": "5.2.2"
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModel
|
|||
|
||||
parameter.Action = new ActionModel(typeof(TestController).GetMethod("Edit"));
|
||||
parameter.Attributes.Add(new FromBodyAttribute());
|
||||
parameter.BinderMarker = new FromBodyAttribute();
|
||||
parameter.IsOptional = true;
|
||||
parameter.ParameterName = "id";
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "foo");
|
||||
parameterName: "foo",
|
||||
binderMarker: null);
|
||||
|
||||
|
||||
var actionBindingContext = new ActionBindingContext(actionContext,
|
||||
|
|
@ -93,7 +94,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
|||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var modelMetadata = metadataProvider.GetMetadataForParameter(modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "foo1");
|
||||
parameterName: "foo1",
|
||||
binderMarker: null);
|
||||
|
||||
|
||||
var actionBindingContext = new ActionBindingContext(actionContext,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// 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.
|
||||
|
||||
#if ASPNET50
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
|
@ -377,5 +376,4 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(HttpStatusCode.PaymentRequired, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.TestHost;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||
{
|
||||
public class WebApiCompatShimParameterBindingTest
|
||||
{
|
||||
private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(WebApiCompatShimWebSite));
|
||||
private readonly Action<IApplicationBuilder> _app = new WebApiCompatShimWebSite.Startup().Configure;
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://localhost/api/Blog/Employees/PostByIdDefault/5")]
|
||||
[InlineData("http://localhost/api/Blog/Employees/PostByIdDefault?id=5")]
|
||||
public async Task ApiController_SimpleParameter_Default_ReadsFromUrl(string url)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("5", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiController_SimpleParameter_Default_DoesNotReadFormData()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/api/Blog/Employees/PostByIdDefault";
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", "5" },
|
||||
});
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("-1", content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://localhost/api/Blog/Employees/PostByIdModelBinder/5")]
|
||||
[InlineData("http://localhost/api/Blog/Employees/PostByIdModelBinder?id=5")]
|
||||
public async Task ApiController_SimpleParameter_ModelBinder_ReadsFromUrl(string url)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("5", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiController_SimpleParameter_ModelBinder_ReadsFromFormData()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/api/Blog/Employees/PostByIdModelBinder";
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", "5" },
|
||||
});
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("5", content);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://localhost/api/Blog/Employees/PostByIdFromQuery/5", "-1")]
|
||||
[InlineData("http://localhost/api/Blog/Employees/PostByIdFromQuery?id=5", "5")]
|
||||
public async Task ApiController_SimpleParameter_FromQuery_ReadsFromQueryNotRouteData(string url, string expected)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(expected, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiController_SimpleParameter_FromQuery_DoesNotReadFormData()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/api/Blog/Employees/PostByIdFromQuery";
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>()
|
||||
{
|
||||
{ "id", "5" },
|
||||
});
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("-1", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiController_ComplexParameter_Default_ReadsFromBody()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/api/Blog/Employees/PutEmployeeDefault";
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, url);
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(new
|
||||
{
|
||||
Id = 5,
|
||||
Name = "Test Employee",
|
||||
}));
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("{\"Id\":5,\"Name\":\"Test Employee\"}", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiController_ComplexParameter_ModelBinder_ReadsFormAndUrl()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/api/Blog/Employees/PutEmployeeModelBinder/5";
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, url);
|
||||
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>()
|
||||
{
|
||||
{ "name", "Test Employee" },
|
||||
});
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("{\"Id\":5,\"Name\":\"Test Employee\"}", content);
|
||||
}
|
||||
|
||||
// name is read from the url - and the rest from the body (formatters)
|
||||
[Fact]
|
||||
public async Task ApiController_TwoParameters_DefaultSources()
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var url = "http://localhost/api/Blog/Employees/PutEmployeeBothDefault?name=Name_Override";
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, url);
|
||||
request.Content = new StringContent(JsonConvert.SerializeObject(new
|
||||
{
|
||||
Id = 5,
|
||||
Name = "Test Employee",
|
||||
}));
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("{\"Id\":5,\"Name\":\"Name_Override\"}", content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -927,7 +927,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private static ModelMetadata GetMetadataForParameter(MethodInfo methodInfo, string parameterName)
|
||||
{
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
return metadataProvider.GetMetadataForParameter(null, methodInfo, parameterName);
|
||||
return metadataProvider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: parameterName,
|
||||
binderMarker: null);
|
||||
}
|
||||
|
||||
private class Person
|
||||
|
|
|
|||
|
|
@ -63,7 +63,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
"Property3", "Property4", "IncludedAndExcludedExplicitly1", "ExcludedExplicitly1" };
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForParameter(null, methodInfo, "param");
|
||||
var metadata = provider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "param",
|
||||
binderMarker: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedIncludedPropertyNames.ToList(), metadata.IncludedProperties);
|
||||
|
|
@ -79,7 +83,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForParameter(null, methodInfo, "param");
|
||||
var metadata = provider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "param",
|
||||
binderMarker: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ParameterPrefix", metadata.ModelName);
|
||||
|
|
@ -108,7 +116,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
var provider = new DataAnnotationsModelMetadataProvider();
|
||||
|
||||
// Act
|
||||
var metadata = provider.GetMetadataForParameter(null, methodInfo, "param");
|
||||
var metadata = provider.GetMetadataForParameter(
|
||||
modelAccessor: null,
|
||||
methodInfo: methodInfo,
|
||||
parameterName: "param",
|
||||
binderMarker: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ParameterPrefix", metadata.ModelName);
|
||||
|
|
|
|||
|
|
@ -252,6 +252,87 @@ namespace System.Web.Http
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_Parameters_SimpleTypeFromUriByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.EmployeesController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.Where(ad => ad.Name == "Get")
|
||||
.ToArray();
|
||||
|
||||
Assert.NotEmpty(actions);
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var parameter = Assert.Single(action.Parameters);
|
||||
Assert.IsType<FromUriAttribute>(parameter.BinderMarker);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_Parameters_ComplexTypeFromBodyByDefault()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.EmployeesController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.Where(ad => ad.Name == "Put")
|
||||
.ToArray();
|
||||
|
||||
Assert.NotEmpty(actions);
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var parameter = Assert.Single(action.Parameters);
|
||||
Assert.IsType<FromBodyAttribute>(parameter.BinderMarker);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_Parameters_BinderMarker()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.EmployeesController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.Where(ad => ad.Name == "Post")
|
||||
.ToArray();
|
||||
|
||||
Assert.NotEmpty(actions);
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var parameter = Assert.Single(action.Parameters);
|
||||
Assert.IsType<ModelBinderAttribute>(parameter.BinderMarker);
|
||||
}
|
||||
}
|
||||
|
||||
private INestedProviderManager<ActionDescriptorProviderContext> CreateProvider()
|
||||
{
|
||||
var assemblyProvider = new Mock<IAssemblyProvider>();
|
||||
|
|
@ -346,5 +427,27 @@ namespace System.Web.Http.TestControllers
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeesController : ApiController
|
||||
{
|
||||
public IActionResult Get(int id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IActionResult Put(Employee employee)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public IActionResult Post([ModelBinder] Employee employee)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class Employee
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Http;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.PipelineCore;
|
||||
using Microsoft.AspNet.Routing;
|
||||
|
|
@ -20,6 +21,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
|
|
@ -49,11 +51,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
@ -68,7 +72,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17});
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
|
|
@ -83,11 +87,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
@ -117,11 +123,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
@ -151,11 +159,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
IsOptional = true,
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
|
|
@ -186,11 +196,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
@ -201,11 +213,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity_ordered",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity_ordered", typeof(int)),
|
||||
},
|
||||
|
|
@ -239,6 +253,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
|
|
@ -249,11 +264,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
@ -284,11 +301,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
IsOptional = true,
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
|
|
@ -300,11 +319,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
@ -335,11 +356,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
@ -350,11 +373,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "price",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("price", typeof(decimal)),
|
||||
},
|
||||
|
|
@ -388,6 +413,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
|
|
@ -398,11 +424,61 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var constraint = new OverloadActionConstraint();
|
||||
|
||||
var context = new ActionConstraintContext();
|
||||
context.Candidates = new List<ActionSelectorCandidate>()
|
||||
{
|
||||
new ActionSelectorCandidate(action1, new [] { constraint }),
|
||||
new ActionSelectorCandidate(action2, new IActionConstraint[] { }),
|
||||
};
|
||||
|
||||
context.CurrentCandidate = context.Candidates[0];
|
||||
context.RouteContext = CreateRouteContext("?quantity=5", new { id = 17 });
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(constraint.Accept(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Accept_AcceptsAction_WithFewerParameters_WhenOtherIsNotOverloaded_FromBodyAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var action1 = new ActionDescriptor();
|
||||
action1.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
};
|
||||
|
||||
var action2 = new ActionDescriptor();
|
||||
action2.Parameters = new List<ParameterDescriptor>()
|
||||
{
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromUriAttribute(),
|
||||
Name = "id",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("id", typeof(int)),
|
||||
},
|
||||
new ParameterDescriptor()
|
||||
{
|
||||
BinderMarker = new FromBodyAttribute(),
|
||||
Name = "quantity",
|
||||
ParameterBindingInfo = new ParameterBindingInfo("quantity", typeof(int)),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Builder
|
|||
{
|
||||
public static Configuration GetTestConfiguration(this IApplicationBuilder app)
|
||||
{
|
||||
var configurationProvider = app.ApplicationServices.GetRequiredService<ITestConfigurationProvider>();
|
||||
var configurationProvider = app.ApplicationServices.GetService<ITestConfigurationProvider>();
|
||||
var configuration = configurationProvider == null
|
||||
? new Configuration()
|
||||
: configurationProvider.Configuration;
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@
|
|||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>24b59501-5f37-4129-96e6-f02ec34c7e2c</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>55571</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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.Web.Http;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace WebApiCompatShimWebSite.Controllers.ParameterBinding
|
||||
{
|
||||
public class EmployeesController : ApiController
|
||||
{
|
||||
public IActionResult PostByIdDefault(int id = -1)
|
||||
{
|
||||
return Ok(id);
|
||||
}
|
||||
|
||||
public IActionResult PostByIdModelBinder([ModelBinder] int id = -1)
|
||||
{
|
||||
return Ok(id);
|
||||
}
|
||||
|
||||
public IActionResult PostByIdFromQuery([FromQuery] int id = -1)
|
||||
{
|
||||
return Ok(id);
|
||||
}
|
||||
|
||||
public IActionResult PutEmployeeDefault(Employee employee)
|
||||
{
|
||||
return Ok(employee);
|
||||
}
|
||||
|
||||
public IActionResult PutEmployeeModelBinder([ModelBinder] Employee employee)
|
||||
{
|
||||
return Ok(employee);
|
||||
}
|
||||
|
||||
public IActionResult PutEmployeeBothDefault(string name, Employee employee)
|
||||
{
|
||||
employee.Name = name;
|
||||
return Ok(employee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
public class Employee
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,5 +8,6 @@
|
|||
"frameworks": {
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": { }
|
||||
}
|
||||
},
|
||||
"webroot": "wwwroot"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue