Skip to content

Commit 2325981

Browse files
committed
Add support for instantiating the startup class
- Adds an overload of UseStartup that takes a factory so users can control the instance creation. The factory is given the WebHostBuilderContext to expose access to configuration and IWebHostEnvironment. - Make sure only one startup delegate runs, the last one registered.
1 parent b62124e commit 2325981

File tree

6 files changed

+79
-15
lines changed

6 files changed

+79
-15
lines changed

src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISuppo
2121
{
2222
private readonly IHostBuilder _builder;
2323
private readonly IConfiguration _config;
24+
private object _startupObject;
2425
private readonly object _startupKey = new object();
2526

2627
private AggregateException _hostingStartupErrors;
@@ -198,10 +199,12 @@ public IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, S
198199
public IWebHostBuilder UseStartup(Type startupType)
199200
{
200201
// UseStartup can be called multiple times. Only run the last one.
201-
_builder.Properties["UseStartup.StartupType"] = startupType;
202+
_startupObject = startupType;
203+
202204
_builder.ConfigureServices((context, services) =>
203205
{
204-
if (_builder.Properties.TryGetValue("UseStartup.StartupType", out var cachedType) && (Type)cachedType == startupType)
206+
// Run this delegate if the startup type matches
207+
if (object.ReferenceEquals(_startupObject, startupType))
205208
{
206209
UseStartup(startupType, context, services);
207210
}
@@ -210,13 +213,30 @@ public IWebHostBuilder UseStartup(Type startupType)
210213
return this;
211214
}
212215

213-
private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
216+
public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
217+
{
218+
// Clear the startup type
219+
_startupObject = startupFactory;
220+
221+
_builder.ConfigureServices((context, services) =>
222+
{
223+
if (object.ReferenceEquals(_startupObject, startupFactory))
224+
{
225+
var webHostBuilderContext = GetWebHostBuilderContext(context);
226+
var instance = startupFactory(webHostBuilderContext) ?? new InvalidOperationException("The specified factory returned null startup instance");
227+
UseStartup(instance.GetType(), context, services, instance);
228+
}
229+
});
230+
231+
return this;
232+
}
233+
234+
private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services, object instance = null)
214235
{
215236
var webHostBuilderContext = GetWebHostBuilderContext(context);
216237
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
217238

218239
ExceptionDispatchInfo startupError = null;
219-
object instance = null;
220240
ConfigureBuilder configureBuilder = null;
221241

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

234-
instance = ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);
254+
instance ??= ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);
235255
context.Properties[_startupKey] = instance;
236256

237257
// Startup.ConfigureServices
@@ -296,13 +316,19 @@ private void ConfigureContainer<TContainer>(HostBuilderContext context, TContain
296316

297317
public IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure)
298318
{
319+
// Clear the startup type
320+
_startupObject = configure;
321+
299322
_builder.ConfigureServices((context, services) =>
300323
{
301-
services.Configure<GenericWebHostServiceOptions>(options =>
324+
if (object.ReferenceEquals(_startupObject, configure))
302325
{
303-
var webhostBuilderContext = GetWebHostBuilderContext(context);
304-
options.ConfigureApplication = app => configure(webhostBuilderContext, app);
305-
});
326+
services.Configure<GenericWebHostServiceOptions>(options =>
327+
{
328+
var webhostBuilderContext = GetWebHostBuilderContext(context);
329+
options.ConfigureApplication = app => configure(webhostBuilderContext, app);
330+
});
331+
}
306332
});
307333

308334
return this;

src/Hosting/Hosting/src/GenericHost/HostingStartupWebHostBuilder.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,10 @@ public IWebHostBuilder UseStartup(Type startupType)
7575
{
7676
return _builder.UseStartup(startupType);
7777
}
78+
79+
public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
80+
{
81+
return _builder.UseStartup(startupFactory);
82+
}
7883
}
7984
}

src/Hosting/Hosting/src/GenericHost/ISupportsStartup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ internal interface ISupportsStartup
1010
{
1111
IWebHostBuilder Configure(Action<WebHostBuilderContext, IApplicationBuilder> configure);
1212
IWebHostBuilder UseStartup(Type startupType);
13+
IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory);
1314
}
1415
}

src/Hosting/Hosting/src/Internal/StartupLoader.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,14 @@ internal class StartupLoader
3737
//
3838
// If the Startup class ConfigureServices returns an <see cref="IServiceProvider"/> and there is at least an <see cref="IStartupConfigureServicesFilter"/> registered we
3939
// throw as the filters can't be applied.
40-
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
40+
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName, object instance = null)
4141
{
4242
var configureMethod = FindConfigureDelegate(startupType, environmentName);
4343

4444
var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
4545
var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);
4646

47-
object instance = null;
48-
if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
47+
if (instance == null && (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)))
4948
{
5049
instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
5150
}
@@ -54,7 +53,7 @@ public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider
5453
// going to be used for anything.
5554
var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object);
5655

57-
var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
56+
var builder = (ConfigureServicesDelegateBuilder)Activator.CreateInstance(
5857
typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
5958
hostingServiceProvider,
6059
servicesMethod,
@@ -104,13 +103,13 @@ Action<object> ConfigureContainerPipeline(Action<object> action)
104103

105104
// The ConfigureContainer pipeline needs an Action<TContainerBuilder> as source, so we just adapt the
106105
// signature with this function.
107-
void Source(TContainerBuilder containerBuilder) =>
106+
void Source(TContainerBuilder containerBuilder) =>
108107
action(containerBuilder);
109108

110109
// The ConfigureContainerBuilder.ConfigureContainerFilters expects an Action<object> as value, but our pipeline
111110
// produces an Action<TContainerBuilder> given a source, so we wrap it on an Action<object> that internally casts
112111
// the object containerBuilder to TContainerBuilder to match the expected signature of our ConfigureContainer pipeline.
113-
void Target(object containerBuilder) =>
112+
void Target(object containerBuilder) =>
114113
BuildStartupConfigureContainerFiltersPipeline(Source)((TContainerBuilder)containerBuilder);
115114
}
116115
}

src/Hosting/Hosting/src/WebHostBuilderExtensions.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,33 @@ private static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Actio
6161
});
6262
}
6363

64+
/// <summary>
65+
/// Specify a factory that creates the startup instance to be used by the web host.
66+
/// </summary>
67+
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
68+
/// <param name="startupFactory">A delegate that specifies a factory for the startup class.</param>
69+
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
70+
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Func<WebHostBuilderContext, object> startupFactory)
71+
{
72+
// Light up the GenericWebHostBuilder implementation
73+
if (hostBuilder is ISupportsStartup supportsStartup)
74+
{
75+
return supportsStartup.UseStartup(startupFactory);
76+
}
77+
78+
return hostBuilder
79+
.ConfigureServices((context, services) =>
80+
{
81+
services.AddSingleton(typeof(IStartup), sp =>
82+
{
83+
var instance = startupFactory(context) ?? new InvalidOperationException("The specified factory returned null startup instance");
84+
85+
var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
86+
87+
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, instance.GetType(), hostingEnvironment.EnvironmentName, instance));
88+
});
89+
});
90+
}
6491

6592
/// <summary>
6693
/// Specify the startup type to be used by the web host.

src/Hosting/Hosting/test/Fakes/GenericWebHostBuilderWrapper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,11 @@ public IWebHostBuilder UseStartup(Type startupType)
7373
_builder.UseStartup(startupType);
7474
return this;
7575
}
76+
77+
public IWebHostBuilder UseStartup(Func<WebHostBuilderContext, object> startupFactory)
78+
{
79+
_builder.UseStartup(startupFactory);
80+
return this;
81+
}
7682
}
7783
}

0 commit comments

Comments
 (0)