1
+ // Licensed to the .NET Foundation under one or more agreements.
2
+ // The .NET Foundation licenses this file to you under the MIT license.
3
+
4
+ using System ;
5
+ using System . Collections . Generic ;
6
+ using System . Diagnostics ;
7
+ using System . Reflection ;
8
+ using System . Threading ;
9
+ using System . Threading . Tasks ;
10
+
11
+ #nullable enable
12
+
13
+ namespace Microsoft . Extensions . Hosting
14
+ {
15
+ internal sealed class HostFactoryResolver
16
+ {
17
+ private const BindingFlags DeclaredOnlyLookup = BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance | BindingFlags . Static | BindingFlags . DeclaredOnly ;
18
+
19
+ public const string BuildWebHost = nameof ( BuildWebHost ) ;
20
+ public const string CreateWebHostBuilder = nameof ( CreateWebHostBuilder ) ;
21
+ public const string CreateHostBuilder = nameof ( CreateHostBuilder ) ;
22
+
23
+ // The amount of time we wait for the diagnostic source events to fire
24
+ private static readonly TimeSpan s_defaultWaitTimeout = TimeSpan . FromSeconds ( 5 ) ;
25
+
26
+ public static Func < string [ ] , TWebHost > ? ResolveWebHostFactory < TWebHost > ( Assembly assembly )
27
+ {
28
+ return ResolveFactory < TWebHost > ( assembly , BuildWebHost ) ;
29
+ }
30
+
31
+ public static Func < string [ ] , TWebHostBuilder > ? ResolveWebHostBuilderFactory < TWebHostBuilder > ( Assembly assembly )
32
+ {
33
+ return ResolveFactory < TWebHostBuilder > ( assembly , CreateWebHostBuilder ) ;
34
+ }
35
+
36
+ public static Func < string [ ] , THostBuilder > ? ResolveHostBuilderFactory < THostBuilder > ( Assembly assembly )
37
+ {
38
+ return ResolveFactory < THostBuilder > ( assembly , CreateHostBuilder ) ;
39
+ }
40
+
41
+ // This helpers encapsulates all of the complex logic required to:
42
+ // 1. Execute the entry point of the specified assembly in a different thread.
43
+ // 2. Wait for the diagnostic source events to fire
44
+ // 3. Give the caller a chance to execute logic to mutate the IHostBuilder
45
+ // 4. Resolve the instance of the applications's IHost
46
+ // 5. Allow the caller to determine if the entry point has completed
47
+ public static Func < string [ ] , object > ? ResolveHostFactory ( Assembly assembly ,
48
+ TimeSpan ? waitTimeout = null ,
49
+ bool stopApplication = true ,
50
+ Action < object > ? configureHostBuilder = null ,
51
+ Action < Exception ? > ? entrypointCompleted = null )
52
+ {
53
+ if ( assembly . EntryPoint is null )
54
+ {
55
+ return null ;
56
+ }
57
+
58
+ try
59
+ {
60
+ // Attempt to load hosting and check the version to make sure the events
61
+ // even have a chance of firing (they were added in .NET >= 6)
62
+ var hostingAssembly = Assembly . Load ( "Microsoft.Extensions.Hosting" ) ;
63
+ if ( hostingAssembly . GetName ( ) . Version is Version version && version . Major < 6 )
64
+ {
65
+ return null ;
66
+ }
67
+
68
+ // We're using a version >= 6 so the events can fire. If they don't fire
69
+ // then it's because the application isn't using the hosting APIs
70
+ }
71
+ catch
72
+ {
73
+ // There was an error loading the extensions assembly, return null.
74
+ return null ;
75
+ }
76
+
77
+ return args => new HostingListener ( args , assembly . EntryPoint , waitTimeout ?? s_defaultWaitTimeout , stopApplication , configureHostBuilder , entrypointCompleted ) . CreateHost ( ) ;
78
+ }
79
+
80
+ private static Func < string [ ] , T > ? ResolveFactory < T > ( Assembly assembly , string name )
81
+ {
82
+ var programType = assembly ? . EntryPoint ? . DeclaringType ;
83
+ if ( programType == null )
84
+ {
85
+ return null ;
86
+ }
87
+
88
+ var factory = programType . GetMethod ( name , DeclaredOnlyLookup ) ;
89
+ if ( ! IsFactory < T > ( factory ) )
90
+ {
91
+ return null ;
92
+ }
93
+
94
+ return args => ( T ) factory ! . Invoke ( null , new object [ ] { args } ) ! ;
95
+ }
96
+
97
+ // TReturn Factory(string[] args);
98
+ private static bool IsFactory < TReturn > ( MethodInfo ? factory )
99
+ {
100
+ return factory != null
101
+ && typeof ( TReturn ) . IsAssignableFrom ( factory . ReturnType )
102
+ && factory . GetParameters ( ) . Length == 1
103
+ && typeof ( string [ ] ) . Equals ( factory . GetParameters ( ) [ 0 ] . ParameterType ) ;
104
+ }
105
+
106
+ // Used by EF tooling without any Hosting references. Looses some return type safety checks.
107
+ public static Func < string [ ] , IServiceProvider ? > ? ResolveServiceProviderFactory ( Assembly assembly , TimeSpan ? waitTimeout = null )
108
+ {
109
+ // Prefer the older patterns by default for back compat.
110
+ var webHostFactory = ResolveWebHostFactory < object > ( assembly ) ;
111
+ if ( webHostFactory != null )
112
+ {
113
+ return args =>
114
+ {
115
+ var webHost = webHostFactory ( args ) ;
116
+ return GetServiceProvider ( webHost ) ;
117
+ } ;
118
+ }
119
+
120
+ var webHostBuilderFactory = ResolveWebHostBuilderFactory < object > ( assembly ) ;
121
+ if ( webHostBuilderFactory != null )
122
+ {
123
+ return args =>
124
+ {
125
+ var webHostBuilder = webHostBuilderFactory ( args ) ;
126
+ var webHost = Build ( webHostBuilder ) ;
127
+ return GetServiceProvider ( webHost ) ;
128
+ } ;
129
+ }
130
+
131
+ var hostBuilderFactory = ResolveHostBuilderFactory < object > ( assembly ) ;
132
+ if ( hostBuilderFactory != null )
133
+ {
134
+ return args =>
135
+ {
136
+ var hostBuilder = hostBuilderFactory ( args ) ;
137
+ var host = Build ( hostBuilder ) ;
138
+ return GetServiceProvider ( host ) ;
139
+ } ;
140
+ }
141
+
142
+ var hostFactory = ResolveHostFactory ( assembly , waitTimeout : waitTimeout ) ;
143
+ if ( hostFactory != null )
144
+ {
145
+ return args =>
146
+ {
147
+ var host = hostFactory ( args ) ;
148
+ return GetServiceProvider ( host ) ;
149
+ } ;
150
+ }
151
+
152
+ return null ;
153
+ }
154
+
155
+ private static object ? Build ( object builder )
156
+ {
157
+ var buildMethod = builder . GetType ( ) . GetMethod ( "Build" ) ;
158
+ return buildMethod ? . Invoke ( builder , Array . Empty < object > ( ) ) ;
159
+ }
160
+
161
+ private static IServiceProvider ? GetServiceProvider ( object ? host )
162
+ {
163
+ if ( host == null )
164
+ {
165
+ return null ;
166
+ }
167
+ var hostType = host . GetType ( ) ;
168
+ var servicesProperty = hostType . GetProperty ( "Services" , DeclaredOnlyLookup ) ;
169
+ return ( IServiceProvider ? ) servicesProperty ? . GetValue ( host ) ;
170
+ }
171
+
172
+ private sealed class HostingListener : IObserver < DiagnosticListener > , IObserver < KeyValuePair < string , object ? > >
173
+ {
174
+ private readonly string [ ] _args ;
175
+ private readonly MethodInfo _entryPoint ;
176
+ private readonly TimeSpan _waitTimeout ;
177
+ private readonly bool _stopApplication ;
178
+
179
+ private readonly TaskCompletionSource < object > _hostTcs = new ( ) ;
180
+ private IDisposable ? _disposable ;
181
+ private Action < object > ? _configure ;
182
+ private Action < Exception ? > ? _entrypointCompleted ;
183
+ private static readonly AsyncLocal < HostingListener > _currentListener = new ( ) ;
184
+
185
+ public HostingListener ( string [ ] args , MethodInfo entryPoint , TimeSpan waitTimeout , bool stopApplication , Action < object > ? configure , Action < Exception ? > ? entrypointCompleted )
186
+ {
187
+ _args = args ;
188
+ _entryPoint = entryPoint ;
189
+ _waitTimeout = waitTimeout ;
190
+ _stopApplication = stopApplication ;
191
+ _configure = configure ;
192
+ _entrypointCompleted = entrypointCompleted ;
193
+ }
194
+
195
+ public object CreateHost ( )
196
+ {
197
+ using var subscription = DiagnosticListener . AllListeners . Subscribe ( this ) ;
198
+
199
+ // Kick off the entry point on a new thread so we don't block the current one
200
+ // in case we need to timeout the execution
201
+ var thread = new Thread ( ( ) =>
202
+ {
203
+ Exception ? exception = null ;
204
+
205
+ try
206
+ {
207
+ // Set the async local to the instance of the HostingListener so we can filter events that
208
+ // aren't scoped to this execution of the entry point.
209
+ _currentListener . Value = this ;
210
+
211
+ var parameters = _entryPoint . GetParameters ( ) ;
212
+ if ( parameters . Length == 0 )
213
+ {
214
+ _entryPoint . Invoke ( null , Array . Empty < object > ( ) ) ;
215
+ }
216
+ else
217
+ {
218
+ _entryPoint . Invoke ( null , new object [ ] { _args } ) ;
219
+ }
220
+
221
+ // Try to set an exception if the entry point returns gracefully, this will force
222
+ // build to throw
223
+ _hostTcs . TrySetException ( new InvalidOperationException ( "Unable to build IHost" ) ) ;
224
+ }
225
+ catch ( TargetInvocationException tie ) when ( tie . InnerException is StopTheHostException )
226
+ {
227
+ // The host was stopped by our own logic
228
+ }
229
+ catch ( TargetInvocationException tie )
230
+ {
231
+ exception = tie . InnerException ?? tie ;
232
+
233
+ // Another exception happened, propagate that to the caller
234
+ _hostTcs . TrySetException ( exception ) ;
235
+ }
236
+ catch ( Exception ex )
237
+ {
238
+ exception = ex ;
239
+
240
+ // Another exception happened, propagate that to the caller
241
+ _hostTcs . TrySetException ( ex ) ;
242
+ }
243
+ finally
244
+ {
245
+ // Signal that the entry point is completed
246
+ _entrypointCompleted ? . Invoke ( exception ) ;
247
+ }
248
+ } )
249
+ {
250
+ // Make sure this doesn't hang the process
251
+ IsBackground = true
252
+ } ;
253
+
254
+ // Start the thread
255
+ thread . Start ( ) ;
256
+
257
+ try
258
+ {
259
+ // Wait before throwing an exception
260
+ if ( ! _hostTcs . Task . Wait ( _waitTimeout ) )
261
+ {
262
+ throw new InvalidOperationException ( "Unable to build IHost" ) ;
263
+ }
264
+ }
265
+ catch ( AggregateException ) when ( _hostTcs . Task . IsCompleted )
266
+ {
267
+ // Lets this propagate out of the call to GetAwaiter().GetResult()
268
+ }
269
+
270
+ Debug . Assert ( _hostTcs . Task . IsCompleted ) ;
271
+
272
+ return _hostTcs . Task . GetAwaiter ( ) . GetResult ( ) ;
273
+ }
274
+
275
+ public void OnCompleted ( )
276
+ {
277
+ _disposable ? . Dispose ( ) ;
278
+ }
279
+
280
+ public void OnError ( Exception error )
281
+ {
282
+
283
+ }
284
+
285
+ public void OnNext ( DiagnosticListener value )
286
+ {
287
+ if ( _currentListener . Value != this )
288
+ {
289
+ // Ignore events that aren't for this listener
290
+ return ;
291
+ }
292
+
293
+ if ( value . Name == "Microsoft.Extensions.Hosting" )
294
+ {
295
+ _disposable = value . Subscribe ( this ) ;
296
+ }
297
+ }
298
+
299
+ public void OnNext ( KeyValuePair < string , object ? > value )
300
+ {
301
+ if ( _currentListener . Value != this )
302
+ {
303
+ // Ignore events that aren't for this listener
304
+ return ;
305
+ }
306
+
307
+ if ( value . Key == "HostBuilding" )
308
+ {
309
+ _configure ? . Invoke ( value . Value ! ) ;
310
+ }
311
+
312
+ if ( value . Key == "HostBuilt" )
313
+ {
314
+ _hostTcs . TrySetResult ( value . Value ! ) ;
315
+
316
+ if ( _stopApplication )
317
+ {
318
+ // Stop the host from running further
319
+ throw new StopTheHostException ( ) ;
320
+ }
321
+ }
322
+ }
323
+
324
+ private sealed class StopTheHostException : Exception
325
+ {
326
+
327
+ }
328
+ }
329
+ }
330
+ }
0 commit comments