diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureBuilder.cs b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureBuilder.cs index 54c9384114..37b715c5b0 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureBuilder.cs @@ -21,34 +21,39 @@ namespace Microsoft.AspNetCore.Hosting.Internal private void Invoke(object instance, IApplicationBuilder builder) { - var serviceProvider = builder.ApplicationServices; - var parameterInfos = MethodInfo.GetParameters(); - var parameters = new object[parameterInfos.Length]; - for (var index = 0; index < parameterInfos.Length; index++) + // Create a scope for Configure, this allows creating scoped dependencies + // without the hassle of manually creating a scope. + using (var scope = builder.ApplicationServices.CreateScope()) { - var parameterInfo = parameterInfos[index]; - if (parameterInfo.ParameterType == typeof(IApplicationBuilder)) + var serviceProvider = scope.ServiceProvider; + var parameterInfos = MethodInfo.GetParameters(); + var parameters = new object[parameterInfos.Length]; + for (var index = 0; index < parameterInfos.Length; index++) { - parameters[index] = builder; - } - else - { - try + var parameterInfo = parameterInfos[index]; + if (parameterInfo.ParameterType == typeof(IApplicationBuilder)) { - parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType); + parameters[index] = builder; } - catch (Exception ex) + else { - throw new Exception(string.Format( - "Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.", - parameterInfo.ParameterType.FullName, - parameterInfo.Name, - MethodInfo.Name, - MethodInfo.DeclaringType.FullName), ex); + try + { + parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType); + } + catch (Exception ex) + { + throw new Exception(string.Format( + "Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.", + parameterInfo.ParameterType.FullName, + parameterInfo.Name, + MethodInfo.Name, + MethodInfo.DeclaringType.FullName), ex); + } } } + MethodInfo.Invoke(instance, parameters); } - MethodInfo.Invoke(instance, parameters); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs b/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs index ba8da1d0f3..cc65dd300b 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal return applicationServiceProvider ?? services.BuildServiceProvider(); }; - return new StartupMethods(configureMethod.Build(instance), configureServices); + return new StartupMethods(instance, configureMethod.Build(instance), configureServices); } public static Type FindStartupType(string startupAssemblyName, string environmentName) diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs b/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs index 5e5e488831..f854c85946 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs @@ -10,15 +10,17 @@ namespace Microsoft.AspNetCore.Hosting.Internal { public class StartupMethods { - public StartupMethods(Action configure, Func configureServices) + public StartupMethods(object instance, Action configure, Func configureServices) { Debug.Assert(configure != null); Debug.Assert(configureServices != null); + StartupInstance = instance; ConfigureDelegate = configure; ConfigureServicesDelegate = configureServices; } + public object StartupInstance { get; } public Func ConfigureServicesDelegate { get; } public Action ConfigureDelegate { get; } diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/StartupWithScopedServices.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/StartupWithScopedServices.cs new file mode 100644 index 0000000000..985f920473 --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/StartupWithScopedServices.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Builder; +using static Microsoft.AspNetCore.Hosting.Tests.StartupManagerTests; + +namespace Microsoft.AspNetCore.Hosting.Fakes +{ + public class StartupWithScopedServices + { + public DisposableService DisposableService { get; set; } + + public void Configure(IApplicationBuilder builder, DisposableService disposable) + { + DisposableService = disposable; + } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs index 31ba1c9062..fc655799c6 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs @@ -36,6 +36,31 @@ namespace Microsoft.AspNetCore.Hosting.Tests Assert.Equal(2, callbackStartup.MethodsCalled); } + [Fact] + public void StartupClassMayHaveScopedServicesInjected() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton>(new DefaultServiceProviderFactory(new ServiceProviderOptions + { + ValidateScopes = true + })); + + serviceCollection.AddScoped(); + var services = serviceCollection.BuildServiceProvider(); + + var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "WithScopedServices"); + var startup = StartupLoader.LoadMethods(services, type, "WithScopedServices"); + Assert.NotNull(startup.StartupInstance); + + var app = new ApplicationBuilder(services); + app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); + startup.ConfigureDelegate(app); + + var instance = (StartupWithScopedServices)startup.StartupInstance; + Assert.NotNull(instance.DisposableService); + Assert.True(instance.DisposableService.Disposed); + } + [Theory] [InlineData(null)] [InlineData("Dev")] @@ -405,5 +430,15 @@ namespace Microsoft.AspNetCore.Hosting.Tests _configurationMethodCalledList.Add(instance); } } + + public class DisposableService : IDisposable + { + public bool Disposed { get; set; } + + public void Dispose() + { + Disposed = true; + } + } } }