Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
[Fixes #2648]: Fix registration of MVC services
Browse files Browse the repository at this point in the history
  • Loading branch information
kichalla committed Jun 4, 2015
1 parent 2212bfa commit c1f4bfe
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 74 deletions.
153 changes: 82 additions & 71 deletions src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,79 @@ public static IServiceCollection AddMvc([NotNull] this IServiceCollection servic
{
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>());

Expand All @@ -62,7 +131,8 @@ public static IServiceCollection AddMvc([NotNull] this IServiceCollection servic

// 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>());
Expand All @@ -76,18 +146,20 @@ public static IServiceCollection AddMvc([NotNull] this IServiceCollection servic
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>());

// 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>());
Expand Down Expand Up @@ -184,74 +256,13 @@ public static IServiceCollection AddMvc([NotNull] this IServiceCollection servic
// 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)
Expand Down
4 changes: 3 additions & 1 deletion src/Microsoft.AspNet.Mvc/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
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
Expand Down Expand Up @@ -79,6 +83,82 @@ public void WithControllersAsServices_ScansControllersFromSpecifiedAssemblies()
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)
Expand Down Expand Up @@ -110,4 +190,4 @@ public class TypeBController
{

}
}
}

0 comments on commit c1f4bfe

Please sign in to comment.