1+ using Microsoft . AspNetCore . Http ;
2+ using Microsoft . Extensions . Logging ;
3+ using System ;
4+ using System . Collections . Generic ;
5+ using System . Net ;
6+ using System . Reflection ;
7+ using System . Text ;
8+ using System . Threading . Tasks ;
9+
10+ namespace MicroBatchFramework . WebHosting
11+ {
12+ internal class WebHostingInterceptor : IBatchInterceptor
13+ {
14+ readonly IBatchInterceptor innerInterceptor ;
15+
16+ public bool CompleteSuccessfully { get ; private set ; }
17+ public string ErrorMessage { get ; private set ; }
18+ public Exception Exception { get ; private set ; }
19+
20+ public WebHostingInterceptor ( IBatchInterceptor innerInterceptor )
21+ {
22+ this . innerInterceptor = innerInterceptor ;
23+ }
24+
25+ public ValueTask OnBatchEngineBeginAsync ( IServiceProvider serviceProvider , ILogger < BatchEngine > logger )
26+ {
27+ return innerInterceptor . OnBatchEngineBeginAsync ( serviceProvider , logger ) ;
28+ }
29+
30+ public ValueTask OnBatchEngineEndAsync ( )
31+ {
32+ return innerInterceptor . OnBatchEngineEndAsync ( ) ;
33+ }
34+
35+ public ValueTask OnBatchRunBeginAsync ( BatchContext context )
36+ {
37+ return innerInterceptor . OnBatchRunBeginAsync ( context ) ;
38+ }
39+
40+ public ValueTask OnBatchRunCompleteAsync ( BatchContext context , string errorMessageIfFailed , Exception exceptionIfExists )
41+ {
42+ this . CompleteSuccessfully = ( errorMessageIfFailed == null && exceptionIfExists == null ) ;
43+ this . ErrorMessage = errorMessageIfFailed ;
44+ this . Exception = exceptionIfExists ;
45+ return innerInterceptor . OnBatchRunCompleteAsync ( context , errorMessageIfFailed , exceptionIfExists ) ;
46+ }
47+ }
48+
49+ internal class LogCollector : ILogger < BatchEngine >
50+ {
51+ readonly ILogger < BatchEngine > innerLogger ;
52+ readonly StringBuilder sb ;
53+
54+ public LogCollector ( ILogger < BatchEngine > innerLogger )
55+ {
56+ this . innerLogger = innerLogger ;
57+ this . sb = new StringBuilder ( ) ;
58+ }
59+
60+ public IDisposable BeginScope < TState > ( TState state )
61+ {
62+ return innerLogger . BeginScope ( state ) ;
63+ }
64+
65+ public bool IsEnabled ( LogLevel logLevel )
66+ {
67+ return true ;
68+ }
69+
70+ public void Log < TState > ( LogLevel logLevel , EventId eventId , TState state , Exception exception , Func < TState , Exception , string > formatter )
71+ {
72+ var msg = formatter ( state , exception ) ;
73+ lock ( sb )
74+ {
75+ sb . AppendLine ( msg ) ;
76+ }
77+
78+ innerLogger . Log < TState > ( logLevel , eventId , state , exception , formatter ) ;
79+ }
80+
81+ public override string ToString ( )
82+ {
83+ lock ( sb )
84+ {
85+ return sb . ToString ( ) ;
86+ }
87+ }
88+ }
89+
90+ public class BatchEngineMiddleware
91+ {
92+ readonly RequestDelegate next ;
93+ readonly IServiceProvider provider ;
94+ readonly ILogger < BatchEngine > logger ;
95+ readonly IBatchInterceptor interceptor ;
96+
97+ readonly Dictionary < string , MethodInfo > methodLookup ;
98+
99+ public BatchEngineMiddleware ( RequestDelegate next , ILogger < BatchEngine > logger , IBatchInterceptor interceptor , IServiceProvider provider , TargetBatchTypeCollection targetTypes )
100+ {
101+ this . next = next ;
102+ this . logger = logger ;
103+ this . interceptor = interceptor ;
104+ this . provider = provider ;
105+ this . methodLookup = BuildMethodLookup ( targetTypes ) ;
106+ }
107+
108+ public async Task Invoke ( HttpContext httpContext )
109+ {
110+ var path = httpContext . Request . Path . Value ;
111+ if ( ! methodLookup . TryGetValue ( path , out var methodInfo ) )
112+ {
113+ await next ( httpContext ) ;
114+ return ;
115+ }
116+
117+ // create args
118+ string [ ] args = null ;
119+ try
120+ {
121+ args = new string [ ( httpContext . Request . Form . Count * 2 ) + 1 ] ;
122+ {
123+ var i = 0 ;
124+ args [ i ++ ] = methodInfo . DeclaringType . Name + "." + methodInfo . Name ;
125+ foreach ( var item in httpContext . Request . Form )
126+ {
127+ args [ i ++ ] = "-" + item . Key ;
128+ args [ i ++ ] = ( item . Value . Count == 0 ) ? null : item . Value [ 0 ] ;
129+ }
130+ }
131+ }
132+ catch ( Exception ex )
133+ {
134+ httpContext . Response . StatusCode = ( int ) HttpStatusCode . InternalServerError ;
135+ await httpContext . Response . WriteAsync ( ex . ToString ( ) ) ;
136+ return ;
137+ }
138+
139+ // run with collect statuses
140+ var hostingInterceptor = new WebHostingInterceptor ( interceptor ) ;
141+ var collectLogger = new LogCollector ( logger ) ;
142+
143+ var engine = new BatchEngine ( collectLogger , provider , hostingInterceptor , httpContext . RequestAborted ) ;
144+ await engine . RunAsync ( methodInfo . DeclaringType , methodInfo , args ) ;
145+
146+ // out result
147+ if ( hostingInterceptor . CompleteSuccessfully )
148+ {
149+ httpContext . Response . StatusCode = ( int ) HttpStatusCode . OK ;
150+ await httpContext . Response . WriteAsync ( collectLogger . ToString ( ) ) ;
151+ }
152+ else
153+ {
154+ var errorMsg = ( ( hostingInterceptor . ErrorMessage != null ) ? hostingInterceptor . ErrorMessage + Environment . NewLine : "" )
155+ + ( ( hostingInterceptor . Exception != null ) ? hostingInterceptor . Exception . ToString ( ) : "" ) ;
156+ httpContext . Response . StatusCode = ( int ) HttpStatusCode . InternalServerError ;
157+ await httpContext . Response . WriteAsync ( errorMsg ) ;
158+ }
159+ }
160+
161+ static Dictionary < string , MethodInfo > BuildMethodLookup ( IEnumerable < Type > batchTypes )
162+ {
163+ var methods = new Dictionary < string , MethodInfo > ( ) ;
164+
165+ foreach ( var type in batchTypes )
166+ {
167+ foreach ( var item in type . GetMethods ( BindingFlags . Public | BindingFlags . Instance | BindingFlags . DeclaredOnly ) )
168+ {
169+ methods . Add ( "/" + type . Name + "/" + item . Name , item ) ;
170+ }
171+ }
172+
173+ return methods ;
174+ }
175+ }
176+ }
0 commit comments