// 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.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; using System.Web.Http.Controllers; using System.Web.Http.Dependencies; using System.Web.Http.Filters; using System.Web.Http.Metadata; using System.Web.Http.ModelBinding; using System.Web.Http.Services; using System.Web.Http.Tracing; using System.Web.Http.Validation; namespace System.Web.Http { /// /// Configuration of instances. /// public class HttpConfiguration : IDisposable { private readonly HttpRouteCollection _routes; private readonly ConcurrentDictionary _properties = new ConcurrentDictionary(); private readonly MediaTypeFormatterCollection _formatters; private readonly Collection _messageHandlers = new Collection(); private readonly HttpFilterCollection _filters = new HttpFilterCollection(); private IDependencyResolver _dependencyResolver = EmptyResolver.Instance; private Action _initializer = DefaultInitializer; private bool _initialized; private bool _disposed; /// /// Initializes a new instance of the class. /// [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The route collection is disposed as part of this class.")] public HttpConfiguration() : this(new HttpRouteCollection(String.Empty)) { } /// /// Initializes a new instance of the class. /// /// The to associate with this instance. public HttpConfiguration(HttpRouteCollection routes) { if (routes == null) { throw Error.ArgumentNull("routes"); } _routes = routes; _formatters = DefaultFormatters(this); Services = new DefaultServices(this); ParameterBindingRules = DefaultActionValueBinder.GetDefaultParameterBinders(); } private HttpConfiguration(HttpConfiguration configuration, HttpControllerSettings settings) { _routes = configuration.Routes; _filters = configuration.Filters; _messageHandlers = configuration.MessageHandlers; _properties = configuration.Properties; _dependencyResolver = configuration.DependencyResolver; IncludeErrorDetailPolicy = configuration.IncludeErrorDetailPolicy; // per-controller settings Services = settings.IsServiceCollectionInitialized ? settings.Services : configuration.Services; _formatters = settings.IsFormatterCollectionInitialized ? settings.Formatters : configuration.Formatters; ParameterBindingRules = settings.IsParameterBindingRuleCollectionInitialized ? settings.ParameterBindingRules : configuration.ParameterBindingRules; // Use the original configuration's initializer so that its Initialize() // will perform the same logic on this clone as on the original. Initializer = configuration.Initializer; // create a new validator cache if the validator providers have changed if (settings.IsServiceCollectionInitialized && !settings.Services.GetModelValidatorProviders().SequenceEqual(configuration.Services.GetModelValidatorProviders())) { ModelValidatorCache validatorCache = new ModelValidatorCache(new Lazy>(() => Services.GetModelValidatorProviders())); settings.Services.Replace(typeof(IModelValidatorCache), validatorCache); } } /// /// Gets or sets the action that will perform final initialization /// of the instance before it is used /// to process requests. /// /// The Action returned by this property will be called to perform /// final initialization of an before it is /// used to process a request. /// /// The passed to this action should be /// considered immutable after the action returns. /// /// public Action Initializer { get { return _initializer; } set { if (value == null) { throw Error.ArgumentNull("value"); } _initializer = value; } } /// /// Gets the list of filters that apply to all requests served using this HttpConfiguration instance. /// public HttpFilterCollection Filters { get { return _filters; } } /// /// Gets an ordered list of instances to be invoked as an /// travels up the stack and an travels down in /// stack in return. The handlers are invoked in a top-down fashion in the incoming path and bottom-up in the outgoing /// path. That is, the first entry is invoked first for an incoming request message but last for an outgoing /// response message. /// /// /// The message handler collection. /// public Collection MessageHandlers { get { return _messageHandlers; } } /// /// Gets the associated with this instance. /// /// /// The . /// public HttpRouteCollection Routes { get { return _routes; } } /// /// Gets the properties associated with this instance. /// public ConcurrentDictionary Properties { get { return _properties; } } /// /// Gets the root virtual path. The property always returns /// "/" as the first character of the returned value. /// public string VirtualPathRoot { get { return _routes.VirtualPathRoot; } } /// /// Gets or sets the dependency resolver associated with this . /// public IDependencyResolver DependencyResolver { get { return _dependencyResolver; } set { if (value == null) { throw Error.PropertyNull(); } _dependencyResolver = value; } } /// /// Gets the container of default services associated with this . /// Only supports the list of service types documented on . For general /// purpose types, please use . /// public ServicesContainer Services { get; internal set; } /// /// Top level hook for how parameters should be bound. /// This should be respected by the IActionValueBinder. If a parameter is not claimed by the list, the IActionValueBinder still binds it. /// public ParameterBindingRulesCollection ParameterBindingRules { get; internal set; } /// /// Gets or sets a value indicating whether error details should be included in error messages. /// public IncludeErrorDetailPolicy IncludeErrorDetailPolicy { get; set; } /// /// Gets the media type formatters. /// public MediaTypeFormatterCollection Formatters { get { return _formatters; } } private static MediaTypeFormatterCollection DefaultFormatters(HttpConfiguration config) { var formatters = new MediaTypeFormatterCollection(); // Basic FormUrlFormatter does not support binding to a T. // Use our JQuery formatter instead. formatters.Add(new JQueryMvcFormUrlEncodedFormatter(config)); return formatters; } [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller owns the disposable object")] internal static HttpConfiguration ApplyControllerSettings(HttpControllerSettings settings, HttpConfiguration configuration) { if (!settings.IsFormatterCollectionInitialized && !settings.IsParameterBindingRuleCollectionInitialized && !settings.IsServiceCollectionInitialized) { return configuration; } // Create a clone of the original configuration, including its initialization rules. // Invoking Initialize therefore initializes the cloned config the same way as the original. HttpConfiguration newConfiguration = new HttpConfiguration(configuration, settings); newConfiguration.Initializer(newConfiguration); return newConfiguration; } private static void DefaultInitializer(HttpConfiguration configuration) { // Register the default IRequiredMemberSelector for formatters that haven't been assigned one ModelMetadataProvider metadataProvider = configuration.Services.GetModelMetadataProvider(); IEnumerable validatorProviders = configuration.Services.GetModelValidatorProviders(); IRequiredMemberSelector defaultRequiredMemberSelector = new ModelValidationRequiredMemberSelector(metadataProvider, validatorProviders); foreach (MediaTypeFormatter formatter in configuration.Formatters) { if (formatter.RequiredMemberSelector == null) { formatter.RequiredMemberSelector = defaultRequiredMemberSelector; } } // Initialize the tracing layer. // This must be the last initialization code to execute // because it alters the configuration and expects no // further changes. As a default service, we know it // must be present. ITraceManager traceManager = configuration.Services.GetTraceManager(); Contract.Assert(traceManager != null); traceManager.Initialize(configuration); } /// /// Invoke the Intializer hook. It is considered immutable from this point forward. /// It's safe to call this multiple times. /// public void EnsureInitialized() { if (_initialized) { return; } _initialized = true; Initializer(this); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { _disposed = true; if (disposing) { _routes.Dispose(); DependencyResolver.Dispose(); } } } } }