4
4
using System . Diagnostics ;
5
5
using System . Diagnostics . CodeAnalysis ;
6
6
using System . Runtime . CompilerServices ;
7
+ using Microsoft . AspNetCore . Authorization ;
8
+ using Microsoft . AspNetCore . Cors . Infrastructure ;
7
9
using Microsoft . AspNetCore . Http ;
8
10
using Microsoft . AspNetCore . Routing . Matching ;
11
+ using Microsoft . AspNetCore . Routing . ShortCircuit ;
9
12
using Microsoft . Extensions . Logging ;
13
+ using Microsoft . Extensions . Options ;
10
14
11
15
namespace Microsoft . AspNetCore . Routing ;
12
16
@@ -19,7 +23,7 @@ internal sealed partial class EndpointRoutingMiddleware
19
23
private readonly EndpointDataSource _endpointDataSource ;
20
24
private readonly DiagnosticListener _diagnosticListener ;
21
25
private readonly RequestDelegate _next ;
22
-
26
+ private readonly RouteOptions _routeOptions ;
23
27
private Task < Matcher > ? _initializationTask ;
24
28
25
29
public EndpointRoutingMiddleware (
@@ -28,6 +32,7 @@ public EndpointRoutingMiddleware(
28
32
IEndpointRouteBuilder endpointRouteBuilder ,
29
33
EndpointDataSource rootCompositeEndpointDataSource ,
30
34
DiagnosticListener diagnosticListener ,
35
+ IOptions < RouteOptions > routeOptions ,
31
36
RequestDelegate next )
32
37
{
33
38
ArgumentNullException . ThrowIfNull ( endpointRouteBuilder ) ;
@@ -36,6 +41,7 @@ public EndpointRoutingMiddleware(
36
41
_logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
37
42
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException ( nameof ( diagnosticListener ) ) ;
38
43
_next = next ?? throw new ArgumentNullException ( nameof ( next ) ) ;
44
+ _routeOptions = routeOptions . Value ;
39
45
40
46
// rootCompositeEndpointDataSource is a constructor parameter only so it always gets disposed by DI. This ensures that any
41
47
// disposable EndpointDataSources also get disposed. _endpointDataSource is a component of rootCompositeEndpointDataSource.
@@ -102,6 +108,12 @@ private Task SetRoutingAndContinue(HttpContext httpContext)
102
108
}
103
109
104
110
Log . MatchSuccess ( _logger , endpoint ) ;
111
+
112
+ var shortCircuitMetadata = endpoint . Metadata . GetMetadata < ShortCircuitMetadata > ( ) ;
113
+ if ( shortCircuitMetadata is not null )
114
+ {
115
+ return ExecuteShortCircuit ( shortCircuitMetadata , endpoint , httpContext ) ;
116
+ }
105
117
}
106
118
107
119
return _next ( httpContext ) ;
@@ -115,6 +127,75 @@ static void Write(DiagnosticListener diagnosticListener, HttpContext httpContext
115
127
}
116
128
}
117
129
130
+ private Task ExecuteShortCircuit ( ShortCircuitMetadata shortCircuitMetadata , Endpoint endpoint , HttpContext httpContext )
131
+ {
132
+ // This check should be kept in sync with the one in EndpointMiddleware
133
+ if ( ! _routeOptions . SuppressCheckForUnhandledSecurityMetadata )
134
+ {
135
+ if ( endpoint . Metadata . GetMetadata < IAuthorizeData > ( ) is not null )
136
+ {
137
+ ThrowCannotShortCircuitAnAuthRouteException ( endpoint ) ;
138
+ }
139
+
140
+ if ( endpoint . Metadata . GetMetadata < ICorsMetadata > ( ) is not null )
141
+ {
142
+ ThrowCannotShortCircuitACorsRouteException ( endpoint ) ;
143
+ }
144
+ }
145
+
146
+ if ( shortCircuitMetadata . StatusCode . HasValue )
147
+ {
148
+ httpContext . Response . StatusCode = shortCircuitMetadata . StatusCode . Value ;
149
+ }
150
+
151
+ if ( endpoint . RequestDelegate is not null )
152
+ {
153
+ if ( ! _logger . IsEnabled ( LogLevel . Information ) )
154
+ {
155
+ // Avoid the AwaitRequestTask state machine allocation if logging is disabled.
156
+ return endpoint . RequestDelegate ( httpContext ) ;
157
+ }
158
+
159
+ Log . ExecutingEndpoint ( _logger , endpoint ) ;
160
+
161
+ try
162
+ {
163
+ var requestTask = endpoint . RequestDelegate ( httpContext ) ;
164
+ if ( ! requestTask . IsCompletedSuccessfully )
165
+ {
166
+ return AwaitRequestTask ( endpoint , requestTask , _logger ) ;
167
+ }
168
+ }
169
+ catch
170
+ {
171
+ Log . ExecutedEndpoint ( _logger , endpoint ) ;
172
+ throw ;
173
+ }
174
+
175
+ Log . ExecutedEndpoint ( _logger , endpoint ) ;
176
+
177
+ return Task . CompletedTask ;
178
+
179
+ static async Task AwaitRequestTask ( Endpoint endpoint , Task requestTask , ILogger logger )
180
+ {
181
+ try
182
+ {
183
+ await requestTask ;
184
+ }
185
+ finally
186
+ {
187
+ Log . ExecutedEndpoint ( logger , endpoint ) ;
188
+ }
189
+ }
190
+
191
+ }
192
+ else
193
+ {
194
+ Log . ShortCircuitedEndpoint ( _logger , endpoint ) ;
195
+ }
196
+ return Task . CompletedTask ;
197
+ }
198
+
118
199
// Initialization is async to avoid blocking threads while reflection and things
119
200
// of that nature take place.
120
201
//
@@ -165,6 +246,18 @@ private Task<Matcher> InitializeCoreAsync()
165
246
}
166
247
}
167
248
249
+ private static void ThrowCannotShortCircuitAnAuthRouteException ( Endpoint endpoint )
250
+ {
251
+ throw new InvalidOperationException ( $ "Endpoint { endpoint . DisplayName } contains authorization metadata, " +
252
+ "but this endpoint is marked with short circuit and it will execute on Routing Middleware." ) ;
253
+ }
254
+
255
+ private static void ThrowCannotShortCircuitACorsRouteException ( Endpoint endpoint )
256
+ {
257
+ throw new InvalidOperationException ( $ "Endpoint { endpoint . DisplayName } contains CORS metadata, " +
258
+ "but this endpoint is marked with short circuit and it will execute on Routing Middleware." ) ;
259
+ }
260
+
168
261
private static partial class Log
169
262
{
170
263
public static void MatchSuccess ( ILogger logger , Endpoint endpoint )
@@ -181,5 +274,14 @@ public static void MatchSkipped(ILogger logger, Endpoint endpoint)
181
274
182
275
[ LoggerMessage ( 3 , LogLevel . Debug , "Endpoint '{EndpointName}' already set, skipping route matching." , EventName = "MatchingSkipped" ) ]
183
276
private static partial void MatchingSkipped ( ILogger logger , string ? endpointName ) ;
277
+
278
+ [ LoggerMessage ( 4 , LogLevel . Information , "The endpoint '{EndpointName}' is being executed without running additional middleware." , EventName = "ExecutingEndpoint" ) ]
279
+ public static partial void ExecutingEndpoint ( ILogger logger , Endpoint endpointName ) ;
280
+
281
+ [ LoggerMessage ( 5 , LogLevel . Information , "The endpoint '{EndpointName}' has been executed without running additional middleware." , EventName = "ExecutedEndpoint" ) ]
282
+ public static partial void ExecutedEndpoint ( ILogger logger , Endpoint endpointName ) ;
283
+
284
+ [ LoggerMessage ( 6 , LogLevel . Information , "The endpoint '{EndpointName}' is being short circuited without running additional middleware or producing a response." , EventName = "ShortCircuitedEndpoint" ) ]
285
+ public static partial void ShortCircuitedEndpoint ( ILogger logger , Endpoint endpointName ) ;
184
286
}
185
287
}
0 commit comments