Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace System.Web.Http.Controllers
{
public class ApiControllerActionInvoker : IHttpActionInvoker
{
public virtual Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
HttpActionDescriptor actionDescriptor = actionContext.ActionDescriptor;
HttpControllerContext controllerContext = actionContext.ControllerContext;
return TaskHelpers.RunSynchronously(() =>
{
return actionDescriptor.ExecuteAsync(controllerContext, actionContext.ActionArguments)
.Then(value => actionDescriptor.ResultConverter.Convert(controllerContext, value));
}, cancellationToken)
.Catch<HttpResponseMessage>(info =>
{
// Propagate anything which isn't HttpResponseException
HttpResponseException httpResponseException = info.Exception as HttpResponseException;
if (httpResponseException == null)
{
return info.Throw();
}
HttpResponseMessage response = httpResponseException.Response;
response.EnsureResponseHasRequest(actionContext.Request);
return info.Handled(response);
}, cancellationToken);
}
}
}

View File

@ -0,0 +1,414 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web.Http.Internal;
using System.Web.Http.Properties;
namespace System.Web.Http.Controllers
{
/// <summary>
/// Reflection based action selector.
/// We optimize for the case where we have an <see cref="ApiControllerActionSelector"/> instance per <see cref="HttpControllerDescriptor"/>
/// instance but can support cases where there are many <see cref="HttpControllerDescriptor"/> instances for one
/// <see cref="ApiControllerActionSelector"/> as well. In the latter case the lookup is slightly slower because it goes through
/// the <see cref="P:HttpControllerDescriptor.Properties"/> dictionary.
/// </summary>
public class ApiControllerActionSelector : IHttpActionSelector
{
private const string ActionRouteKey = "action";
private const string ControllerRouteKey = "controller";
private ActionSelectorCacheItem _fastCache;
private readonly object _cacheKey = new object();
public virtual HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
if (controllerContext == null)
{
throw Error.ArgumentNull("controllerContext");
}
ActionSelectorCacheItem internalSelector = GetInternalSelector(controllerContext.ControllerDescriptor);
return internalSelector.SelectAction(controllerContext);
}
public virtual ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor)
{
if (controllerDescriptor == null)
{
throw Error.ArgumentNull("controllerDescriptor");
}
ActionSelectorCacheItem internalSelector = GetInternalSelector(controllerDescriptor);
return internalSelector.GetActionMapping();
}
private ActionSelectorCacheItem GetInternalSelector(HttpControllerDescriptor controllerDescriptor)
{
// First check in the local fast cache and if not a match then look in the broader
// HttpControllerDescriptor.Properties cache
if (_fastCache == null)
{
ActionSelectorCacheItem selector = new ActionSelectorCacheItem(controllerDescriptor);
Interlocked.CompareExchange(ref _fastCache, selector, null);
return selector;
}
else if (_fastCache.HttpControllerDescriptor == controllerDescriptor)
{
// If the key matches and we already have the delegate for creating an instance then just execute it
return _fastCache;
}
else
{
// If the key doesn't match then lookup/create delegate in the HttpControllerDescriptor.Properties for
// that HttpControllerDescriptor instance
ActionSelectorCacheItem selector = (ActionSelectorCacheItem)controllerDescriptor.Properties.GetOrAdd(
_cacheKey,
_ => new ActionSelectorCacheItem(controllerDescriptor));
return selector;
}
}
// All caching is in a dedicated cache class, which may be optionally shared across selector instances.
// Make this a private nested class so that nobody else can conflict with our state.
// Cache is initialized during ctor on a single thread.
private class ActionSelectorCacheItem
{
private readonly HttpControllerDescriptor _controllerDescriptor;
private readonly ReflectedHttpActionDescriptor[] _actionDescriptors;
private readonly IDictionary<ReflectedHttpActionDescriptor, IEnumerable<string>> _actionParameterNames = new Dictionary<ReflectedHttpActionDescriptor, IEnumerable<string>>();
private readonly ILookup<string, ReflectedHttpActionDescriptor> _actionNameMapping;
// Selection commonly looks up an action by verb.
// Cache this mapping. These caches are completely optional and we still behave correctly if we cache miss.
// We can adjust the specific set we cache based on profiler information.
// Conceptually, this set of caches could be a HttpMethod --> ReflectedHttpActionDescriptor[].
// - Beware that HttpMethod has a very slow hash function (it does case-insensitive string hashing). So don't use Dict.
// - there are unbounded number of http methods, so make sure the cache doesn't grow indefinitely.
// - we can build the cache at startup and don't need to continually add to it.
private readonly HttpMethod[] _cacheListVerbKinds = new HttpMethod[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };
private readonly ReflectedHttpActionDescriptor[][] _cacheListVerbs;
public ActionSelectorCacheItem(HttpControllerDescriptor controllerDescriptor)
{
Contract.Assert(controllerDescriptor != null);
// Initialize the cache entirely in the ctor on a single thread.
_controllerDescriptor = controllerDescriptor;
MethodInfo[] allMethods = _controllerDescriptor.ControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
MethodInfo[] validMethods = Array.FindAll(allMethods, IsValidActionMethod);
_actionDescriptors = new ReflectedHttpActionDescriptor[validMethods.Length];
for (int i = 0; i < validMethods.Length; i++)
{
MethodInfo method = validMethods[i];
ReflectedHttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor(_controllerDescriptor, method);
_actionDescriptors[i] = actionDescriptor;
HttpActionBinding actionBinding = controllerDescriptor.ActionValueBinder.GetBinding(actionDescriptor);
// Build action parameter name mapping, only consider parameters that are simple types, do not have default values and come from URI
_actionParameterNames.Add(
actionDescriptor,
actionBinding.ParameterBindings
.Where(binding => TypeHelper.IsSimpleUnderlyingType(binding.Descriptor.ParameterType) && !binding.HasDefaultValue() && binding.WillReadUri())
.Select(binding => binding.Descriptor.Prefix ?? binding.Descriptor.ParameterName));
}
_actionNameMapping = _actionDescriptors.ToLookup(actionDesc => actionDesc.ActionName, StringComparer.OrdinalIgnoreCase);
// Bucket the action descriptors by common verbs.
int len = _cacheListVerbKinds.Length;
_cacheListVerbs = new ReflectedHttpActionDescriptor[len][];
for (int i = 0; i < len; i++)
{
_cacheListVerbs[i] = FindActionsForVerbWorker(_cacheListVerbKinds[i]);
}
}
public HttpControllerDescriptor HttpControllerDescriptor
{
get { return _controllerDescriptor; }
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing of response instance.")]
public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
string actionName;
bool useActionName = controllerContext.RouteData.Values.TryGetValue(ActionRouteKey, out actionName);
ReflectedHttpActionDescriptor[] actionsFoundByHttpMethods;
HttpMethod incomingMethod = controllerContext.Request.Method;
// First get an initial candidate list.
if (useActionName)
{
// We have an explicit {action} value, do traditional binding. Just lookup by actionName
ReflectedHttpActionDescriptor[] actionsFoundByName = _actionNameMapping[actionName].ToArray();
// Throws HttpResponseException with NotFound status because no action matches the Name
if (actionsFoundByName.Length == 0)
{
throw new HttpResponseException(controllerContext.Request.CreateResponse(
HttpStatusCode.NotFound,
Error.Format(SRResources.ApiControllerActionSelector_ActionNameNotFound, _controllerDescriptor.ControllerName, actionName)));
}
// This filters out any incompatible verbs from the incoming action list
actionsFoundByHttpMethods = actionsFoundByName.Where(actionDescriptor => actionDescriptor.SupportedHttpMethods.Contains(incomingMethod)).ToArray();
}
else
{
// No {action} parameter, infer it from the verb.
actionsFoundByHttpMethods = FindActionsForVerb(incomingMethod);
}
// Throws HttpResponseException with MethodNotAllowed status because no action matches the Http Method
if (actionsFoundByHttpMethods.Length == 0)
{
throw new HttpResponseException(controllerContext.Request.CreateResponse(
HttpStatusCode.MethodNotAllowed,
Error.Format(SRResources.ApiControllerActionSelector_HttpMethodNotSupported, incomingMethod)));
}
// If there are multiple candidates, then apply overload resolution logic.
if (actionsFoundByHttpMethods.Length > 1)
{
actionsFoundByHttpMethods = FindActionUsingRouteAndQueryParameters(controllerContext, actionsFoundByHttpMethods).ToArray();
}
List<ReflectedHttpActionDescriptor> selectedActions = RunSelectionFilters(controllerContext, actionsFoundByHttpMethods);
actionsFoundByHttpMethods = null;
switch (selectedActions.Count)
{
case 0:
// Throws HttpResponseException with NotFound status because no action matches the request
throw new HttpResponseException(controllerContext.Request.CreateResponse(
HttpStatusCode.NotFound,
Error.Format(SRResources.ApiControllerActionSelector_ActionNotFound, _controllerDescriptor.ControllerName)));
case 1:
return selectedActions[0];
default:
// Throws HttpResponseException with InternalServerError status because multiple action matches the request
string ambiguityList = CreateAmbiguousMatchList(selectedActions);
throw new HttpResponseException(controllerContext.Request.CreateResponse(
HttpStatusCode.InternalServerError,
Error.Format(SRResources.ApiControllerActionSelector_AmbiguousMatch, ambiguityList)));
}
}
public ILookup<string, HttpActionDescriptor> GetActionMapping()
{
return new LookupAdapter() { Source = _actionNameMapping };
}
private IEnumerable<ReflectedHttpActionDescriptor> FindActionUsingRouteAndQueryParameters(HttpControllerContext controllerContext, IEnumerable<ReflectedHttpActionDescriptor> actionsFound)
{
// TODO, DevDiv 320655, improve performance of this method.
IDictionary<string, object> routeValues = controllerContext.RouteData.Values;
IEnumerable<string> routeParameterNames = routeValues.Select(route => route.Key)
.Where(key =>
!String.Equals(key, ControllerRouteKey, StringComparison.OrdinalIgnoreCase) &&
!String.Equals(key, ActionRouteKey, StringComparison.OrdinalIgnoreCase));
IEnumerable<string> queryParameterNames = controllerContext.Request.RequestUri.ParseQueryString().AllKeys;
bool hasRouteParameters = routeParameterNames.Any();
bool hasQueryParameters = queryParameterNames.Any();
if (hasRouteParameters || hasQueryParameters)
{
// refine the results based on route parameters to make sure that route parameters take precedence over query parameters
if (hasRouteParameters && hasQueryParameters)
{
// route parameters is a subset of action parameters
actionsFound = actionsFound.Where(descriptor => !routeParameterNames.Except(_actionParameterNames[descriptor], StringComparer.OrdinalIgnoreCase).Any());
}
// further refine the results making sure that action parameters is a subset of route parameters and query parameters
if (actionsFound.Count() > 1)
{
IEnumerable<string> combinedParameterNames = queryParameterNames.Union(routeParameterNames);
// action parameters is a subset of route parameters and query parameters
actionsFound = actionsFound.Where(descriptor => !_actionParameterNames[descriptor].Except(combinedParameterNames, StringComparer.OrdinalIgnoreCase).Any());
// select the results with the longest parameter match
if (actionsFound.Count() > 1)
{
actionsFound = actionsFound
.GroupBy(descriptor => _actionParameterNames[descriptor].Count())
.OrderByDescending(g => g.Key)
.First();
}
}
}
else
{
// return actions with no parameters
actionsFound = actionsFound.Where(descriptor => !_actionParameterNames[descriptor].Any());
}
return actionsFound;
}
private static List<ReflectedHttpActionDescriptor> RunSelectionFilters(HttpControllerContext controllerContext, IEnumerable<HttpActionDescriptor> descriptorsFound)
{
// remove all methods which are opting out of this request
// to opt out, at least one attribute defined on the method must return false
List<ReflectedHttpActionDescriptor> matchesWithSelectionAttributes = null;
List<ReflectedHttpActionDescriptor> matchesWithoutSelectionAttributes = new List<ReflectedHttpActionDescriptor>();
foreach (ReflectedHttpActionDescriptor actionDescriptor in descriptorsFound)
{
IActionMethodSelector[] attrs = actionDescriptor.CacheAttrsIActionMethodSelector;
if (attrs.Length == 0)
{
matchesWithoutSelectionAttributes.Add(actionDescriptor);
}
else
{
bool match = Array.TrueForAll(attrs, selector => selector.IsValidForRequest(controllerContext, actionDescriptor.MethodInfo));
if (match)
{
if (matchesWithSelectionAttributes == null)
{
matchesWithSelectionAttributes = new List<ReflectedHttpActionDescriptor>();
}
matchesWithSelectionAttributes.Add(actionDescriptor);
}
}
}
// if a matching action method had a selection attribute, consider it more specific than a matching action method
// without a selection attribute
if ((matchesWithSelectionAttributes != null) && (matchesWithSelectionAttributes.Count > 0))
{
return matchesWithSelectionAttributes;
}
else
{
return matchesWithoutSelectionAttributes;
}
}
// This is called when we don't specify an Action name
// Get list of actions that match a given verb. This can match by name or IActionHttpMethodSelecto
private ReflectedHttpActionDescriptor[] FindActionsForVerb(HttpMethod verb)
{
// Check cache for common verbs.
for (int i = 0; i < _cacheListVerbKinds.Length; i++)
{
// verb selection on common verbs is normalized to have object reference identity.
// This is significantly more efficient than comparing the verbs based on strings.
if (Object.ReferenceEquals(verb, _cacheListVerbKinds[i]))
{
return _cacheListVerbs[i];
}
}
// General case for any verbs.
return FindActionsForVerbWorker(verb);
}
// This is called when we don't specify an Action name
// Get list of actions that match a given verb. This can match by name or IActionHttpMethodSelector.
// Since this list is fixed for a given verb type, it can be pre-computed and cached.
// This function should not do caching. It's the helper that builds the caches.
private ReflectedHttpActionDescriptor[] FindActionsForVerbWorker(HttpMethod verb)
{
List<ReflectedHttpActionDescriptor> listMethods = new List<ReflectedHttpActionDescriptor>();
foreach (ReflectedHttpActionDescriptor descriptor in _actionDescriptors)
{
if (descriptor.SupportedHttpMethods.Contains(verb))
{
listMethods.Add(descriptor);
}
}
return listMethods.ToArray();
}
private static string CreateAmbiguousMatchList(IEnumerable<HttpActionDescriptor> ambiguousDescriptors)
{
StringBuilder exceptionMessageBuilder = new StringBuilder();
foreach (ReflectedHttpActionDescriptor descriptor in ambiguousDescriptors)
{
MethodInfo methodInfo = descriptor.MethodInfo;
exceptionMessageBuilder.AppendLine();
exceptionMessageBuilder.Append(Error.Format(
SRResources.ActionSelector_AmbiguousMatchType,
methodInfo, methodInfo.DeclaringType.FullName));
}
return exceptionMessageBuilder.ToString();
}
private static bool IsValidActionMethod(MethodInfo methodInfo)
{
if (methodInfo.IsSpecialName)
{
// not a normal method, e.g. a constructor or an event
return false;
}
if (methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(TypeHelper.ApiControllerType))
{
// is a method on Object, IHttpController, ApiController
return false;
}
return true;
}
}
// We need to expose ILookup<string, HttpActionDescriptor>, but we have a ILookup<string, ReflectedHttpActionDescriptor>
// ReflectedHttpActionDescriptor derives from HttpActionDescriptor, but ILookup doesn't support Covariance.
// Adapter class since ILookup doesn't support Covariance.
// Fortunately, IGrouping, IEnumerable support Covariance, so it's easy to forward.
private class LookupAdapter : ILookup<string, HttpActionDescriptor>
{
public ILookup<string, ReflectedHttpActionDescriptor> Source;
public int Count
{
get { return Source.Count; }
}
public IEnumerable<HttpActionDescriptor> this[string key]
{
get { return Source[key]; }
}
public bool Contains(string key)
{
return Source.Contains(key);
}
public IEnumerator<IGrouping<string, HttpActionDescriptor>> GetEnumerator()
{
return Source.GetEnumerator();
}
Collections.IEnumerator Collections.IEnumerable.GetEnumerator()
{
return Source.GetEnumerator();
}
}
}
}

View File

@ -0,0 +1,107 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Metadata;
namespace System.Web.Http.Controllers
{
/// <summary>
/// This describes *how* the binding will happen. Does not actually bind.
/// This is static for a given action descriptor and can be reused across requests.
/// This may be a nice thing to log. Or set a breakpoint after we create and preview what's about to happen.
/// In theory, this could be precompiled for each Action descriptor.
/// </summary>
public class HttpActionBinding
{
private HttpActionDescriptor _actionDescriptor;
private HttpParameterBinding[] _parameterBindings;
private ModelMetadataProvider _metadataProvider;
public HttpActionBinding()
{
}
public HttpActionBinding(HttpActionDescriptor actionDescriptor, HttpParameterBinding[] bindings)
{
ActionDescriptor = actionDescriptor;
ParameterBindings = bindings;
}
/// <summary>
/// Back pointer to the action this binding is for.
/// This can also provide the Type[], string[] names for the parameters.
/// </summary>
public HttpActionDescriptor ActionDescriptor
{
get
{
return _actionDescriptor;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_actionDescriptor = value;
}
}
/// <summary>
/// Specifies synchronous bindings for each parameter.This is a parallel array to the ActionDescriptor's parameter array.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Want an array")]
public HttpParameterBinding[] ParameterBindings
{
get
{
return _parameterBindings;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_parameterBindings = value;
}
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing of response instance.")]
public virtual Task ExecuteBindingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (_parameterBindings.Length == 0)
{
return TaskHelpers.Completed();
}
// First, make sure the actionBinding is valid before trying to execute it. This keeps us in a known state in case of errors.
foreach (HttpParameterBinding parameterBinder in ParameterBindings)
{
if (!parameterBinder.IsValid)
{
// Error code here is 500 because the WebService developer's action signature is bad.
return TaskHelpers.FromError(new HttpResponseException(actionContext.Request.CreateResponse(
HttpStatusCode.InternalServerError, parameterBinder.ErrorMessage)));
}
}
if (_metadataProvider == null)
{
HttpConfiguration config = actionContext.ControllerContext.Configuration;
_metadataProvider = config.Services.GetModelMetadataProvider();
}
// Execute all the binders.
IEnumerable<Task> tasks = from parameterBinder in ParameterBindings select parameterBinder.ExecuteBindingAsync(_metadataProvider, actionContext, cancellationToken);
return TaskHelpers.Iterate(tasks, cancellationToken);
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http.ModelBinding;
namespace System.Web.Http.Controllers
{
/// <summary>
/// Contains information for the executing action.
/// </summary>
public class HttpActionContext
{
private readonly ModelStateDictionary _modelState = new ModelStateDictionary();
private readonly Dictionary<string, object> _operationArguments = new Dictionary<string, object>();
private HttpActionDescriptor _actionDescriptor;
private HttpControllerContext _controllerContext;
public HttpActionContext(HttpControllerContext controllerContext, HttpActionDescriptor actionDescriptor)
{
if (controllerContext == null)
{
throw Error.ArgumentNull("controllerContext");
}
if (actionDescriptor == null)
{
throw Error.ArgumentNull("actionDescriptor");
}
_controllerContext = controllerContext;
_actionDescriptor = actionDescriptor;
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpActionContext"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public HttpActionContext()
{
}
public HttpControllerContext ControllerContext
{
get { return _controllerContext; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_controllerContext = value;
}
}
public HttpActionDescriptor ActionDescriptor
{
get { return _actionDescriptor; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_actionDescriptor = value;
}
}
public ModelStateDictionary ModelState
{
get { return _modelState; }
}
public Dictionary<string, object> ActionArguments
{
get { return _operationArguments; }
}
public HttpResponseMessage Response { get; set; }
/// <summary>
/// Gets the current <see cref="HttpRequestMessage"/>.
/// </summary>
public HttpRequestMessage Request
{
get { return _controllerContext != null ? _controllerContext.Request : null; }
}
}
}

View File

@ -0,0 +1,124 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web.Http.Metadata;
using System.Web.Http.ModelBinding;
using System.Web.Http.Properties;
using System.Web.Http.Validation;
namespace System.Web.Http.Controllers
{
/// <summary>
/// Extension methods for <see cref="HttpActionContext"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class HttpActionContextExtensions
{
/// <summary>
/// Gets the <see cref="ModelMetadataProvider"/> instance for a given <see cref="HttpActionContext"/>.
/// </summary>
/// <param name="actionContext">The context.</param>
/// <returns>An <see cref="ModelMetadataProvider"/> instance.</returns>
public static ModelMetadataProvider GetMetadataProvider(this HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
return actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider();
}
/// <summary>
/// Gets the collection of registered <see cref="ModelValidatorProvider"/> instances.
/// </summary>
/// <param name="actionContext">The context.</param>
/// <returns>A collection of <see cref="ModelValidatorProvider"/> instances.</returns>
public static IEnumerable<ModelValidatorProvider> GetValidatorProviders(this HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
return actionContext.ControllerContext.Configuration.Services.GetModelValidatorProviders();
}
/// <summary>
/// Gets the collection of registered <see cref="ModelValidator"/> instances.
/// </summary>
/// <param name="actionContext">The context.</param>
/// <param name="metadata">The metadata.</param>
/// <returns>A collection of registered <see cref="ModelValidator"/> instances.</returns>
public static IEnumerable<ModelValidator> GetValidators(this HttpActionContext actionContext, ModelMetadata metadata)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
IEnumerable<ModelValidatorProvider> validatorProviders = GetValidatorProviders(actionContext);
return validatorProviders.SelectMany(provider => provider.GetValidators(metadata, validatorProviders));
}
/// <summary>
/// Gets the <see cref="ModelBindingContext"/> for this <see cref="HttpActionContext"/>.
/// </summary>
/// <param name="actionContext">The action context.</param>
/// <param name="bindingContext">The binding context.</param>
/// <param name="binder">When this method returns, the value associated with the specified binding context, if the context is found; otherwise, the default value for the type of the value parameter.</param>
/// <returns><c>true</c> if <see cref="ModelBindingContext"/> was present; otherwise <c>false</c>.</returns>
public static bool TryGetBinder(this HttpActionContext actionContext, ModelBindingContext bindingContext, out IModelBinder binder)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
if (bindingContext == null)
{
throw Error.ArgumentNull("bindingContext");
}
binder = null;
ModelBinderProvider providerFromAttr;
if (ModelBindingHelper.TryGetProviderFromAttributes(bindingContext.ModelType, out providerFromAttr))
{
binder = providerFromAttr.GetBinder(actionContext, bindingContext);
}
else
{
binder = actionContext.ControllerContext.Configuration.Services.GetModelBinderProviders()
.Select(p => p.GetBinder(actionContext, bindingContext))
.Where(b => b != null)
.FirstOrDefault();
}
return binder != null;
}
/// <summary>
/// Gets the <see cref="ModelBindingContext"/> for this <see cref="HttpActionContext"/>.
/// </summary>
/// <param name="actionContext">The execution context.</param>
/// <param name="bindingContext">The binding context.</param>
/// <returns>The <see cref="ModelBindingContext"/>.</returns>
public static IModelBinder GetBinder(this HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
IModelBinder binder;
if (TryGetBinder(actionContext, bindingContext, out binder))
{
return binder;
}
throw Error.InvalidOperation(SRResources.ModelBinderProviderCollection_BinderForTypeNotFound, bindingContext.ModelType);
}
}
}

View File

@ -0,0 +1,247 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Internal;
using System.Web.Http.Properties;
namespace System.Web.Http.Controllers
{
public abstract class HttpActionDescriptor
{
private readonly ConcurrentDictionary<object, object> _properties = new ConcurrentDictionary<object, object>();
private IActionResultConverter _converter;
private readonly Lazy<Collection<FilterInfo>> _filterPipeline;
private HttpConfiguration _configuration;
private HttpControllerDescriptor _controllerDescriptor;
private readonly Collection<HttpMethod> _supportedHttpMethods = new Collection<HttpMethod>();
private HttpActionBinding _actionBinding;
private static readonly ResponseMessageResultConverter _responseMessageResultConverter = new ResponseMessageResultConverter();
private static readonly VoidResultConverter _voidResultConverter = new VoidResultConverter();
protected HttpActionDescriptor()
{
_filterPipeline = new Lazy<Collection<FilterInfo>>(InitializeFilterPipeline);
}
protected HttpActionDescriptor(HttpControllerDescriptor controllerDescriptor)
: this()
{
if (controllerDescriptor == null)
{
throw Error.ArgumentNull("controllerDesriptor");
}
_controllerDescriptor = controllerDescriptor;
_configuration = _controllerDescriptor.Configuration;
}
public abstract string ActionName { get; }
public HttpConfiguration Configuration
{
get { return _configuration; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_configuration = value;
}
}
public virtual HttpActionBinding ActionBinding
{
get
{
if (_actionBinding == null)
{
IActionValueBinder actionValueBinder = _controllerDescriptor.ActionValueBinder;
HttpActionBinding actionBinding = actionValueBinder.GetBinding(this);
_actionBinding = actionBinding;
}
return _actionBinding;
}
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_actionBinding = value;
}
}
public HttpControllerDescriptor ControllerDescriptor
{
get { return _controllerDescriptor; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_controllerDescriptor = value;
}
}
/// <summary>
/// The return type of the method or <c>null</c> if the method does not return a value (e.g. a method returning
/// <c>void</c>).
/// </summary>
/// <remarks>
/// This property should describe the type of the value contained by the result of executing the action
/// via the <see cref="ExecuteAsync(HttpControllerContext, IDictionary{string, object})"/>.
/// </remarks>
public abstract Type ReturnType { get; }
/// <summary>
/// Gets the converter for correctly transforming the result of calling
/// <see cref="ExecuteAsync(HttpControllerContext, IDictionary{string, object})"/> into an instance of
/// <see cref="HttpResponseMessage"/>.
/// </summary>
/// <remarks>
/// The behavior of the returned converter should align with the action's declared <see cref="ReturnType"/>.
/// </remarks>
public virtual IActionResultConverter ResultConverter
{
get
{
// This initialization is not thread safe but that's fine since the converters do not have
// any interesting state. If 2 threads get 2 different instances of the same converter type
// we don't really care.
if (_converter == null)
{
_converter = GetResultConverter(ReturnType);
}
return _converter;
}
}
public virtual Collection<HttpMethod> SupportedHttpMethods
{
get { return _supportedHttpMethods; }
}
/// <summary>
/// Gets the properties associated with this instance.
/// </summary>
public ConcurrentDictionary<object, object> Properties
{
get { return _properties; }
}
public virtual Collection<T> GetCustomAttributes<T>() where T : class
{
return new Collection<T>();
}
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Filters can be built dynamically")]
public virtual Collection<IFilter> GetFilters()
{
return new Collection<IFilter>();
}
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Parameters can be built dynamically")]
public abstract Collection<HttpParameterDescriptor> GetParameters();
internal static IActionResultConverter GetResultConverter(Type type)
{
if (type != null && type.IsGenericParameter)
{
// This can happen if somebody declares an action method as:
// public T Get<T>() { }
throw Error.InvalidOperation(SRResources.HttpActionDescriptor_NoConverterForGenericParamterTypeExists, type);
}
if (type == null)
{
return _voidResultConverter;
}
else if (typeof(HttpResponseMessage).IsAssignableFrom(type))
{
return _responseMessageResultConverter;
}
else
{
Type valueConverterType = typeof(ValueResultConverter<>).MakeGenericType(type);
return TypeActivator.Create<IActionResultConverter>(valueConverterType).Invoke();
}
}
/// <summary>
/// Executes the described action and returns a <see cref="Task{T}"/> that once completed will
/// contain the return value of the action.
/// </summary>
/// <param name="controllerContext">The context.</param>
/// <param name="arguments">The arguments.</param>
/// <returns>A <see cref="Task{T}"/> that once completed will contain the return value of the action.</returns>
public abstract Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments);
/// <summary>
/// Returns the filters for the given configuration and action. The filter collection is ordered
/// according to the FilterScope (in order from least specific to most specific: First, Global, Controller, Action).
///
/// If a given filter disallows duplicates (AllowMultiple=False) then the most specific filter is maintained
/// and less specific filters get removed (e.g. if there is a Authorize filter with a Controller scope and another
/// one with an Action scope then the one with the Action scope will be maintained and the one with the Controller
/// scope will be discarded).
/// </summary>
/// <returns>A <see cref="Collection{T}"/> of all filters associated with this <see cref="HttpActionDescriptor"/>.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Filter pipeline can be built dynamically")]
public virtual Collection<FilterInfo> GetFilterPipeline()
{
return _filterPipeline.Value;
}
private Collection<FilterInfo> InitializeFilterPipeline()
{
IEnumerable<IFilterProvider> filterProviders = _configuration.Services.GetFilterProviders();
IEnumerable<FilterInfo> filters = filterProviders.SelectMany(fp => fp.GetFilters(_configuration, this)).OrderBy(f => f, FilterInfoComparer.Instance);
// Need to discard duplicate filters from the end, so that most specific ones get kept (Action scope) and
// less specific ones get removed (Global)
filters = RemoveDuplicates(filters.Reverse()).Reverse();
return new Collection<FilterInfo>(filters.ToList());
}
private static IEnumerable<FilterInfo> RemoveDuplicates(IEnumerable<FilterInfo> filters)
{
Contract.Assert(filters != null);
HashSet<Type> visitedTypes = new HashSet<Type>();
foreach (FilterInfo filter in filters)
{
object filterInstance = filter.Instance;
Type filterInstanceType = filterInstance.GetType();
if (!visitedTypes.Contains(filterInstanceType) || AllowMultiple(filterInstance))
{
yield return filter;
visitedTypes.Add(filterInstanceType);
}
}
}
private static bool AllowMultiple(object filterInstance)
{
IFilter filter = filterInstance as IFilter;
return filter == null || filter.AllowMultiple;
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Http.Dependencies;
using System.Web.Http.Dispatcher;
namespace System.Web.Http.Controllers
{
/// <summary>
/// Provides a mechanism for a <see cref="IHttpController"/> implementation to indicate
/// what kind of <see cref="IHttpControllerActivator"/>, <see cref="IHttpActionSelector"/>, <see cref="IActionValueBinder"/>
/// and <see cref="IHttpActionInvoker"/> to use for that controller. The types are
/// first looked up in the <see cref="IDependencyResolver"/> and if not found there
/// then created directly.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class HttpControllerConfigurationAttribute : Attribute
{
public Type HttpControllerActivator { get; set; }
public Type HttpActionSelector { get; set; }
public Type HttpActionInvoker { get; set; }
public Type ActionValueBinder { get; set; }
}
}

View File

@ -0,0 +1,149 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Net.Http;
using System.Web.Http.Routing;
namespace System.Web.Http.Controllers
{
/// <summary>
/// Contains information for a single HTTP operation.
/// </summary>
public class HttpControllerContext
{
private HttpConfiguration _configuration;
private IHttpRouteData _routeData;
private HttpRequestMessage _request;
private HttpControllerDescriptor _controllerDescriptor;
private IHttpController _controller;
private UrlHelper _urlHelper;
public HttpControllerContext(HttpConfiguration configuration, IHttpRouteData routeData, HttpRequestMessage request)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
if (routeData == null)
{
throw Error.ArgumentNull("routeData");
}
if (request == null)
{
throw Error.ArgumentNull("request");
}
_configuration = configuration;
_routeData = routeData;
_request = request;
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpControllerContext"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public HttpControllerContext()
{
}
public HttpConfiguration Configuration
{
get { return _configuration; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_configuration = value;
}
}
public HttpRequestMessage Request
{
get { return _request; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_request = value;
}
}
public IHttpRouteData RouteData
{
get { return _routeData; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_routeData = value;
}
}
/// <summary>
/// Gets or sets the controller descriptor.
/// </summary>
/// <value>
/// The controller descriptor.
/// </value>
public HttpControllerDescriptor ControllerDescriptor
{
get { return _controllerDescriptor; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_controllerDescriptor = value;
}
}
/// <summary>
/// Gets or sets the HTTP controller.
/// </summary>
/// <value>
/// The HTTP controller.
/// </value>
public IHttpController Controller
{
get { return _controller; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_controller = value;
}
}
/// <summary>
/// Returns an instance of a UrlHelper, which is used to generate URLs to other APIs.
/// </summary>
public UrlHelper Url
{
get
{
if (_urlHelper == null)
{
_urlHelper = new UrlHelper(this);
}
return _urlHelper;
}
set { _urlHelper = value; }
}
}
}

View File

@ -0,0 +1,283 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Dispatcher;
using System.Web.Http.Filters;
using System.Web.Http.Internal;
namespace System.Web.Http.Controllers
{
public class HttpControllerDescriptor
{
private readonly ConcurrentDictionary<object, object> _properties = new ConcurrentDictionary<object, object>();
private HttpConfiguration _configuration;
private string _controllerName;
private Type _controllerType;
private IHttpControllerActivator _controllerActivator;
private IHttpActionSelector _actionSelector;
private IHttpActionInvoker _actionInvoker;
private IActionValueBinder _actionValueBinder;
private object[] _attrCached;
public HttpControllerDescriptor(HttpConfiguration configuration, string controllerName, Type controllerType)
{
if (configuration == null)
{
throw Error.ArgumentNull("configuration");
}
if (controllerName == null)
{
throw Error.ArgumentNull("controllerName");
}
if (controllerType == null)
{
throw Error.ArgumentNull("controllerType");
}
_configuration = configuration;
_controllerName = controllerName;
_controllerType = controllerType;
Initialize();
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpControllerDescriptor"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public HttpControllerDescriptor()
{
}
/// <summary>
/// Gets the properties associated with this instance.
/// </summary>
public ConcurrentDictionary<object, object> Properties
{
get { return _properties; }
}
public HttpConfiguration Configuration
{
get { return _configuration; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_configuration = value;
}
}
public string ControllerName
{
get { return _controllerName; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_controllerName = value;
}
}
public Type ControllerType
{
get { return _controllerType; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_controllerType = value;
}
}
public IHttpControllerActivator HttpControllerActivator
{
get { return _controllerActivator; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_controllerActivator = value;
}
}
public IHttpActionSelector HttpActionSelector
{
get { return _actionSelector; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_actionSelector = value;
}
}
public IHttpActionInvoker HttpActionInvoker
{
get { return _actionInvoker; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_actionInvoker = value;
}
}
public IActionValueBinder ActionValueBinder
{
get { return _actionValueBinder; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_actionValueBinder = value;
}
}
/// <summary>
/// Creates a controller instance for the given <see cref="HttpRequestMessage"/>
/// </summary>
/// <param name="request">The request message</param>
/// <returns></returns>
public virtual IHttpController CreateController(HttpRequestMessage request)
{
if (request == null)
{
throw Error.ArgumentNull("request");
}
// Invoke the controller activator
IHttpController instance = HttpControllerActivator.Create(request, this, ControllerType);
return instance;
}
/// <summary>
/// Returns the collection of <see cref="IFilter">filters</see> associated with this descriptor's controller.
/// </summary>
/// <remarks>The default implementation calls <see cref="GetCustomAttributes{IFilter}()"/>.</remarks>
/// <returns>A collection of filters associated with this controller.</returns>
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Filters can be built dynamically")]
public virtual Collection<IFilter> GetFilters()
{
return GetCustomAttributes<IFilter>();
}
/// <summary>
/// Returns a collection of attributes that can be assigned to <typeparamref name="T"/> for this descriptor's controller.
/// </summary>
/// <remarks>The default implementation retrieves the matching set of attributes declared on <see cref="ControllerType"/>.</remarks>
/// <typeparam name="T">Used to filter the collection of attributes. Use a value of <see cref="Object"/> to retrieve all attributes.</typeparam>
/// <returns>A collection of attributes associated with this controller.</returns>
public virtual Collection<T> GetCustomAttributes<T>() where T : class
{
// Getting custom attributes via reflection is slow.
// But iterating over a object[] to pick out specific types is fast.
// Furthermore, many different services may call to ask for different attributes, so we have multiple callers.
// That means there's not a single cache for the callers, which means there's some value caching here.
if (_attrCached == null)
{
// Even in a race, we'll just ask for the custom attributes twice.
_attrCached = ControllerType.GetCustomAttributes(inherit: true);
}
return new Collection<T>(TypeHelper.OfType<T>(_attrCached));
}
private void Initialize()
{
// Look for attribute to provide specialized information for this controller type
HttpControllerConfigurationAttribute controllerConfig =
_controllerType.GetCustomAttributes<HttpControllerConfigurationAttribute>(inherit: true).FirstOrDefault();
// If we find attribute then first ask dependency resolver and if we get null then create it ourselves
if (controllerConfig != null)
{
if (controllerConfig.HttpControllerActivator != null)
{
_controllerActivator = GetService<IHttpControllerActivator>(_configuration, controllerConfig.HttpControllerActivator);
}
if (controllerConfig.HttpActionSelector != null)
{
_actionSelector = GetService<IHttpActionSelector>(_configuration, controllerConfig.HttpActionSelector);
}
if (controllerConfig.HttpActionInvoker != null)
{
_actionInvoker = GetService<IHttpActionInvoker>(_configuration, controllerConfig.HttpActionInvoker);
}
if (controllerConfig.ActionValueBinder != null)
{
_actionValueBinder = GetService<IActionValueBinder>(_configuration, controllerConfig.ActionValueBinder);
}
}
// For everything still null we fall back to the default service list.
if (_controllerActivator == null)
{
_controllerActivator = Configuration.Services.GetHttpControllerActivator();
}
if (_actionSelector == null)
{
_actionSelector = Configuration.Services.GetActionSelector();
}
if (_actionInvoker == null)
{
_actionInvoker = Configuration.Services.GetActionInvoker();
}
if (_actionValueBinder == null)
{
_actionValueBinder = Configuration.Services.GetActionValueBinder();
}
}
/// <summary>
/// Helper for looking up or activating <see cref="IHttpControllerActivator"/>, <see cref="IHttpActionSelector"/>,
/// and <see cref="IHttpActionInvoker"/>. Note that we here use the slow <see cref="M:Activator.CreateInstance"/>
/// as the instances live for the lifetime of the <see cref="HttpControllerDescriptor"/> instance itself so there is
/// little benefit in caching a delegate.
/// </summary>
/// <typeparam name="TBase">The type of the base.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="serviceType">Type of the service.</param>
/// <returns>A new instance.</returns>
private static TBase GetService<TBase>(HttpConfiguration configuration, Type serviceType) where TBase : class
{
Contract.Assert(configuration != null);
if (serviceType != null)
{
return (TBase)configuration.DependencyResolver.GetService(serviceType)
?? (TBase)Activator.CreateInstance(serviceType);
}
return null;
}
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Metadata;
namespace System.Web.Http.Controllers
{
/// <summary>
/// Describes how a parameter is bound. The binding should be static (based purely on the descriptor) and
/// can be shared across requests.
/// </summary>
public abstract class HttpParameterBinding
{
private readonly HttpParameterDescriptor _descriptor;
protected HttpParameterBinding(HttpParameterDescriptor descriptor)
{
if (descriptor == null)
{
throw Error.ArgumentNull("descriptor");
}
_descriptor = descriptor;
}
/// <summary>
/// True iff this binding owns the body. This is important since the body can be a stream that is only read once.
/// This lets us know who is trying to read the body, and enforce that there is only one reader.
/// </summary>
public virtual bool WillReadBody
{
get { return false; }
}
/// <summary>
/// True if the binding was successful and ExecuteBinding can be called.
/// False if there was an error determining this binding. This means a developer error somewhere, such as
/// configuration, parameter types, proper attributes, etc.
/// </summary>
public bool IsValid
{
get { return ErrorMessage == null; }
}
/// <summary>
/// Get an error message describing why this binding is invalid.
/// </summary>
public virtual string ErrorMessage
{
get { return null; }
}
public HttpParameterDescriptor Descriptor
{
get { return _descriptor; }
}
/// <summary>
/// Execute the binding for the given request.
/// On success, this will add the parameter to the actionContext.ActionArguments dictionary.
/// Caller ensures <see cref="IsValid"/> is true.
/// </summary>
/// <param name="metadataProvider">metadata provider to use for validation.</param>
/// <param name="actionContext">action context for the binding. This contains the parameter dictionary that will get populated.</param>
/// <param name="cancellationToken">Cancellation token for cancelling the binding operation. Or a binder can also bind a parameter to this.</param>
/// <returns>Task that is signaled when the binding is complete. For simple bindings from a URI, this should be signalled immediately.
/// For bindings that read the content body, this may do network IO.</returns>
public abstract Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web.Http.Internal;
using System.Web.Http.ModelBinding;
namespace System.Web.Http.Controllers
{
public abstract class HttpParameterDescriptor
{
private readonly ConcurrentDictionary<object, object> _properties = new ConcurrentDictionary<object, object>();
private ModelBinderAttribute _modelBinderAttribute;
private bool _searchedModelBinderAttribute;
private HttpConfiguration _configuration;
private HttpActionDescriptor _actionDescriptor;
protected HttpParameterDescriptor()
{
}
protected HttpParameterDescriptor(HttpActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
{
throw Error.ArgumentNull("actionDescriptor");
}
_actionDescriptor = actionDescriptor;
_configuration = _actionDescriptor.Configuration;
}
public HttpConfiguration Configuration
{
get { return _configuration; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_configuration = value;
}
}
public HttpActionDescriptor ActionDescriptor
{
get { return _actionDescriptor; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_actionDescriptor = value;
}
}
/// <summary>
/// Gets the properties associated with this instance.
/// </summary>
public ConcurrentDictionary<object, object> Properties
{
get { return _properties; }
}
public virtual object DefaultValue
{
get { return null; }
}
public abstract string ParameterName { get; }
public abstract Type ParameterType { get; }
public virtual string Prefix
{
get
{
ModelBinderAttribute attribute = ModelBinderAttribute;
return attribute != null && !String.IsNullOrEmpty(attribute.Name)
? attribute.Name
: null;
}
}
public virtual ModelBinderAttribute ModelBinderAttribute
{
get
{
if (_modelBinderAttribute == null)
{
if (!_searchedModelBinderAttribute)
{
_searchedModelBinderAttribute = true;
_modelBinderAttribute = FindModelBinderAttribute();
}
}
return _modelBinderAttribute;
}
set { _modelBinderAttribute = value; }
}
public virtual Collection<T> GetCustomAttributes<T>() where T : class
{
return new Collection<T>();
}
private ModelBinderAttribute FindModelBinderAttribute()
{
// Can be on parameter itself or on the parameter's type. Nearest wins.
return GetCustomAttributes<ModelBinderAttribute>().SingleOrDefault()
?? ParameterType.GetCustomAttributes<ModelBinderAttribute>(false).SingleOrDefault();
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.ObjectModel;
using System.Net.Http;
namespace System.Web.Http.Controllers
{
internal interface IActionHttpMethodProvider
{
Collection<HttpMethod> HttpMethods { get; }
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
namespace System.Web.Http.Controllers
{
internal interface IActionMethodSelector
{
bool IsValidForRequest(HttpControllerContext controllerContext, MethodInfo methodInfo);
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Net.Http;
namespace System.Web.Http.Controllers
{
/// <summary>
/// A contract for a conversion routine that can take the result of an action returned from
/// <see cref="HttpActionDescriptor.ExecuteAsync(HttpControllerContext, IDictionary{string, object})"/>
/// and convert it to an instance of <see cref="HttpResponseMessage"/>.
/// </summary>
public interface IActionResultConverter
{
HttpResponseMessage Convert(HttpControllerContext controllerContext, object actionResult);
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http.Controllers
{
public interface IActionValueBinder
{
HttpActionBinding GetBinding(HttpActionDescriptor actionDescriptor);
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace System.Web.Http.Controllers
{
public interface IHttpActionInvoker
{
Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Linq;
namespace System.Web.Http.Controllers
{
public interface IHttpActionSelector
{
/// <summary>
/// Selects the action.
/// </summary>
/// <param name="controllerContext">The controller context.</param>
/// <returns>The selected action.</returns>
HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);
/// <summary>
/// Returns a map, keyed by action string, of all <see cref="HttpActionDescriptor"/> that the selector can select.
/// This is primarily called by <see cref="System.Web.Http.Description.IApiExplorer"/> to discover all the possible actions in the controller.
/// </summary>
/// <param name="controllerDescriptor">The controller descriptor.</param>
/// <returns>A map of <see cref="HttpActionDescriptor"/> that the selector can select, or null if the selector does not have a well-defined mapping of <see cref="HttpActionDescriptor"/>.</returns>
ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace System.Web.Http.Controllers
{
public interface IHttpController
{
Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,439 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Internal;
using System.Web.Http.Properties;
namespace System.Web.Http.Controllers
{
/// <summary>
/// An action descriptor representing a reflected synchronous or asynchronous action method.
/// </summary>
public class ReflectedHttpActionDescriptor : HttpActionDescriptor
{
private static readonly object[] _empty = new object[0];
private readonly Lazy<Collection<HttpParameterDescriptor>> _parameters;
private Lazy<ActionExecutor> _actionExecutor;
private MethodInfo _methodInfo;
private Type _returnType;
private string _actionName;
private Collection<HttpMethod> _supportedHttpMethods;
// Getting custom attributes via reflection is slow.
// But iterating over a object[] to pick out specific types is fast.
// Furthermore, many different services may call to ask for different attributes, so we have multiple callers.
// That means there's not a single cache for the callers, which means there's some value caching here.
// This cache can be a 2x speedup in some benchmarks.
private object[] _attrCached;
private static readonly HttpMethod[] _supportedHttpMethodsByConvention =
{
HttpMethod.Get,
HttpMethod.Post,
HttpMethod.Put,
HttpMethod.Delete,
HttpMethod.Head,
HttpMethod.Options,
new HttpMethod("PATCH")
};
/// <summary>
/// Initializes a new instance of the <see cref="ReflectedHttpActionDescriptor"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public ReflectedHttpActionDescriptor()
{
_parameters = new Lazy<Collection<HttpParameterDescriptor>>(() => InitializeParameterDescriptors());
_supportedHttpMethods = new Collection<HttpMethod>();
}
public ReflectedHttpActionDescriptor(HttpControllerDescriptor controllerDescriptor, MethodInfo methodInfo)
: base(controllerDescriptor)
{
if (methodInfo == null)
{
throw Error.ArgumentNull("methodInfo");
}
InitializeProperties(methodInfo);
_parameters = new Lazy<Collection<HttpParameterDescriptor>>(() => InitializeParameterDescriptors());
}
/// <summary>
/// Caches that the ActionSelector use.
/// </summary>
internal IActionMethodSelector[] CacheAttrsIActionMethodSelector { get; private set; }
public override string ActionName
{
get { return _actionName; }
}
public override Collection<HttpMethod> SupportedHttpMethods
{
get { return _supportedHttpMethods; }
}
public MethodInfo MethodInfo
{
get { return _methodInfo; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
InitializeProperties(value);
}
}
/// <summary>
/// The return type of the method or <c>null</c> if the method does not return a value (e.g. a method returning
/// <c>void</c>).
/// </summary>
/// <remarks>
/// This implementation returns the exact value of <see cref="System.Reflection.MethodInfo.ReturnType"/> for
/// synchronous methods and an unwrapped value for asynchronous methods (e.g. the <c>T</c> of <see cref="Task{T}"/>.
/// This returns <c>null</c> for methods returning <c>void</c> or <see cref="Task"/>.
/// </remarks>
public override Type ReturnType
{
get { return _returnType; }
}
public override Collection<T> GetCustomAttributes<T>()
{
Contract.Assert(_methodInfo != null); // can't get attributes without the method set!
Contract.Assert(_attrCached != null); // setting the method should build the attribute cache
return new Collection<T>(TypeHelper.OfType<T>(_attrCached));
}
/// <summary>
/// Executes the described action and returns a <see cref="Task{T}"/> that once completed will
/// contain the return value of the action.
/// </summary>
/// <param name="controllerContext">The context.</param>
/// <param name="arguments">The arguments.</param>
/// <returns>A <see cref="Task{T}"/> that once completed will contain the return value of the action.</returns>
public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments)
{
if (controllerContext == null)
{
throw Error.ArgumentNull("controllerContext");
}
if (arguments == null)
{
throw Error.ArgumentNull("arguments");
}
return TaskHelpers.RunSynchronously(() =>
{
object[] argumentValues = PrepareParameters(arguments, controllerContext);
return _actionExecutor.Value.Execute(controllerContext.Controller, argumentValues);
});
}
public override Collection<IFilter> GetFilters()
{
return new Collection<IFilter>(GetCustomAttributes<IFilter>().Concat(base.GetFilters()).ToList());
}
public override Collection<HttpParameterDescriptor> GetParameters()
{
return _parameters.Value;
}
private void InitializeProperties(MethodInfo methodInfo)
{
_methodInfo = methodInfo;
_returnType = GetReturnType(methodInfo);
_actionExecutor = new Lazy<ActionExecutor>(() => InitializeActionExecutor(_methodInfo));
_attrCached = _methodInfo.GetCustomAttributes(inherit: true);
CacheAttrsIActionMethodSelector = _attrCached.OfType<IActionMethodSelector>().ToArray();
_actionName = GetActionName(_methodInfo, _attrCached);
_supportedHttpMethods = GetSupportedHttpMethods(_methodInfo, _attrCached);
}
internal static Type GetReturnType(MethodInfo methodInfo)
{
Type result = methodInfo.ReturnType;
if (typeof(Task).IsAssignableFrom(result))
{
result = TypeHelper.GetTaskInnerTypeOrNull(methodInfo.ReturnType);
}
if (result == typeof(void))
{
result = null;
}
return result;
}
private Collection<HttpParameterDescriptor> InitializeParameterDescriptors()
{
Contract.Assert(_methodInfo != null);
List<HttpParameterDescriptor> parameterInfos = _methodInfo.GetParameters().Select(
(item) => new ReflectedHttpParameterDescriptor(this, item)).ToList<HttpParameterDescriptor>();
return new Collection<HttpParameterDescriptor>(parameterInfos);
}
private object[] PrepareParameters(IDictionary<string, object> parameters, HttpControllerContext controllerContext)
{
// This is on a hotpath, so a quick check to avoid the allocation if we have no parameters.
if (_parameters.Value.Count == 0)
{
return _empty;
}
ParameterInfo[] parameterInfos = MethodInfo.GetParameters();
var rawParameterValues = from parameterInfo in parameterInfos
select ExtractParameterFromDictionary(parameterInfo, parameters, controllerContext);
object[] parametersArray = rawParameterValues.ToArray();
return parametersArray;
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing of response instance.")]
private object ExtractParameterFromDictionary(ParameterInfo parameterInfo, IDictionary<string, object> parameters, HttpControllerContext controllerContext)
{
object value;
if (!parameters.TryGetValue(parameterInfo.Name, out value))
{
// the key should always be present, even if the parameter value is null
throw new HttpResponseException(controllerContext.Request.CreateResponse(
HttpStatusCode.BadRequest,
Error.Format(SRResources.ReflectedActionDescriptor_ParameterNotInDictionary,
parameterInfo.Name, parameterInfo.ParameterType, MethodInfo, MethodInfo.DeclaringType)));
}
if (value == null && !TypeHelper.TypeAllowsNullValue(parameterInfo.ParameterType))
{
// tried to pass a null value for a non-nullable parameter type
throw new HttpResponseException(controllerContext.Request.CreateResponse(
HttpStatusCode.BadRequest,
Error.Format(SRResources.ReflectedActionDescriptor_ParameterCannotBeNull,
parameterInfo.Name, parameterInfo.ParameterType, MethodInfo, MethodInfo.DeclaringType)));
}
if (value != null && !parameterInfo.ParameterType.IsInstanceOfType(value))
{
// value was supplied but is not of the proper type
throw new HttpResponseException(controllerContext.Request.CreateResponse(
HttpStatusCode.BadRequest,
Error.Format(SRResources.ReflectedActionDescriptor_ParameterValueHasWrongType,
parameterInfo.Name, MethodInfo, MethodInfo.DeclaringType, value.GetType(), parameterInfo.ParameterType)));
}
return value;
}
private static string GetActionName(MethodInfo methodInfo, object[] actionAttributes)
{
ActionNameAttribute nameAttribute = TypeHelper.OfType<ActionNameAttribute>(actionAttributes).FirstOrDefault();
return nameAttribute != null
? nameAttribute.Name
: methodInfo.Name;
}
private static Collection<HttpMethod> GetSupportedHttpMethods(MethodInfo methodInfo, object[] actionAttributes)
{
Collection<HttpMethod> supportedHttpMethods = new Collection<HttpMethod>();
ICollection<IActionHttpMethodProvider> httpMethodProviders = TypeHelper.OfType<IActionHttpMethodProvider>(actionAttributes);
if (httpMethodProviders.Count > 0)
{
// Get HttpMethod from attributes
foreach (IActionHttpMethodProvider httpMethodSelector in httpMethodProviders)
{
foreach (HttpMethod httpMethod in httpMethodSelector.HttpMethods)
{
supportedHttpMethods.Add(httpMethod);
}
}
}
else
{
// Get HttpMethod from method name convention
for (int i = 0; i < _supportedHttpMethodsByConvention.Length; i++)
{
if (methodInfo.Name.StartsWith(_supportedHttpMethodsByConvention[i].Method, StringComparison.OrdinalIgnoreCase))
{
supportedHttpMethods.Add(_supportedHttpMethodsByConvention[i]);
break;
}
}
}
if (supportedHttpMethods.Count == 0)
{
// Use POST as the default HttpMethod
supportedHttpMethods.Add(HttpMethod.Post);
}
return supportedHttpMethods;
}
private static ActionExecutor InitializeActionExecutor(MethodInfo methodInfo)
{
if (methodInfo.ContainsGenericParameters)
{
throw Error.InvalidOperation(SRResources.ReflectedHttpActionDescriptor_CannotCallOpenGenericMethods,
methodInfo, methodInfo.ReflectedType.FullName);
}
return new ActionExecutor(methodInfo);
}
private sealed class ActionExecutor
{
private static readonly Task<object> _completedTaskReturningNull = TaskHelpers.FromResult<object>(null);
private readonly Func<object, object[], Task<object>> _executor;
private static MethodInfo _convertOfTMethod = typeof(ActionExecutor).GetMethod("Convert", BindingFlags.Static | BindingFlags.NonPublic);
public ActionExecutor(MethodInfo methodInfo)
{
Contract.Assert(methodInfo != null);
_executor = GetExecutor(methodInfo);
}
public Task<object> Execute(object instance, object[] arguments)
{
return _executor(instance, arguments);
}
// Method called via reflection.
private static Task<object> Convert<T>(object taskAsObject)
{
Task<T> task = (Task<T>)taskAsObject;
return task.Then(r => (object)r);
}
// Do not inline or optimize this method to avoid stack-related reflection demand issues when
// running from the GAC in medium trust
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static Func<object, Task<object>> CompileGenericTaskConversionDelegate(Type taskValueType)
{
Contract.Assert(taskValueType != null);
return (Func<object, Task<object>>)Delegate.CreateDelegate(typeof(Func<object, Task<object>>), _convertOfTMethod.MakeGenericMethod(taskValueType));
}
private static Func<object, object[], Task<object>> GetExecutor(MethodInfo methodInfo)
{
// Parameters to executor
ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "instance");
ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
// Build parameter list
List<Expression> parameters = new List<Expression>();
ParameterInfo[] paramInfos = methodInfo.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
ParameterInfo paramInfo = paramInfos[i];
BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
UnaryExpression valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
// valueCast is "(Ti) parameters[i]"
parameters.Add(valueCast);
}
// Call method
UnaryExpression instanceCast = (!methodInfo.IsStatic) ? Expression.Convert(instanceParameter, methodInfo.ReflectedType) : null;
MethodCallExpression methodCall = methodCall = Expression.Call(instanceCast, methodInfo, parameters);
// methodCall is "((MethodInstanceType) instance).method((T0) parameters[0], (T1) parameters[1], ...)"
// Create function
if (methodCall.Type == typeof(void))
{
// for: public void Action()
Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceParameter, parametersParameter);
Action<object, object[]> voidExecutor = lambda.Compile();
return (instance, methodParameters) =>
{
voidExecutor(instance, methodParameters);
return _completedTaskReturningNull;
};
}
else
{
// must coerce methodCall to match Func<object, object[], object> signature
UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceParameter, parametersParameter);
Func<object, object[], object> compiled = lambda.Compile();
if (methodCall.Type == typeof(Task))
{
// for: public Task Action()
return (instance, methodParameters) =>
{
Task r = (Task)compiled(instance, methodParameters);
ThrowIfWrappedTaskInstance(methodInfo, r.GetType());
return r.Then(() => (object)null);
};
}
else if (typeof(Task).IsAssignableFrom(methodCall.Type))
{
// for: public Task<T> Action()
// constructs: return (Task<object>)Convert<T>(((Task<T>)instance).method((T0) param[0], ...))
Type taskValueType = TypeHelper.GetTaskInnerTypeOrNull(methodCall.Type);
var compiledConversion = CompileGenericTaskConversionDelegate(taskValueType);
return (instance, methodParameters) =>
{
object callResult = compiled(instance, methodParameters);
Task<object> convertedResult = compiledConversion(callResult);
return convertedResult;
};
}
else
{
// for: public T Action()
return (instance, methodParameters) =>
{
var result = compiled(instance, methodParameters);
// Throw when the result of a method is Task. Asynchronous methods need to declare that they
// return a Task.
Task resultAsTask = result as Task;
if (resultAsTask != null)
{
throw Error.InvalidOperation(SRResources.ActionExecutor_UnexpectedTaskInstance,
methodInfo.Name, methodInfo.DeclaringType.Name);
}
return TaskHelpers.FromResult(result);
};
}
}
}
private static void ThrowIfWrappedTaskInstance(MethodInfo method, Type type)
{
// Throw if a method declares a return type of Task and returns an instance of Task<Task> or Task<Task<T>>
// This most likely indicates that the developer forgot to call Unwrap() somewhere.
Contract.Assert(method.ReturnType == typeof(Task));
// Fast path: check if type is exactly Task first.
if (type != typeof(Task))
{
Type innerTaskType = TypeHelper.GetTaskInnerTypeOrNull(type);
if (innerTaskType != null && typeof(Task).IsAssignableFrom(innerTaskType))
{
throw Error.InvalidOperation(SRResources.ActionExecutor_WrappedTaskInstance,
method.Name, method.DeclaringType.Name, type.FullName);
}
}
}
}
}
}

View File

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.ObjectModel;
using System.Reflection;
using System.Web.Http.Internal;
namespace System.Web.Http.Controllers
{
public class ReflectedHttpParameterDescriptor : HttpParameterDescriptor
{
private ParameterInfo _parameterInfo;
public ReflectedHttpParameterDescriptor(HttpActionDescriptor actionDescriptor, ParameterInfo parameterInfo)
: base(actionDescriptor)
{
if (parameterInfo == null)
{
throw Error.ArgumentNull("parameterInfo");
}
ParameterInfo = parameterInfo;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReflectedHttpParameterDescriptor"/> class.
/// </summary>
/// <remarks>The default constructor is intended for use by unit testing only.</remarks>
public ReflectedHttpParameterDescriptor()
{
}
public override object DefaultValue
{
get
{
object value;
if (ParameterInfo.TryGetDefaultValue(out value))
{
return value;
}
else
{
return base.DefaultValue;
}
}
}
public ParameterInfo ParameterInfo
{
get { return _parameterInfo; }
set
{
if (value == null)
{
throw Error.PropertyNull();
}
_parameterInfo = value;
}
}
public override string ParameterName
{
get { return ParameterInfo.Name; }
}
public override Type ParameterType
{
get { return ParameterInfo.ParameterType; }
}
public override Collection<T> GetCustomAttributes<T>()
{
return new Collection<T>(ParameterInfoExtensions.GetCustomAttributes<T>(ParameterInfo, inherit: false));
}
}
}

Some files were not shown because too many files have changed in this diff Show More