// 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.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Web.Http.ExceptionHandling; using System.Web.Http.Properties; namespace System.Web.Http.Controllers { // This is common to both per-controller and global config. // It facilitates sharing all the mutation operations between them. public abstract class ServicesContainer : IDisposable { // Wrapped/composite versions of the exception service interfaces designed for consumption in catch blocks. // See ExceptionServices.GetLogger/Handler for how these internal services are used. // These instances must be stored separately and not provided via GetService because existing stores for // GetService do not provide concurrency control and these two wrappers are potentially initialized late. internal readonly Lazy ExceptionServicesLogger; internal readonly Lazy ExceptionServicesHandler; /// Initializes a new instance of the class. protected ServicesContainer() { ExceptionServicesLogger = new Lazy(CreateExceptionServicesLogger); ExceptionServicesHandler = new Lazy(CreateExceptionServicesHandler); } public abstract object GetService(Type serviceType); public abstract IEnumerable GetServices(Type serviceType); // critical method for mutation operations (Add,Insert,Clear,Replace, etc) // This is used for multi-services. // There are other abstract methods to mutate the single services. [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "expose for mutation")] protected abstract List GetServiceInstances(Type serviceType); protected virtual void ResetCache(Type serviceType) { } /// /// Determine whether the service type should be fetched with GetService or GetServices. /// /// type of service to query /// true iff the service is singular. public abstract bool IsSingleService(Type serviceType); /// /// Adds a service to the end of services list for the given service type. /// /// The service type. /// The service instance. public void Add(Type serviceType, object service) { Insert(serviceType, Int32.MaxValue, service); } /// /// Adds the services of the specified collection to the end of the services list for /// the given service type. /// /// The service type. /// The services to add. public void AddRange(Type serviceType, IEnumerable services) { InsertRange(serviceType, Int32.MaxValue, services); } /// /// Removes all the service instances of the given service type. /// /// The service type to clear from the services list. public virtual void Clear(Type serviceType) { if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } if (IsSingleService(serviceType)) { ClearSingle(serviceType); } else { ClearMultiple(serviceType); } ResetCache(serviceType); } protected abstract void ClearSingle(Type serviceType); protected virtual void ClearMultiple(Type serviceType) { List instances = GetServiceInstances(serviceType); instances.Clear(); } /// /// Searches for a service that matches the conditions defined by the specified /// predicate, and returns the zero-based index of the first occurrence. /// /// The service type. /// The delegate that defines the conditions of the element /// to search for. /// The zero-based index of the first occurrence, if found; otherwise, -1. public int FindIndex(Type serviceType, Predicate match) { if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } if (match == null) { throw Error.ArgumentNull("match"); } List instances = GetServiceInstances(serviceType); return instances.FindIndex(match); } /// /// Inserts a service into the collection at the specified index. /// /// The service type. /// The zero-based index at which the service should be inserted. /// If is passed, ensures the element is added to the end. /// The service to insert. public void Insert(Type serviceType, int index, object service) { if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } if (service == null) { throw Error.ArgumentNull("service"); } if (!serviceType.IsAssignableFrom(service.GetType())) { throw Error.Argument("service", SRResources.Common_TypeMustDriveFromType, service.GetType().Name, serviceType.Name); } List instances = GetServiceInstances(serviceType); if (index == Int32.MaxValue) { index = instances.Count; } instances.Insert(index, service); ResetCache(serviceType); } /// /// Inserts the elements of the collection into the service list at the specified index. /// /// The service type. /// The zero-based index at which the new elements should be inserted. /// If is passed, ensures the elements are added to the end. /// The collection of services to insert. public void InsertRange(Type serviceType, int index, IEnumerable services) { if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } if (services == null) { throw Error.ArgumentNull("services"); } object[] filteredServices = services.Where(svc => svc != null).ToArray(); object incorrectlyTypedService = filteredServices.FirstOrDefault(svc => !serviceType.IsAssignableFrom(svc.GetType())); if (incorrectlyTypedService != null) { throw Error.Argument("services", SRResources.Common_TypeMustDriveFromType, incorrectlyTypedService.GetType().Name, serviceType.Name); } List instances = GetServiceInstances(serviceType); if (index == Int32.MaxValue) { index = instances.Count; } instances.InsertRange(index, filteredServices); ResetCache(serviceType); } /// /// Removes the first occurrence of the given service from the service list for the given service type. /// /// The service type. /// The service instance to remove. /// true if the item is successfully removed; otherwise, false. public bool Remove(Type serviceType, object service) { if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } if (service == null) { throw Error.ArgumentNull("service"); } List instances = GetServiceInstances(serviceType); bool result = instances.Remove(service); ResetCache(serviceType); return result; } /// /// Removes all the elements that match the conditions defined by the specified predicate. /// /// The service type. /// The delegate that defines the conditions of the elements to remove. /// The number of elements removed from the list. public int RemoveAll(Type serviceType, Predicate match) { if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } if (match == null) { throw Error.ArgumentNull("match"); } List instances = GetServiceInstances(serviceType); int result = instances.RemoveAll(match); ResetCache(serviceType); return result; } /// /// Removes the service at the specified index. /// /// The service type. /// The zero-based index of the service to remove. public void RemoveAt(Type serviceType, int index) { if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } List instances = GetServiceInstances(serviceType); instances.RemoveAt(index); ResetCache(serviceType); } /// /// Replaces all existing services for the given service type with the given /// service instance. This works for both singular and plural services. /// /// The service type. /// The service instance. /// public void Replace(Type serviceType, object service) { // Check this early, so we don't call RemoveAll before Insert would catch the null service. if (serviceType == null) { throw Error.ArgumentNull("serviceType"); } if ((service != null) && (!serviceType.IsAssignableFrom(service.GetType()))) { throw Error.Argument("service", SRResources.Common_TypeMustDriveFromType, service.GetType().Name, serviceType.Name); } if (IsSingleService(serviceType)) { ReplaceSingle(serviceType, service); } else { ReplaceMultiple(serviceType, service); } ResetCache(serviceType); } protected abstract void ReplaceSingle(Type serviceType, object service); protected virtual void ReplaceMultiple(Type serviceType, object service) { RemoveAll(serviceType, _ => true); Insert(serviceType, 0, service); } /// /// Replaces all existing services for the given service type with the given /// service instances. /// /// The service type. /// The service instances. public void ReplaceRange(Type serviceType, IEnumerable services) { // Check this early, so we don't call RemoveAll before InsertRange would catch the null services. if (services == null) { throw Error.ArgumentNull("services"); } RemoveAll(serviceType, _ => true); InsertRange(serviceType, 0, services); } /// [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Although this class is not sealed, end users cannot set instances of it so in practice it is sealed.")] [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", Justification = "Although this class is not sealed, end users cannot set instances of it so in practice it is sealed.")] public virtual void Dispose() { } private IExceptionLogger CreateExceptionServicesLogger() { return ExceptionServices.CreateLogger(this); } private IExceptionHandler CreateExceptionServicesHandler() { return ExceptionServices.CreateHandler(this); } } }