diff --git a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs index 37bb13f21b..25b6379e63 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServiceCollectionExtensions.cs @@ -34,10 +34,79 @@ namespace Microsoft.Framework.DependencyInjection { ConfigureDefaultServices(services); + AddMvcServices(services); + + return services; + } + + /// + /// Configures a set of for the application. + /// + /// The services available in the application. + /// The which need to be configured. + public static void ConfigureMvc( + [NotNull] this IServiceCollection services, + [NotNull] Action setupAction) + { + services.Configure(setupAction); + } + + /// + /// Register the specified as services and as a source for controller + /// discovery. + /// + /// The . + /// A sequence of controller s to register in the + /// and used for controller discovery. + /// The . + public static IServiceCollection WithControllersAsServices( + [NotNull] this IServiceCollection services, + [NotNull] IEnumerable 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()); + services.Replace(ServiceDescriptor.Instance(controllerTypeProvider)); + + return services; + } + + /// + /// Registers controller types from the specified as services and as a source + /// for controller discovery. + /// + /// The . + /// Assemblies to scan. + /// The . + public static IServiceCollection WithControllersAsServices( + [NotNull] this IServiceCollection services, + [NotNull] IEnumerable 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, MvcOptionsSetup>()); - services.TryAdd( - ServiceDescriptor.Transient, RazorViewEngineOptionsSetup>()); + // multiple registration service + services.AddTransient, MvcOptionsSetup>(); + // multiple registration service + services.AddTransient, RazorViewEngineOptionsSetup>(); services.TryAdd(ServiceDescriptor.Transient()); @@ -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()); + // multiple registration service + services.AddTransient(); services.TryAdd(ServiceDescriptor .Singleton()); @@ -76,10 +146,11 @@ namespace Microsoft.Framework.DependencyInjection return new DefaultObjectValidator(options.ValidationExcludeFilters, modelMetadataProvider); })); - services.TryAdd(ServiceDescriptor - .Transient()); + // multiple registration service + services.AddTransient(); - services.TryAdd(ServiceDescriptor.Transient()); + // multiple registration service + services.AddTransient(); services.TryAdd(ServiceDescriptor .Singleton()); @@ -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()); - services.TryAdd(ServiceDescriptor.Transient()); + // multiple registration service + services.AddTransient(); services.TryAdd(ServiceDescriptor.Transient()); services.TryAdd(ServiceDescriptor.Transient()); @@ -184,74 +256,13 @@ namespace Microsoft.Framework.DependencyInjection // Api Description services.TryAdd(ServiceDescriptor .Singleton()); - services.TryAdd(ServiceDescriptor.Transient()); + // multiple registration service + services.AddTransient(); // Temp Data services.TryAdd(ServiceDescriptor.Scoped()); // This does caching so it should stay singleton services.TryAdd(ServiceDescriptor.Singleton()); - - return services; - } - - /// - /// Configures a set of for the application. - /// - /// The services available in the application. - /// The which need to be configured. - public static void ConfigureMvc( - [NotNull] this IServiceCollection services, - [NotNull] Action setupAction) - { - services.Configure(setupAction); - } - - /// - /// Register the specified as services and as a source for controller - /// discovery. - /// - /// The . - /// A sequence of controller s to register in the - /// and used for controller discovery. - /// The . - public static IServiceCollection WithControllersAsServices( - [NotNull] this IServiceCollection services, - [NotNull] IEnumerable 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()); - services.Replace(ServiceDescriptor.Instance(controllerTypeProvider)); - - return services; - } - - /// - /// Registers controller types from the specified as services and as a source - /// for controller discovery. - /// - /// The . - /// Assemblies to scan. - /// The . - public static IServiceCollection WithControllersAsServices( - [NotNull] this IServiceCollection services, - [NotNull] IEnumerable 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) diff --git a/src/Microsoft.AspNet.Mvc/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Mvc/Properties/AssemblyInfo.cs index 025a94598c..1fd2520817 100644 --- a/src/Microsoft.AspNet.Mvc/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNet.Mvc/Properties/AssemblyInfo.cs @@ -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")] \ No newline at end of file +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Test")] \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index 675c648bd2..8772d6a874 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -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' 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 MutliRegistrationServiceTypes + { + get + { + return new[] + { + typeof(IConfigureOptions), + typeof(IConfigureOptions), + 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 { } -} \ No newline at end of file +}