[Fixes #2648]: Fix registration of MVC services

This commit is contained in:
Kiran Challa 2015-06-03 11:53:36 -07:00
parent 2212bfa6be
commit c1f4bfedab
3 changed files with 167 additions and 74 deletions

View File

@ -34,10 +34,79 @@ namespace Microsoft.Framework.DependencyInjection
{
ConfigureDefaultServices(services);
AddMvcServices(services);
return services;
}
/// <summary>
/// Configures a set of <see cref="MvcOptions"/> for the application.
/// </summary>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">The <see cref="MvcOptions"/> which need to be configured.</param>
public static void ConfigureMvc(
[NotNull] this IServiceCollection services,
[NotNull] Action<MvcOptions> setupAction)
{
services.Configure(setupAction);
}
/// <summary>
/// Register the specified <paramref name="controllerTypes"/> as services and as a source for controller
/// discovery.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="controllerTypes">A sequence of controller <see cref="Type"/>s to register in the
/// <paramref name="services"/> and used for controller discovery.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection WithControllersAsServices(
[NotNull] this IServiceCollection services,
[NotNull] IEnumerable<Type> controllerTypes)
{
var controllerTypeProvider = new FixedSetControllerTypeProvider();
foreach (var type in controllerTypes)
{
services.TryAdd(ServiceDescriptor.Transient(type, type));
controllerTypeProvider.ControllerTypes.Add(type.GetTypeInfo());
}
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
services.Replace(ServiceDescriptor.Instance<IControllerTypeProvider>(controllerTypeProvider));
return services;
}
/// <summary>
/// Registers controller types from the specified <paramref name="assemblies"/> as services and as a source
/// for controller discovery.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="controllerAssemblies">Assemblies to scan.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection WithControllersAsServices(
[NotNull] this IServiceCollection services,
[NotNull] IEnumerable<Assembly> controllerAssemblies)
{
var assemblyProvider = new FixedSetAssemblyProvider();
foreach (var assembly in controllerAssemblies)
{
assemblyProvider.CandidateAssemblies.Add(assembly);
}
var controllerTypeProvider = new DefaultControllerTypeProvider(assemblyProvider);
var controllerTypes = controllerTypeProvider.ControllerTypes;
return WithControllersAsServices(services, controllerTypes.Select(type => type.AsType()));
}
// To enable unit testing
internal static void AddMvcServices(IServiceCollection services)
{
// Options and core services.
services.TryAdd(ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcOptionsSetup>());
services.TryAdd(
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
// multiple registration service
services.AddTransient<IConfigureOptions<MvcOptions>, MvcOptionsSetup>();
// multiple registration service
services.AddTransient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>();
services.TryAdd(ServiceDescriptor.Transient<IAssemblyProvider, DefaultAssemblyProvider>());
@ -62,7 +131,8 @@ namespace Microsoft.Framework.DependencyInjection
// This provider needs access to the per-request services, but might be used many times for a given
// request.
services.TryAdd(ServiceDescriptor.Transient<IActionConstraintProvider, DefaultActionConstraintProvider>());
// multiple registration service
services.AddTransient<IActionConstraintProvider, DefaultActionConstraintProvider>();
services.TryAdd(ServiceDescriptor
.Singleton<IActionSelectorDecisionTreeProvider, ActionSelectorDecisionTreeProvider>());
@ -76,10 +146,11 @@ namespace Microsoft.Framework.DependencyInjection
return new DefaultObjectValidator(options.ValidationExcludeFilters, modelMetadataProvider);
}));
services.TryAdd(ServiceDescriptor
.Transient<IActionDescriptorProvider, ControllerActionDescriptorProvider>());
// multiple registration service
services.AddTransient<IActionDescriptorProvider, ControllerActionDescriptorProvider>();
services.TryAdd(ServiceDescriptor.Transient<IActionInvokerProvider, ControllerActionInvokerProvider>());
// multiple registration service
services.AddTransient<IActionInvokerProvider, ControllerActionInvokerProvider>();
services.TryAdd(ServiceDescriptor
.Singleton<IActionDescriptorsCollectionProvider, DefaultActionDescriptorsCollectionProvider>());
@ -87,7 +158,8 @@ namespace Microsoft.Framework.DependencyInjection
// The IGlobalFilterProvider is used to build the action descriptors (likely once) and so should
// remain transient to avoid keeping it in memory.
services.TryAdd(ServiceDescriptor.Transient<IGlobalFilterProvider, DefaultGlobalFilterProvider>());
services.TryAdd(ServiceDescriptor.Transient<IFilterProvider, DefaultFilterProvider>());
// multiple registration service
services.AddTransient<IFilterProvider, DefaultFilterProvider>();
services.TryAdd(ServiceDescriptor.Transient<FormatFilter, FormatFilter>());
services.TryAdd(ServiceDescriptor.Transient<CorsAuthorizationFilter, CorsAuthorizationFilter>());
@ -184,74 +256,13 @@ namespace Microsoft.Framework.DependencyInjection
// Api Description
services.TryAdd(ServiceDescriptor
.Singleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>());
services.TryAdd(ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>());
// multiple registration service
services.AddTransient<IApiDescriptionProvider, DefaultApiDescriptionProvider>();
// Temp Data
services.TryAdd(ServiceDescriptor.Scoped<ITempDataDictionary, TempDataDictionary>());
// This does caching so it should stay singleton
services.TryAdd(ServiceDescriptor.Singleton<ITempDataProvider, SessionStateTempDataProvider>());
return services;
}
/// <summary>
/// Configures a set of <see cref="MvcOptions"/> for the application.
/// </summary>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">The <see cref="MvcOptions"/> which need to be configured.</param>
public static void ConfigureMvc(
[NotNull] this IServiceCollection services,
[NotNull] Action<MvcOptions> setupAction)
{
services.Configure(setupAction);
}
/// <summary>
/// Register the specified <paramref name="controllerTypes"/> as services and as a source for controller
/// discovery.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="controllerTypes">A sequence of controller <see cref="Type"/>s to register in the
/// <paramref name="services"/> and used for controller discovery.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection WithControllersAsServices(
[NotNull] this IServiceCollection services,
[NotNull] IEnumerable<Type> controllerTypes)
{
var controllerTypeProvider = new FixedSetControllerTypeProvider();
foreach (var type in controllerTypes)
{
services.TryAdd(ServiceDescriptor.Transient(type, type));
controllerTypeProvider.ControllerTypes.Add(type.GetTypeInfo());
}
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
services.Replace(ServiceDescriptor.Instance<IControllerTypeProvider>(controllerTypeProvider));
return services;
}
/// <summary>
/// Registers controller types from the specified <paramref name="assemblies"/> as services and as a source
/// for controller discovery.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="controllerAssemblies">Assemblies to scan.</param>
/// <returns>The <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection WithControllersAsServices(
[NotNull] this IServiceCollection services,
[NotNull] IEnumerable<Assembly> controllerAssemblies)
{
var assemblyProvider = new FixedSetAssemblyProvider();
foreach (var assembly in controllerAssemblies)
{
assemblyProvider.CandidateAssemblies.Add(assembly);
}
var controllerTypeProvider = new DefaultControllerTypeProvider(assemblyProvider);
var controllerTypes = controllerTypeProvider.ControllerTypes;
return WithControllersAsServices(services, controllerTypes.Select(type => type.AsType()));
}
private static void ConfigureDefaultServices(IServiceCollection services)

View File

@ -2,5 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: AssemblyMetadata("Serviceable", "True")]
[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Test")]

View File

@ -5,9 +5,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc.ActionConstraints;
using Microsoft.AspNet.Mvc.ApiExplorer;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.MvcServiceCollectionExtensionsTestControllers;
using Microsoft.Framework.Configuration;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Xunit;
namespace Microsoft.AspNet.Mvc
@ -79,6 +83,82 @@ namespace Microsoft.AspNet.Mvc
Assert.Equal(ServiceLifetime.Singleton, services[3].Lifetime);
}
// Some MVC services can be registered multiple times, for example, 'IConfigureOptions<MvcOptions>' can
// be registered by calling 'ConfigureMvc(...)' before the call to 'AddMvc()' in which case the options
// configiuration is run in the order they were registered.
// For these kind of multi registration service types, we want to make sure that MVC appends its services
// to the list i.e it does a 'Add' rather than 'TryAdd' on the ServiceCollection.
[Fact]
public void MultiRegistrationServiceTypes_AreRegistered_MultipleTimes()
{
// Arrange
var services = new ServiceCollection();
var multiRegistrationServiceTypes = MutliRegistrationServiceTypes;
// Act
MvcServiceCollectionExtensions.AddMvcServices(services);
MvcServiceCollectionExtensions.AddMvcServices(services);
// Assert
foreach (var serviceType in multiRegistrationServiceTypes)
{
AssertServiceCountEquals(services, serviceType, 2);
}
}
[Fact]
public void SingleRegistrationServiceTypes_AreNotRegistered_MultipleTimes()
{
// Arrange
var services = new ServiceCollection();
var multiRegistrationServiceTypes = MutliRegistrationServiceTypes;
// Act
MvcServiceCollectionExtensions.AddMvcServices(services);
MvcServiceCollectionExtensions.AddMvcServices(services);
// Assert
var singleRegistrationServiceTypes = services
.Where(serviceDescriptor => !multiRegistrationServiceTypes.Contains(serviceDescriptor.ServiceType))
.Select(serviceDescriptor => serviceDescriptor.ServiceType);
foreach (var singleRegistrationType in singleRegistrationServiceTypes)
{
AssertServiceCountEquals(services, singleRegistrationType, 1);
}
}
private IEnumerable<Type> MutliRegistrationServiceTypes
{
get
{
return new[]
{
typeof(IConfigureOptions<MvcOptions>),
typeof(IConfigureOptions<RazorViewEngineOptions>),
typeof(IActionConstraintProvider),
typeof(IActionDescriptorProvider),
typeof(IActionInvokerProvider),
typeof(IFilterProvider),
typeof(IApiDescriptionProvider)
};
}
}
private void AssertServiceCountEquals(
IServiceCollection services,
Type serviceType,
int expectedServiceRegistrationCount)
{
var serviceDescriptors = services.Where(serviceDescriptor => serviceDescriptor.ServiceType == serviceType);
var actual = serviceDescriptors.Count();
Assert.True(
(expectedServiceRegistrationCount == actual),
$"Expected service type '{serviceType}' to be registered {expectedServiceRegistrationCount}" +
$" time(s) but was actually registered {actual} time(s).");
}
private class CustomActivator : IControllerActivator
{
public object Create(ActionContext context, Type controllerType)
@ -110,4 +190,4 @@ namespace Microsoft.AspNet.Mvc.MvcServiceCollectionExtensionsTestControllers
{
}
}
}