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,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Controllers;
namespace System.Web.Http
{
[SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification = "The accessor is exposed as an Collection<HttpMethod>.")]
[CLSCompliant(false)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class AcceptVerbsAttribute : Attribute, IActionHttpMethodProvider
{
private readonly Collection<HttpMethod> _httpMethods;
public AcceptVerbsAttribute(params string[] methods)
{
_httpMethods = methods != null
? new Collection<HttpMethod>(methods.Select(method => HttpMethodHelper.GetHttpMethod(method)).ToArray())
: new Collection<HttpMethod>(new HttpMethod[0]);
}
internal AcceptVerbsAttribute(params HttpMethod[] methods)
{
_httpMethods = new Collection<HttpMethod>(methods);
}
public Collection<HttpMethod> HttpMethods
{
get
{
return _httpMethods;
}
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ActionNameAttribute : Attribute
{
public ActionNameAttribute(string name)
{
Name = name;
}
public string Name { get; private set; }
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Http
{
/// <summary>
/// Actions and controllers marked with this attribute are skipped by <see cref="AuthorizeAttribute"/> during authorization.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class AllowAnonymousAttribute : Attribute
{
}
}

View File

@ -0,0 +1,326 @@
// 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.Http;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;
using System.Web.Http.Properties;
using System.Web.Http.Routing;
namespace System.Web.Http
{
public abstract class ApiController : IHttpController, IDisposable
{
private bool _disposed;
private HttpRequestMessage _request;
private ModelStateDictionary _modelState;
private HttpConfiguration _configuration;
private HttpControllerContext _controllerContext;
/// <summary>
/// Gets the <see name="HttpRequestMessage"/> of the current ApiController.
///
/// The setter is not intended to be used other than for unit testing purpose.
/// </summary>
public HttpRequestMessage Request
{
get { return _request; }
set
{
if (value == null)
{
throw Error.ArgumentNull("value");
}
_request = value;
}
}
/// <summary>
/// Gets the <see name="HttpConfiguration"/> of the current ApiController.
///
/// The setter is not intended to be used other than for unit testing purpose.
/// </summary>
public HttpConfiguration Configuration
{
get { return _configuration; }
set
{
if (value == null)
{
throw Error.ArgumentNull("value");
}
_configuration = value;
}
}
/// <summary>
/// Gets the <see name="HttpControllerContext"/> of the current ApiController.
///
/// The setter is not intended to be used other than for unit testing purpose.
/// </summary>
public HttpControllerContext ControllerContext
{
get { return _controllerContext; }
set
{
if (value == null)
{
throw Error.ArgumentNull("value");
}
_controllerContext = value;
}
}
/// <summary>
/// Gets model state after the model binding process. This ModelState will be empty before model binding happens.
/// Please do not populate this property other than for unit testing purpose.
/// </summary>
public ModelStateDictionary ModelState
{
get
{
if (_modelState == null)
{
// The getter is not intended to be used by multiple threads, so it is fine to initialize here
_modelState = new ModelStateDictionary();
}
return _modelState;
}
}
/// <summary>
/// Returns an instance of a UrlHelper, which is used to generate URLs to other APIs.
/// </summary>
public UrlHelper Url
{
get { return ControllerContext.Url; }
}
/// <summary>
/// Returns the current principal associated with this request.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "That would make for poor usability.")]
public IPrincipal User
{
get { return Thread.CurrentPrincipal; }
}
public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
if (_request != null)
{
// if user has registered a controller factory which produces the same controller instance, we should throw here
throw Error.InvalidOperation(SRResources.CannotSupportSingletonInstance, typeof(ApiController).Name, typeof(IHttpControllerActivator).Name);
}
Initialize(controllerContext);
// We can't be reused, and we know we're disposable, so make sure we go away when
// the request has been completed.
if (_request != null)
{
_request.RegisterForDispose(this);
}
HttpControllerDescriptor controllerDescriptor = controllerContext.ControllerDescriptor;
HttpActionDescriptor actionDescriptor = controllerDescriptor.HttpActionSelector.SelectAction(controllerContext);
HttpActionContext actionContext = new HttpActionContext(controllerContext, actionDescriptor);
IEnumerable<FilterInfo> filters = actionDescriptor.GetFilterPipeline();
FilterGrouping filterGrouping = new FilterGrouping(filters);
IEnumerable<IActionFilter> actionFilters = filterGrouping.ActionFilters;
IEnumerable<IAuthorizationFilter> authorizationFilters = filterGrouping.AuthorizationFilters;
IEnumerable<IExceptionFilter> exceptionFilters = filterGrouping.ExceptionFilters;
// Func<Task<HttpResponseMessage>>
Task<HttpResponseMessage> result = InvokeActionWithAuthorizationFilters(actionContext, cancellationToken, authorizationFilters, () =>
{
HttpActionBinding actionBinding = actionDescriptor.ActionBinding;
Task bindTask = actionBinding.ExecuteBindingAsync(actionContext, cancellationToken);
return bindTask.Then<HttpResponseMessage>(() =>
{
_modelState = actionContext.ModelState;
Func<Task<HttpResponseMessage>> invokeFunc = InvokeActionWithActionFilters(actionContext, cancellationToken, actionFilters, () =>
{
return controllerDescriptor.HttpActionInvoker.InvokeActionAsync(actionContext, cancellationToken);
});
return invokeFunc();
});
})();
result = InvokeActionWithExceptionFilters(result, actionContext, cancellationToken, exceptionFilters);
return result;
}
protected virtual void Initialize(HttpControllerContext controllerContext)
{
if (controllerContext == null)
{
throw Error.ArgumentNull("controllerContext");
}
ControllerContext = controllerContext;
_request = controllerContext.Request;
_configuration = controllerContext.Configuration;
}
internal static Task<HttpResponseMessage> InvokeActionWithExceptionFilters(Task<HttpResponseMessage> actionTask, HttpActionContext actionContext, CancellationToken cancellationToken, IEnumerable<IExceptionFilter> filters)
{
Contract.Assert(actionTask != null);
Contract.Assert(actionContext != null);
Contract.Assert(filters != null);
return actionTask.Catch<HttpResponseMessage>(
info =>
{
HttpActionExecutedContext executedContext = new HttpActionExecutedContext(actionContext, info.Exception);
// Note: exception filters need to be scheduled in the reverse order so that
// the more specific filter (e.g. Action) executes before the less specific ones (e.g. Global)
filters = filters.Reverse();
// Note: in order to work correctly with the TaskHelpers.Iterate method, the lazyTaskEnumeration
// must be lazily evaluated. Otherwise all the tasks might start executing even though we want to run them
// sequentially and not invoke any of the following ones if an earlier fails.
IEnumerable<Task> lazyTaskEnumeration = filters.Select(filter => filter.ExecuteExceptionFilterAsync(executedContext, cancellationToken));
Task<HttpResponseMessage> resultTask =
TaskHelpers.Iterate(lazyTaskEnumeration, cancellationToken)
.Then<HttpResponseMessage>(() =>
{
if (executedContext.Response != null)
{
return TaskHelpers.FromResult<HttpResponseMessage>(executedContext.Response);
}
else
{
return TaskHelpers.FromError<HttpResponseMessage>(executedContext.Exception);
}
});
return info.Task(resultTask);
});
}
internal static Func<Task<HttpResponseMessage>> InvokeActionWithAuthorizationFilters(HttpActionContext actionContext, CancellationToken cancellationToken, IEnumerable<IAuthorizationFilter> filters, Func<Task<HttpResponseMessage>> innerAction)
{
Contract.Assert(actionContext != null);
Contract.Assert(filters != null);
Contract.Assert(innerAction != null);
// Because the continuation gets built from the inside out we need to reverse the filter list
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
filters = filters.Reverse();
Func<Task<HttpResponseMessage>> result = filters.Aggregate(innerAction, (continuation, filter) =>
{
return () => filter.ExecuteAuthorizationFilterAsync(actionContext, cancellationToken, continuation);
});
return result;
}
internal static Func<Task<HttpResponseMessage>> InvokeActionWithActionFilters(HttpActionContext actionContext, CancellationToken cancellationToken, IEnumerable<IActionFilter> filters, Func<Task<HttpResponseMessage>> innerAction)
{
Contract.Assert(actionContext != null);
Contract.Assert(filters != null);
Contract.Assert(innerAction != null);
// Because the continuation gets built from the inside out we need to reverse the filter list
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
filters = filters.Reverse();
Func<Task<HttpResponseMessage>> result = filters.Aggregate(innerAction, (continuation, filter) =>
{
return () => filter.ExecuteActionFilterAsync(actionContext, cancellationToken, continuation);
});
return result;
}
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
if (disposing)
{
// TODO: Dispose controller state
}
}
}
#endregion
/// <summary>
/// Quickly split filters into different types
/// </summary>
/// <remarks>Avoid <see cref="M:ReadOnlyCollection.Select"/> because it has a very slow implementation that shows on profiles.</remarks>
private class FilterGrouping
{
private List<IActionFilter> _actionFilters = new List<IActionFilter>();
private List<IAuthorizationFilter> _authorizationFilters = new List<IAuthorizationFilter>();
private List<IExceptionFilter> _exceptionFilters = new List<IExceptionFilter>();
public FilterGrouping(IEnumerable<FilterInfo> filters)
{
Contract.Assert(filters != null);
foreach (FilterInfo f in filters)
{
var filter = f.Instance;
Categorize(filter, _actionFilters);
Categorize(filter, _authorizationFilters);
Categorize(filter, _exceptionFilters);
}
}
public IEnumerable<IActionFilter> ActionFilters
{
get { return _actionFilters; }
}
public IEnumerable<IAuthorizationFilter> AuthorizationFilters
{
get { return _authorizationFilters; }
}
public IEnumerable<IExceptionFilter> ExceptionFilters
{
get { return _exceptionFilters; }
}
private static void Categorize<T>(IFilter filter, List<T> list) where T : class
{
T match = filter as T;
if (match != null)
{
list.Add(match);
}
}
}
}
}

View File

@ -0,0 +1,179 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace System.Web.Http
{
/// <summary>
/// An authorization filter that verifies the request's <see cref="IPrincipal"/>.
/// </summary>
/// <remarks>You can declare multiple of these attributes per action. You can also use <see cref="AllowAnonymousAttribute"/>
/// to disable authorization for a specific action.</remarks>
/// <seealso cref="M:AuthorizeCore"/>
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want to support extensibility")]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : AuthorizationFilterAttribute
{
private static readonly string[] _emptyArray = new string[0];
private readonly object _typeId = new object();
private string _roles;
private string[] _rolesSplit = _emptyArray;
private string _users;
private string[] _usersSplit = _emptyArray;
/// <summary>
/// Gets or sets the authorized roles.
/// </summary>
/// <value>
/// The roles string.
/// </value>
/// <remarks>Multiple role names can be specified using the comma character as a separator.</remarks>
public string Roles
{
get { return _roles ?? String.Empty; }
set
{
_roles = value;
_rolesSplit = SplitString(value);
}
}
/// <summary>
/// Gets a unique identifier for this <see cref="T:System.Attribute"/>.
/// </summary>
/// <returns>The unique identifier for the attribute.</returns>
public override object TypeId
{
get { return _typeId; }
}
/// <summary>
/// Gets or sets the authorized users.
/// </summary>
/// <value>
/// The users string.
/// </value>
/// <remarks>Multiple role names can be specified using the comma character as a separator.</remarks>
public string Users
{
get { return _users ?? String.Empty; }
set
{
_users = value;
_usersSplit = SplitString(value);
}
}
/// <summary>
/// Determines whether access for this particular request is authorized. This method uses the user <see cref="IPrincipal"/>
/// returned via <see cref="System.Threading.Thread.CurrentPrincipal"/>. Authorization is denied if the user is not authenticated,
/// the user is not in the authorized group of <see cref="P:Users"/> (if defined), or if the user is not in any of the authorized
/// <see cref="P:Roles"/> (if defined).
/// </summary>
/// <returns><c>true</c> if access is authorized; otherwise <c>false</c>.</returns>
private bool AuthorizeCore()
{
IPrincipal user = Thread.CurrentPrincipal;
if (user == null || !user.Identity.IsAuthenticated)
{
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
{
return false;
}
return true;
}
/// <summary>
/// Called when an action is being authorized. This method uses the user <see cref="IPrincipal"/>
/// returned via <see cref="M:HttpRequestMessageExtensions.GetUserPrincipal"/>. Authorization is denied if
/// - the request is not associated with any user.
/// - the user is not authenticated,
/// - the user is authenticated but is not in the authorized group of <see cref="P:Users"/> (if defined), or if the user
/// is not in any of the authorized <see cref="P:Roles"/> (if defined).
///
/// If authorization is denied then this method will invoke <see cref="M:HandleUnauthorizedRequest"/> to process the unauthorized request.
/// </summary>
/// <remarks>You can use <see cref="AllowAnonymousAttribute"/> to cause authorization checks to be skipped for a particular
/// action or controller.</remarks>
/// <param name="actionContext">The context.</param>
/// <exception cref="ArgumentNullException">The context parameter is null.</exception>
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
if (SkipAuthorization(actionContext))
{
return;
}
if (!AuthorizeCore())
{
HandleUnauthorizedRequest(actionContext);
}
}
/// <summary>
/// Processes requests that fail authorization. This default implementation creates a new response with the
/// Unauthorized status code. Override this method to provide your own handling for unauthorized requests.
/// </summary>
/// <param name="actionContext">The context.</param>
protected virtual void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
actionContext.Response = actionContext.ControllerContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
private static bool SkipAuthorization(HttpActionContext actionContext)
{
Contract.Assert(actionContext != null);
return actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()
|| actionContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();
}
/// <summary>
/// Splits the string on commas and removes any leading/trailing whitespace from each result item.
/// </summary>
/// <param name="original">The input string.</param>
/// <returns>An array of strings parsed from the input <paramref name="original"/> string.</returns>
internal static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return _emptyArray;
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
}

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);
}
}

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