Skip to content

Add support for instantiating the startup class #24144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISuppo
{
private readonly IHostBuilder _builder;
private readonly IConfiguration _config;
private object _startupObject;
private readonly object _startupKey = new object();

private AggregateException _hostingStartupErrors;
Expand Down Expand Up @@ -198,10 +199,12 @@ public IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, S
public IWebHostBuilder UseStartup(Type startupType)
{
// UseStartup can be called multiple times. Only run the last one.
_builder.Properties["UseStartup.StartupType"] = startupType;
_startupObject = startupType;

_builder.ConfigureServices((context, services) =>
{
if (_builder.Properties.TryGetValue("UseStartup.StartupType", out var cachedType) && (Type)cachedType == startupType)
// Run this delegate if the startup type matches
if (object.ReferenceEquals(_startupObject, startupType))
{
UseStartup(startupType, context, services);
}
Expand All @@ -210,13 +213,31 @@ public IWebHostBuilder UseStartup(Type startupType)
return this;
}

private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
{
// Clear the startup type
_startupObject = startupFactory;

_builder.ConfigureServices((context, services) =>
{
// UseStartup can be called multiple times. Only run the last one.
if (object.ReferenceEquals(_startupObject, startupFactory))
{
var webHostBuilderContext = GetWebHostBuilderContext(context);
var instance = startupFactory(webHostBuilderContext) ?? throw new InvalidOperationException("The specified factory returned null startup instance.");
UseStartup(instance.GetType(), context, services, instance);
}
});

return this;
}

private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null)
{
var webHostBuilderContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];

ExceptionDispatchInfo startupError = null;
object instance = null;
ConfigureBuilder configureBuilder = null;

try
Expand All @@ -231,7 +252,7 @@ private void UseStartup(Type startupType, HostBuilderContext context, IServiceCo
throw new NotSupportedException($"ConfigureServices returning an {typeof(IServiceProvider)} isn't supported.");
}

instance = ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);
instance ??= ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);
context.Properties[_startupKey] = instance;

// Startup.ConfigureServices
Expand Down Expand Up @@ -296,13 +317,19 @@ private void ConfigureContainer<TContainer>(HostBuilderContext context, TContain

public IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure)
{
// Clear the startup type
_startupObject = configure;

_builder.ConfigureServices((context, services) =>
{
services.Configure<GenericWebHostServiceOptions>(options =>
if (object.ReferenceEquals(_startupObject, configure))
{
var webhostBuilderContext = GetWebHostBuilderContext(context);
options.ConfigureApplication = app => configure(webhostBuilderContext, app);
});
services.Configure<GenericWebHostServiceOptions>(options =>
{
var webhostBuilderContext = GetWebHostBuilderContext(context);
options.ConfigureApplication = app => configure(webhostBuilderContext, app);
});
}
});

return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,10 @@ public IWebHostBuilder UseStartup(Type startupType)
{
return _builder.UseStartup(startupType);
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
{
return _builder.UseStartup(startupFactory);
}
}
}
1 change: 1 addition & 0 deletions src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ internal interface ISupportsStartup
{
IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure);
IWebHostBuilder UseStartup(Type startupType);
IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory);
}
}
11 changes: 5 additions & 6 deletions src/Hosting/Hosting/src/Internal/StartupLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ internal class StartupLoader
//
// If the Startup class ConfigureServices returns an <see cref="IServiceProvider"/> and there is at least an <see cref="IStartupConfigureServicesFilter"/> registered we
// throw as the filters can't be applied.
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName, object instance = null)
{
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))
if (instance == null && (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)))
{
instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
}
Expand All @@ -54,7 +53,7 @@ public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider
// going to be used for anything.
var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object);

var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
var builder = (ConfigureServicesDelegateBuilder)Activator.CreateInstance(
typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
hostingServiceProvider,
servicesMethod,
Expand Down Expand Up @@ -104,13 +103,13 @@ Action<object> ConfigureContainerPipeline(Action<object> action)

// The ConfigureContainer pipeline needs an Action<TContainerBuilder> as source, so we just adapt the
// signature with this function.
void Source(TContainerBuilder containerBuilder) =>
void Source(TContainerBuilder containerBuilder) =>
action(containerBuilder);

// The ConfigureContainerBuilder.ConfigureContainerFilters expects an Action<object> as value, but our pipeline
// produces an Action<TContainerBuilder> given a source, so we wrap it on an Action<object> that internally casts
// the object containerBuilder to TContainerBuilder to match the expected signature of our ConfigureContainer pipeline.
void Target(object containerBuilder) =>
void Target(object containerBuilder) =>
BuildStartupConfigureContainerFiltersPipeline(Source)((TContainerBuilder)containerBuilder);
}
}
Expand Down
47 changes: 47 additions & 0 deletions src/Hosting/Hosting/src/WebHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,48 @@ private static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Actio
});
}

/// <summary>
/// Specify a factory that creates the startup instance to be used by the web host.
/// </summary>
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, object> startupFactory)
{
if (startupFactory == null)
{
throw new ArgumentNullException(nameof(startupFactory));
}

var startupAssemblyName = startupFactory.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name;

hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);

// Light up the GenericWebHostBuilder implementation
if (hostBuilder is ISupportsStartup supportsStartup)
{
return supportsStartup.UseStartup(startupFactory);
}

return hostBuilder
.ConfigureServices((context, services) =>
{
services.AddSingleton(typeof(IStartup), sp =>
{
var instance = startupFactory(context) ?? throw new InvalidOperationException("The specified factory returned null startup instance.");

var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();

// Check if the instance implements IStartup before wrapping
if (instance is IStartup startup)
{
return startup;
}

return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, instance.GetType(), hostingEnvironment.EnvironmentName, instance));
});
});
}

/// <summary>
/// Specify the startup type to be used by the web host.
Expand All @@ -70,6 +112,11 @@ private static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Actio
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
if (startupType == null)
{
throw new ArgumentNullException(nameof(startupType));
}

var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;

hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,11 @@ public IWebHostBuilder UseStartup(Type startupType)
_builder.UseStartup(startupType);
return this;
}

public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
{
_builder.UseStartup(startupFactory);
return this;
}
}
}
Loading