diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureContainerBuilder.cs b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureContainerBuilder.cs new file mode 100644 index 0000000000..8eb9603e3e --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureContainerBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public class ConfigureContainerBuilder + { + public ConfigureContainerBuilder(MethodInfo configureContainerMethod) + { + MethodInfo = configureContainerMethod; + } + + public MethodInfo MethodInfo { get; } + + public Action Build(object instance) => container => Invoke(instance, container); + + public Type GetContainerType() + { + var parameters = MethodInfo.GetParameters(); + if (parameters.Length != 1) + { + // REVIEW: This might be a breaking change + throw new InvalidOperationException($"The {MethodInfo.Name} method must take only one parameter."); + } + return parameters[0].ParameterType; + } + + private void Invoke(object instance, object container) + { + if (MethodInfo == null) + { + return; + } + + var arguments = new object[1] { container }; + + MethodInfo.Invoke(instance, arguments); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs index 1c05894119..d8f92fa953 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs @@ -12,19 +12,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal { public ConfigureServicesBuilder(MethodInfo configureServices) { - if (configureServices == null) - { - throw new ArgumentNullException(nameof(configureServices)); - } - - // Only support IServiceCollection parameters - var parameters = configureServices.GetParameters(); - if (parameters.Length > 1 || - parameters.Any(p => p.ParameterType != typeof(IServiceCollection))) - { - throw new InvalidOperationException("The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection."); - } - MethodInfo = configureServices; } @@ -32,22 +19,29 @@ namespace Microsoft.AspNetCore.Hosting.Internal public Func Build(object instance) => services => Invoke(instance, services); - private IServiceProvider Invoke(object instance, IServiceCollection exportServices) + private IServiceProvider Invoke(object instance, IServiceCollection services) { - if (exportServices == null) + if (MethodInfo == null) { - throw new ArgumentNullException(nameof(exportServices)); + return null; } - var parameters = new object[MethodInfo.GetParameters().Length]; + // Only support IServiceCollection parameters + var parameters = MethodInfo.GetParameters(); + if (parameters.Length > 1 || + parameters.Any(p => p.ParameterType != typeof(IServiceCollection))) + { + throw new InvalidOperationException("The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection."); + } + + var arguments = new object[MethodInfo.GetParameters().Length]; - // Ctor ensures we have at most one IServiceCollection parameter if (parameters.Length > 0) { - parameters[0] = exportServices; + arguments[0] = services; } - return MethodInfo.Invoke(instance, parameters) as IServiceProvider ?? exportServices.BuildServiceProvider(); + return MethodInfo.Invoke(instance, arguments) as IServiceProvider; } } } \ 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 6f030c069e..ba8da1d0f3 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs @@ -11,18 +11,56 @@ namespace Microsoft.AspNetCore.Hosting.Internal { public class StartupLoader { - public static StartupMethods LoadMethods(IServiceProvider services, Type startupType, string environmentName) + public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName) { var configureMethod = FindConfigureDelegate(startupType, environmentName); var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); + var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName); object instance = null; if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) { - instance = ActivatorUtilities.GetServiceOrCreateInstance(services, startupType); + instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); } - return new StartupMethods(configureMethod.Build(instance), servicesMethod?.Build(instance)); + var configureServicesCallback = servicesMethod.Build(instance); + var configureContainerCallback = configureContainerMethod.Build(instance); + + Func configureServices = services => + { + // Call ConfigureServices, if that returned an IServiceProvider, we're done + IServiceProvider applicationServiceProvider = configureServicesCallback.Invoke(services); + + if (applicationServiceProvider != null) + { + return applicationServiceProvider; + } + + // If there's a ConfigureContainer method + if (configureContainerMethod.MethodInfo != null) + { + // We have a ConfigureContainer method, get the IServiceProviderFactory + var serviceProviderFactoryType = typeof(IServiceProviderFactory<>).MakeGenericType(configureContainerMethod.GetContainerType()); + var serviceProviderFactory = hostingServiceProvider.GetRequiredService(serviceProviderFactoryType); + // var builder = serviceProviderFactory.CreateBuilder(services); + var builder = serviceProviderFactoryType.GetMethod(nameof(DefaultServiceProviderFactory.CreateBuilder)).Invoke(serviceProviderFactory, new object[] { services }); + configureContainerCallback.Invoke(builder); + // applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder); + applicationServiceProvider = (IServiceProvider)serviceProviderFactoryType.GetMethod(nameof(DefaultServiceProviderFactory.CreateServiceProvider)).Invoke(serviceProviderFactory, new object[] { builder }); + } + else + { + // Get the default factory + var serviceProviderFactory = hostingServiceProvider.GetRequiredService>(); + + // Don't bother calling CreateBuilder since it just returns the default service collection + applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(services); + } + + return applicationServiceProvider ?? services.BuildServiceProvider(); + }; + + return new StartupMethods(configureMethod.Build(instance), configureServices); } public static Type FindStartupType(string startupAssemblyName, string environmentName) @@ -30,8 +68,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal if (string.IsNullOrEmpty(startupAssemblyName)) { throw new ArgumentException( - string.Format("A startup method, startup type or startup assembly is required. If specifying an assembly, '{0}' cannot be null or empty.", - nameof(startupAssemblyName)), + string.Format("A startup method, startup type or startup assembly is required. If specifying an assembly, '{0}' cannot be null or empty.", + nameof(startupAssemblyName)), nameof(startupAssemblyName)); } @@ -83,11 +121,17 @@ namespace Microsoft.AspNetCore.Hosting.Internal return new ConfigureBuilder(configureMethod); } + private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) + { + var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false); + return new ConfigureContainerBuilder(configureMethod); + } + private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) { var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); - return servicesMethod == null ? null : new ConfigureServicesBuilder(servicesMethod); + return new ConfigureServicesBuilder(servicesMethod); } private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true) diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs b/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs index 343c215aa8..5e5e488831 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/StartupMethods.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; @@ -9,17 +10,13 @@ namespace Microsoft.AspNetCore.Hosting.Internal { public class StartupMethods { - internal static Func DefaultBuildServiceProvider = s => s.BuildServiceProvider(); - - public StartupMethods(Action configure) - : this(configure, configureServices: null) - { - } - public StartupMethods(Action configure, Func configureServices) { + Debug.Assert(configure != null); + Debug.Assert(configureServices != null); + ConfigureDelegate = configure; - ConfigureServicesDelegate = configureServices ?? DefaultBuildServiceProvider; + ConfigureServicesDelegate = configureServices; } public Func ConfigureServicesDelegate { get; } diff --git a/src/Microsoft.AspNetCore.Hosting/Startup/StartupBase.cs b/src/Microsoft.AspNetCore.Hosting/Startup/StartupBase.cs index aff4c877a6..83e65c8d9e 100644 --- a/src/Microsoft.AspNetCore.Hosting/Startup/StartupBase.cs +++ b/src/Microsoft.AspNetCore.Hosting/Startup/StartupBase.cs @@ -16,4 +16,31 @@ namespace Microsoft.AspNetCore.Hosting return services.BuildServiceProvider(); } } + + public abstract class StartupBase : IStartup + { + private readonly IServiceProviderFactory _factory; + + public StartupBase(IServiceProviderFactory factory) + { + _factory = factory; + } + + public abstract void Configure(IApplicationBuilder app); + + public virtual void ConfigureServices(IServiceCollection services) + { + + } + + IServiceProvider IStartup.ConfigureServices(IServiceCollection services) + { + ConfigureServices(services); + var builder = _factory.CreateBuilder(services); + ConfigureContainer(builder); + return _factory.CreateServiceProvider(builder); + } + + public virtual void ConfigureContainer(TContainerBuilder containerBuilder) { } + } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs index e4b2e91963..09c0983f59 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.PlatformAbstractions; @@ -200,6 +201,7 @@ namespace Microsoft.AspNetCore.Hosting // Conjure up a RequestServices services.AddTransient(); + services.AddTransient, DefaultServiceProviderFactory>(); // Ensure object pooling is available everywhere. services.AddSingleton(); diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyBadContainerFactory.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyBadContainerFactory.cs new file mode 100644 index 0000000000..058abc894a --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyBadContainerFactory.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Tests.Internal +{ + public class MyBadContainerFactory : IServiceProviderFactory + { + public MyContainer CreateBuilder(IServiceCollection services) + { + var container = new MyContainer(); + container.Populate(services); + return container; + } + + public IServiceProvider CreateServiceProvider(MyContainer containerBuilder) + { + containerBuilder.Build(); + return null; + } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyContainer.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyContainer.cs new file mode 100644 index 0000000000..97a5d63431 --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyContainer.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Tests.Internal +{ + public class MyContainer : IServiceProvider + { + private IServiceProvider _inner; + private IServiceCollection _services; + + public bool FancyMethodCalled { get; private set; } + + public string Environment { get; set; } + + public object GetService(Type serviceType) + { + return _inner.GetService(serviceType); + } + + public void Populate(IServiceCollection services) + { + _services = services; + } + + public void Build() + { + _inner = _services.BuildServiceProvider(); + } + + public void MyFancyContainerMethod() + { + FancyMethodCalled = true; + } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyContainerFactory.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyContainerFactory.cs new file mode 100644 index 0000000000..06a1ad614b --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Internal/MyContainerFactory.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Tests.Internal +{ + public class MyContainerFactory : IServiceProviderFactory + { + public MyContainer CreateBuilder(IServiceCollection services) + { + var container = new MyContainer(); + container.Populate(services); + return container; + } + + public IServiceProvider CreateServiceProvider(MyContainer containerBuilder) + { + containerBuilder.Build(); + return containerBuilder; + } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs index aa6e36ed68..974e5ffaa2 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/StartupManagerTests.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Hosting.Fakes; using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.AspNetCore.Hosting.Tests.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Xunit; @@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupClassMayHaveHostingServicesInjected() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); serviceCollection.AddSingleton(this); var services = serviceCollection.BuildServiceProvider(); @@ -46,8 +48,9 @@ namespace Microsoft.AspNetCore.Hosting.Tests [InlineData("BaseClass")] public void StartupClassAddsConfigureServicesToApplicationServices(string environment) { - var services = new ServiceCollection().BuildServiceProvider(); - + var services = new ServiceCollection() + .AddSingleton, DefaultServiceProviderFactory>() + .BuildServiceProvider(); var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", environment); var startup = StartupLoader.LoadMethods(services, type, environment); @@ -65,6 +68,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupWithNoConfigureThrows() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); serviceCollection.AddSingleton(this); var services = serviceCollection.BuildServiceProvider(); var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "Boom"); @@ -76,6 +80,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupWithTwoConfiguresThrows() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); serviceCollection.AddSingleton(this); var services = serviceCollection.BuildServiceProvider(); @@ -84,11 +89,12 @@ namespace Microsoft.AspNetCore.Hosting.Tests var ex = Assert.Throws(() => StartupLoader.LoadMethods(services, type, "TwoConfigures")); Assert.Equal("Having multiple overloads of method 'Configure' is not supported.", ex.Message); } - + [Fact] public void StartupWithPrivateConfiguresThrows() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); serviceCollection.AddSingleton(this); var services = serviceCollection.BuildServiceProvider(); @@ -103,6 +109,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupWithTwoConfigureServicesThrows() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); serviceCollection.AddSingleton(this); var services = serviceCollection.BuildServiceProvider(); @@ -116,6 +123,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupClassCanHandleConfigureServicesThatReturnsNull() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); var services = serviceCollection.BuildServiceProvider(); var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "WithNullConfigureServices"); @@ -132,6 +140,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupClassWithConfigureServicesShouldMakeServiceAvailableInConfigure() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); var services = serviceCollection.BuildServiceProvider(); var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "WithConfigureServices"); @@ -149,6 +158,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupLoaderCanLoadByType() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); var services = serviceCollection.BuildServiceProvider(); var hostingEnv = new HostingEnvironment(); @@ -166,6 +176,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests public void StartupLoaderCanLoadByTypeWithEnvironment() { var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, DefaultServiceProviderFactory>(); var services = serviceCollection.BuildServiceProvider(); var startup = StartupLoader.LoadMethods(services, typeof(TestStartup), "No"); @@ -177,6 +188,181 @@ namespace Microsoft.AspNetCore.Hosting.Tests Assert.IsAssignableFrom(typeof(InvalidOperationException), ex.InnerException); } + [Fact] + public void CustomProviderFactoryCallsConfigureContainer() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, MyContainerFactory>(); + var services = serviceCollection.BuildServiceProvider(); + + var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartup), "Development"); + + var app = new ApplicationBuilder(services); + app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); + + Assert.IsType(typeof(MyContainer), app.ApplicationServices); + Assert.True(((MyContainer)app.ApplicationServices).FancyMethodCalled); + } + + [Fact] + public void CustomServiceProviderFactoryStartupBaseClassCallsConfigureContainer() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, MyContainerFactory>(); + var services = serviceCollection.BuildServiceProvider(); + + var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartupBaseClass), "Development"); + + var app = new ApplicationBuilder(services); + app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); + + Assert.IsType(typeof(MyContainer), app.ApplicationServices); + Assert.True(((MyContainer)app.ApplicationServices).FancyMethodCalled); + } + + [Fact] + public void CustomServiceProviderFactoryEnvironmentBasedConfigureContainer() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, MyContainerFactory>(); + var services = serviceCollection.BuildServiceProvider(); + + var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartupEnvironmentBased), "Production"); + + var app = new ApplicationBuilder(services); + app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); + + Assert.IsType(typeof(MyContainer), app.ApplicationServices); + Assert.Equal(((MyContainer)app.ApplicationServices).Environment, "Production"); + } + + [Fact] + public void CustomServiceProviderFactoryThrowsIfNotRegisteredWithConfigureContainerMethod() + { + var serviceCollection = new ServiceCollection(); + var services = serviceCollection.BuildServiceProvider(); + + var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartup), "Development"); + + Assert.Throws(() => startup.ConfigureServicesDelegate(serviceCollection)); + } + + [Fact] + public void CustomServiceProviderFactoryThrowsIfNotRegisteredWithConfigureContainerMethodStartupBase() + { + var serviceCollection = new ServiceCollection(); + var services = serviceCollection.BuildServiceProvider(); + + Assert.Throws(() => StartupLoader.LoadMethods(services, typeof(MyContainerStartupBaseClass), "Development")); + } + + [Fact] + public void CustomServiceProviderFactoryFailsWithOverloadsInStartup() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, MyContainerFactory>(); + var services = serviceCollection.BuildServiceProvider(); + + Assert.Throws(() => StartupLoader.LoadMethods(services, typeof(MyContainerStartupWithOverloads), "Development")); + } + + [Fact] + public void BadServiceProviderFactoryFailsThatReturnsNullServiceProviderOverriddenByDefault() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton, MyBadContainerFactory>(); + var services = serviceCollection.BuildServiceProvider(); + + var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartup), "Development"); + + var app = new ApplicationBuilder(services); + app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); + + Assert.NotNull(app.ApplicationServices); + Assert.IsNotType(typeof(MyContainer), app.ApplicationServices); + } + + public class MyContainerStartupWithOverloads + { + public void ConfigureServices(IServiceCollection services) + { + + } + + public void ConfigureContainer(MyContainer container) + { + container.MyFancyContainerMethod(); + } + + public void ConfigureContainer(IServiceCollection services) + { + + } + + public void Configure(IApplicationBuilder app) + { + + } + } + + public class MyContainerStartupEnvironmentBased + { + public void ConfigureServices(IServiceCollection services) + { + + } + + public void ConfigureDevelopmentContainer(MyContainer container) + { + container.Environment = "Development"; + } + + public void ConfigureProductionContainer(MyContainer container) + { + container.Environment = "Production"; + } + + public void Configure(IApplicationBuilder app) + { + + } + } + + public class MyContainerStartup + { + public void ConfigureServices(IServiceCollection services) + { + + } + + public void ConfigureContainer(MyContainer container) + { + container.MyFancyContainerMethod(); + } + + public void Configure(IApplicationBuilder app) + { + + } + } + + public class MyContainerStartupBaseClass : StartupBase + { + public MyContainerStartupBaseClass(IServiceProviderFactory factory) : base(factory) + { + } + + public override void Configure(IApplicationBuilder app) + { + + } + + public override void ConfigureContainer(MyContainer containerBuilder) + { + containerBuilder.MyFancyContainerMethod(); + } + } + public class SimpleService { public SimpleService()