1
+ using System ;
2
+ using System . IO ;
3
+ using System . Threading . Tasks ;
4
+ using CommandDotNet . Execution ;
5
+ using Microsoft . Extensions . Configuration ;
6
+ using Microsoft . Extensions . DependencyInjection ;
7
+ using Microsoft . Extensions . Hosting ;
8
+
9
+ namespace CommandDotNet . Hosting
10
+ {
11
+ public static class HostingExtensions
12
+ {
13
+ public static AppRunner UseHost ( this AppRunner runner ,
14
+ string [ ] ? args = null ,
15
+ Action < IHostBuilder > ? configureHost = null ,
16
+ Func < IHostBuilder > hostBuilderFactory = null ,
17
+ bool supportReplSessions = false )
18
+ {
19
+ var hostBuilder = hostBuilderFactory ? . Invoke ( ) ?? Host . CreateDefaultBuilder ( args ) ;
20
+
21
+ hostBuilder . ConfigureHostConfiguration ( hostConfig =>
22
+ {
23
+ hostConfig . SetBasePath ( Directory . GetCurrentDirectory ( ) ) ;
24
+ } ) ;
25
+
26
+ configureHost ? . Invoke ( hostBuilder ) ;
27
+ return runner . UseHost ( hostBuilder , supportReplSessions ) ;
28
+ }
29
+
30
+ public static AppRunner UseHost ( this AppRunner runner , IHostBuilder hostBuilder , bool supportReplSessions = false )
31
+ {
32
+ if ( hostBuilder == null )
33
+ {
34
+ throw new ArgumentNullException ( nameof ( hostBuilder ) ) ;
35
+ }
36
+
37
+ return runner . Configure ( cfg =>
38
+ {
39
+ hostBuilder . ConfigureServices ( services =>
40
+ {
41
+ if ( supportReplSessions )
42
+ {
43
+ services . AddScoped < ScopedServices > ( ) ;
44
+ services . AddScoped ( p => p . GetRequiredService < ScopedServices > ( ) . CommandContext ! ) ;
45
+ services . AddScoped ( p => p . GetRequiredService < ScopedServices > ( ) . CommandContext ! . Console ) ;
46
+ }
47
+ else
48
+ {
49
+ services . AddSingleton < ScopedServices > ( ) ;
50
+ services . AddSingleton ( p => p . GetRequiredService < ScopedServices > ( ) . CommandContext ! ) ;
51
+ services . AddSingleton ( p => p . GetRequiredService < ScopedServices > ( ) . CommandContext ! . Console ) ;
52
+ }
53
+ } ) ;
54
+
55
+ var host = hostBuilder . Build ( ) ;
56
+
57
+ cfg . UseMiddleware ( RunHost , MiddlewareSteps . CancellationHandler + 1000 ) ;
58
+ cfg . UseMiddleware ( InjectCommandContextForDI , MiddlewareSteps . CancellationHandler + 1001 ) ;
59
+ cfg . Services . Add ( new Config ( host , supportReplSessions ) ) ;
60
+ } ) ;
61
+ }
62
+
63
+ private static async Task < int > RunHost ( CommandContext context , ExecutionDelegate next )
64
+ {
65
+ var config = context . AppConfig . Services . GetOrThrow < Config > ( ) ;
66
+
67
+ if ( config . IsStarted )
68
+ {
69
+ // this call is within a REPL session.
70
+ // do not recreate the host
71
+ return await next ( context ) ;
72
+ }
73
+
74
+ config . IsStarted = true ;
75
+ await config . Host . StartAsync ( ) ;
76
+ var result = await next ( context ) ;
77
+ await config . Host . StopAsync ( ) ;
78
+
79
+ return result ;
80
+ }
81
+
82
+ private class Config
83
+ {
84
+ public bool IsStarted ;
85
+ public IHost Host { get ; }
86
+ public bool SupportReplSessions { get ; }
87
+
88
+ public Config ( IHost host , bool supportReplSessions )
89
+ {
90
+ Host = host ?? throw new ArgumentNullException ( nameof ( host ) ) ;
91
+ SupportReplSessions = supportReplSessions ;
92
+ }
93
+ }
94
+
95
+ private class ScopedServices
96
+ {
97
+ public CommandContext ? CommandContext ;
98
+ }
99
+
100
+ private static Task < int > InjectCommandContextForDI ( CommandContext context , ExecutionDelegate next )
101
+ {
102
+ var config = context . AppConfig . Services . GetOrThrow < Config > ( ) ;
103
+ var serviceProvider = config . Host . Services ;
104
+
105
+ if ( config . SupportReplSessions )
106
+ {
107
+ using var scope = serviceProvider . CreateScope ( ) ;
108
+ scope . ServiceProvider . GetRequiredService < ScopedServices > ( ) . CommandContext = context ;
109
+ return next ( context ) ;
110
+ }
111
+
112
+ serviceProvider . GetRequiredService < ScopedServices > ( ) . CommandContext = context ;
113
+ return next ( context ) ;
114
+ }
115
+ }
116
+ }
0 commit comments