|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension methods for setting up essential MVC services in an <see cref="IServiceCollection" />.
/// </summary>
public static class MvcCoreServiceCollectionExtensions
{
/// <summary>
/// Adds the minimum essential MVC services to the specified <see cref="IServiceCollection" />. Additional services
/// including MVC's support for authorization, formatters, and validation must be added separately using the
/// <see cref="IMvcCoreBuilder"/> returned from this method.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>An <see cref="IMvcCoreBuilder"/> that can be used to further configure the MVC services.</returns>
/// <remarks>
/// The <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> approach for configuring
/// MVC is provided for experienced MVC developers who wish to have full control over the set of default services
/// registered. <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> will register
/// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any
/// application will satisfy its requirements with just a call to
/// <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>. Additional configuration using the
/// <see cref="IMvcCoreBuilder"/> will be required.
/// </remarks>
public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
var environment = GetServiceFromCollection<IWebHostEnvironment>(services);
var partManager = GetApplicationPartManager(services, environment);
services.TryAddSingleton(partManager);
ConfigureDefaultFeatureProviders(partManager);
ConfigureDefaultServices(services);
AddMvcCoreServices(services);
var builder = new MvcCoreBuilder(services, partManager);
return builder;
}
private static void ConfigureDefaultFeatureProviders(ApplicationPartManager manager)
{
if (!manager.FeatureProviders.OfType<ControllerFeatureProvider>().Any())
{
manager.FeatureProviders.Add(new ControllerFeatureProvider());
}
}
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services, IWebHostEnvironment? environment)
{
var manager = GetServiceFromCollection<ApplicationPartManager>(services);
if (manager == null)
{
manager = new ApplicationPartManager();
var entryAssemblyName = environment?.ApplicationName;
if (string.IsNullOrEmpty(entryAssemblyName))
{
return manager;
}
manager.PopulateDefaultParts(entryAssemblyName);
}
return manager;
}
private static T? GetServiceFromCollection<T>(IServiceCollection services)
{
return (T?)services
.LastOrDefault(d => d.ServiceType == typeof(T))
?.ImplementationInstance;
}
/// <summary>
/// Adds the minimum essential MVC services to the specified <see cref="IServiceCollection" />. Additional services
/// including MVC's support for authorization, formatters, and validation must be added separately using the
/// <see cref="IMvcCoreBuilder"/> returned from this method.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="setupAction">An <see cref="Action{MvcOptions}"/> to configure the provided <see cref="MvcOptions"/>.</param>
/// <returns>An <see cref="IMvcCoreBuilder"/> that can be used to further configure the MVC services.</returns>
/// <remarks>
/// The <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> approach for configuring
/// MVC is provided for experienced MVC developers who wish to have full control over the set of default services
/// registered. <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/> will register
/// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any
/// application will satisfy its requirements with just a call to
/// <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>. Additional configuration using the
/// <see cref="IMvcCoreBuilder"/> will be required.
/// </remarks>
public static IMvcCoreBuilder AddMvcCore(
this IServiceCollection services,
Action<MvcOptions> setupAction)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(setupAction);
var builder = services.AddMvcCore();
services.Configure(setupAction);
return builder;
}
// To enable unit testing
internal static void AddMvcCoreServices(IServiceCollection services)
{
//
// Options
//
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, MvcCoreRouteOptionsSetup>());
//
// Action Discovery
//
// These are consumed only when creating action descriptors, then they can be deallocated
services.TryAddSingleton<ApplicationModelFactory>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, DefaultApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, ApiBehaviorApplicationModelProvider>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IActionDescriptorProvider, ControllerActionDescriptorProvider>());
services.TryAddSingleton<IActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider>();
//
// Action Selection
//
services.TryAddSingleton<IActionSelector, ActionSelector>();
services.TryAddSingleton<ActionConstraintCache>();
// Will be cached by the DefaultActionSelector
services.TryAddEnumerable(ServiceDescriptor.Transient<IActionConstraintProvider, DefaultActionConstraintProvider>());
// Policies for Endpoints
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, ActionConstraintMatcherPolicy>());
//
// Controller Factory
//
// This has a cache, so it needs to be a singleton
services.TryAddSingleton<IControllerFactory, DefaultControllerFactory>();
// Will be cached by the DefaultControllerFactory
services.TryAddTransient<IControllerActivator, DefaultControllerActivator>();
services.TryAddSingleton<IControllerFactoryProvider, ControllerFactoryProvider>();
services.TryAddSingleton<IControllerActivatorProvider, ControllerActivatorProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IControllerPropertyActivator, DefaultControllerPropertyActivator>());
//
// Action Invoker
//
// The IActionInvokerFactory is cacheable
services.TryAddSingleton<IActionInvokerFactory, ActionInvokerFactory>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());
// These are stateless
services.TryAddSingleton<ControllerActionInvokerCache>();
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IFilterProvider, DefaultFilterProvider>());
services.TryAddSingleton<IActionResultTypeMapper, ActionResultTypeMapper>();
//
// Request body limit filters
//
services.TryAddTransient<RequestSizeLimitFilter>();
services.TryAddTransient<DisableRequestSizeLimitFilter>();
services.TryAddTransient<RequestFormLimitsFilter>();
//
// ModelBinding, Validation
//
// The DefaultModelMetadataProvider does significant caching and should be a singleton.
services.TryAddSingleton<IModelMetadataProvider, DefaultModelMetadataProvider>();
services.TryAdd(ServiceDescriptor.Transient<ICompositeMetadataDetailsProvider>(s =>
{
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders);
}));
services.TryAddSingleton<IModelBinderFactory, ModelBinderFactory>();
services.TryAddSingleton<IObjectModelValidator>(s =>
{
var options = s.GetRequiredService<IOptions<MvcOptions>>().Value;
var metadataProvider = s.GetRequiredService<IModelMetadataProvider>();
return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders, options);
});
services.TryAddSingleton<ClientValidatorCache>();
services.TryAddSingleton<ParameterBinder>();
//
// Random Infrastructure
//
services.TryAddSingleton<MvcMarkerService, MvcMarkerService>();
services.TryAddSingleton<ITypeActivatorCache, TypeActivatorCache>();
services.TryAddSingleton<IUrlHelperFactory, UrlHelperFactory>();
services.TryAddSingleton<IHttpRequestStreamReaderFactory, MemoryPoolHttpRequestStreamReaderFactory>();
services.TryAddSingleton<IHttpResponseStreamWriterFactory, MemoryPoolHttpResponseStreamWriterFactory>();
services.TryAddSingleton(ArrayPool<byte>.Shared);
services.TryAddSingleton(ArrayPool<char>.Shared);
services.TryAddSingleton<OutputFormatterSelector, DefaultOutputFormatterSelector>();
services.TryAddSingleton<IActionResultExecutor<ObjectResult>, ObjectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<PhysicalFileResult>, PhysicalFileResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<VirtualFileResult>, VirtualFileResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<FileStreamResult>, FileStreamResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<FileContentResult>, FileContentResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectResult>, RedirectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<LocalRedirectResult>, LocalRedirectResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToActionResult>, RedirectToActionResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToRouteResult>, RedirectToRouteResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<RedirectToPageResult>, RedirectToPageResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<ContentResult>, ContentResultExecutor>();
services.TryAddSingleton<IActionResultExecutor<JsonResult>, SystemTextJsonResultExecutor>();
services.TryAddSingleton<IClientErrorFactory, ProblemDetailsClientErrorFactory>();
//
// Route Handlers
//
services.TryAddSingleton<MvcRouteHandler>(); // Only one per app
services.TryAddTransient<MvcAttributeRouteHandler>(); // Many per app
//
// Endpoint Routing / Endpoints
//
services.TryAddSingleton<ControllerActionEndpointDataSourceFactory>();
services.TryAddSingleton<OrderedEndpointsSequenceProviderCache>();
services.TryAddSingleton<ControllerActionEndpointDataSourceIdProvider>();
services.TryAddSingleton<ActionEndpointFactory>();
services.TryAddSingleton<DynamicControllerEndpointSelectorCache>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, DynamicControllerEndpointMatcherPolicy>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IRequestDelegateFactory, ControllerRequestDelegateFactory>());
//
// Middleware pipeline filter related
//
services.TryAddSingleton<MiddlewareFilterConfigurationProvider>();
// This maintains a cache of middleware pipelines, so it needs to be a singleton
services.TryAddSingleton<MiddlewareFilterBuilder>();
// Sets ApplicationBuilder on MiddlewareFilterBuilder
services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter, MiddlewareFilterBuilderStartupFilter>());
// ProblemDetails
services.TryAddSingleton<ProblemDetailsFactory, DefaultProblemDetailsFactory>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IProblemDetailsWriter, DefaultApiProblemDetailsWriter>());
}
private static void ConfigureDefaultServices(IServiceCollection services)
{
services.AddRouting();
}
}
|