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
+}