// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Net.Http; using System.Web.Http.Dispatcher; using System.Web.Http.Filters; using System.Web.Http.Internal; namespace System.Web.Http.Controllers { /// /// Description and configuration for a controller. /// public class HttpControllerDescriptor { private readonly ConcurrentDictionary _properties = new ConcurrentDictionary(); private HttpConfiguration _configuration; private string _controllerName; private Type _controllerType; private object[] _attributeCache; private object[] _declaredOnlyAttributeCache; 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(); } /// /// Initializes a new instance of the class. /// /// The default constructor is intended for use by unit testing only. public HttpControllerDescriptor() { } // For unit testing purposes. internal HttpControllerDescriptor(HttpConfiguration configuration) { Initialize(configuration); } /// /// Gets the properties associated with this instance. /// public virtual ConcurrentDictionary Properties { get { return _properties; } } /// /// Gets the configuration associated with the . /// 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; } } /// /// Creates a controller instance for the given /// /// The request message /// public virtual IHttpController CreateController(HttpRequestMessage request) { if (request == null) { throw Error.ArgumentNull("request"); } // Invoke the controller activator IHttpControllerActivator activator = Configuration.Services.GetHttpControllerActivator(); IHttpController instance = activator.Create(request, this, ControllerType); return instance; } /// /// Returns the collection of filters associated with this descriptor's controller. /// /// The default implementation calls . /// A collection of filters associated with this controller. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Filters can be built dynamically")] public virtual Collection GetFilters() { return GetCustomAttributes(); } /// /// Returns a collection of attributes that can be assigned to for this descriptor's controller. /// /// The default implementation retrieves the matching set of attributes declared on . /// Used to filter the collection of attributes. Use a value of to retrieve all attributes. /// A collection of attributes associated with this controller. public virtual Collection GetCustomAttributes() where T : class { return GetCustomAttributes(inherit: true); } /// /// Returns a collection of attributes that can be assigned to for this descriptor's controller. /// /// The default implementation retrieves the matching set of attributes declared on . /// Used to filter the collection of attributes. Use a value of to retrieve all attributes. /// true to search this controller's inheritance chain to find the attributes; otherwise, false. /// A collection of attributes associated with this controller. public virtual Collection GetCustomAttributes(bool inherit) where T : class { object[] attributes; // 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 (inherit) { if (_attributeCache == null) { // Even in a race, we'll just ask for the custom attributes twice. _attributeCache = ControllerType.GetCustomAttributes(inherit: true); } attributes = _attributeCache; } else { if (_declaredOnlyAttributeCache == null) { // Even in a race, we'll just ask for the custom attributes twice. _declaredOnlyAttributeCache = ControllerType.GetCustomAttributes(inherit: false); } attributes = _declaredOnlyAttributeCache; } return new Collection(TypeHelper.OfType(attributes)); } // For unit tests for initializing mock objects. Controller may not have a type, so we can't do the normal Initialize() path. internal void Initialize(HttpConfiguration configuration) { _configuration = configuration; } // Initialize the Controller Descriptor. This invokes all IControllerConfiguration attributes // on the controller type (and its base types) private void Initialize() { InvokeAttributesOnControllerType(this, ControllerType); } // Helper to invoke any Controller config attributes on this controller type or its base classes. private static void InvokeAttributesOnControllerType(HttpControllerDescriptor controllerDescriptor, Type type) { Contract.Assert(controllerDescriptor != null); if (type == null) { return; } // Initialize base class before derived classes (same order as ctors). InvokeAttributesOnControllerType(controllerDescriptor, type.BaseType); // Check for attribute object[] attrs = type.GetCustomAttributes(inherit: false); foreach (object attr in attrs) { var controllerConfig = attr as IControllerConfiguration; if (controllerConfig != null) { var originalConfig = controllerDescriptor.Configuration; var controllerSettings = new HttpControllerSettings(originalConfig); controllerConfig.Initialize(controllerSettings, controllerDescriptor); controllerDescriptor.Configuration = HttpConfiguration.ApplyControllerSettings(controllerSettings, originalConfig); } } } } }