@@ -34,7 +34,8 @@ public void AddEndpoints(
34
34
List < Endpoint > endpoints ,
35
35
ActionDescriptor action ,
36
36
IReadOnlyList < ConventionalRouteEntry > routes ,
37
- IReadOnlyList < Action < EndpointBuilder > > conventions )
37
+ IReadOnlyList < Action < EndpointBuilder > > conventions ,
38
+ bool createInertEndpoints )
38
39
{
39
40
if ( endpoints == null )
40
41
{
@@ -56,6 +57,17 @@ public void AddEndpoints(
56
57
throw new ArgumentNullException ( nameof ( conventions ) ) ;
57
58
}
58
59
60
+ if ( createInertEndpoints )
61
+ {
62
+ // For each ActionDescriptor create a single 'inert' Endpoint without routing information.
63
+ //
64
+ // This makes those endpoints accessible for dynamic and fallback routing. We don't want to
65
+ // mix RouteEndpoint that is selected by routing, vs Endpoint that is selected by user code/policies
66
+ //
67
+ // This also helps us avoid ambiguities when an endpoint has multiple conventional routes.
68
+ endpoints . Add ( CreateInertEndpoint ( action , conventions ) ) ;
69
+ }
70
+
59
71
if ( action . AttributeRouteInfo == null )
60
72
{
61
73
// In traditional conventional routing setup, the routes defined by a user have a static order
@@ -181,32 +193,54 @@ private RouteEndpoint CreateEndpoint(
181
193
bool suppressPathMatching ,
182
194
IReadOnlyList < Action < EndpointBuilder > > conventions )
183
195
{
184
-
185
- // We don't want to close over the retrieve the Invoker Factory in ActionEndpointFactory as
186
- // that creates cycles in DI. Since we're creating this delegate at startup time
187
- // we don't want to create all of the things we use at runtime until the action
188
- // actually matches.
189
- //
190
- // The request delegate is already a closure here because we close over
191
- // the action descriptor.
192
- IActionInvokerFactory invokerFactory = null ;
193
-
194
- RequestDelegate requestDelegate = ( context ) =>
196
+ var builder = new RouteEndpointBuilder ( CreateRequestDelegate ( action ) , routePattern , order )
195
197
{
196
- var routeData = context . GetRouteData ( ) ;
197
- var actionContext = new ActionContext ( context , routeData , action ) ;
198
+ DisplayName = action . DisplayName ,
199
+ } ;
198
200
199
- if ( invokerFactory == null )
201
+ // Add action metadata first so it has a low precedence
202
+ if ( action . EndpointMetadata != null )
203
+ {
204
+ foreach ( var d in action . EndpointMetadata )
200
205
{
201
- invokerFactory = context . RequestServices . GetRequiredService < IActionInvokerFactory > ( ) ;
206
+ builder . Metadata . Add ( d ) ;
202
207
}
208
+ }
203
209
204
- var invoker = invokerFactory . CreateInvoker ( actionContext ) ;
205
- return invoker . InvokeAsync ( ) ;
206
- } ;
210
+ builder . Metadata . Add ( action ) ;
211
+
212
+ if ( dataTokens != null )
213
+ {
214
+ builder . Metadata . Add ( new DataTokensMetadata ( dataTokens ) ) ;
215
+ }
207
216
208
- var builder = new RouteEndpointBuilder ( requestDelegate , routePattern , order )
217
+ builder . Metadata . Add ( new RouteNameMetadata ( routeName ) ) ;
218
+
219
+ AddMetadataFromActionDescriptor ( builder , action ) ;
220
+
221
+ if ( suppressLinkGeneration )
209
222
{
223
+ builder . Metadata . Add ( new SuppressLinkGenerationMetadata ( ) ) ;
224
+ }
225
+
226
+ if ( suppressPathMatching )
227
+ {
228
+ builder . Metadata . Add ( new SuppressMatchingMetadata ( ) ) ;
229
+ }
230
+
231
+ for ( var i = 0 ; i < conventions . Count ; i ++ )
232
+ {
233
+ conventions [ i ] ( builder ) ;
234
+ }
235
+
236
+ return ( RouteEndpoint ) builder . Build ( ) ;
237
+ }
238
+
239
+ private Endpoint CreateInertEndpoint ( ActionDescriptor action , IReadOnlyList < Action < EndpointBuilder > > conventions )
240
+ {
241
+ var builder = new InertEndpointBuilder ( )
242
+ {
243
+ RequestDelegate = CreateRequestDelegate ( action ) ,
210
244
DisplayName = action . DisplayName ,
211
245
} ;
212
246
@@ -221,13 +255,44 @@ private RouteEndpoint CreateEndpoint(
221
255
222
256
builder . Metadata . Add ( action ) ;
223
257
224
- if ( dataTokens != null )
258
+ AddMetadataFromActionDescriptor ( builder , action ) ;
259
+
260
+ for ( var i = 0 ; i < conventions . Count ; i ++ )
225
261
{
226
- builder . Metadata . Add ( new DataTokensMetadata ( dataTokens ) ) ;
262
+ conventions [ i ] ( builder ) ;
227
263
}
228
264
229
- builder . Metadata . Add ( new RouteNameMetadata ( routeName ) ) ;
265
+ return builder . Build ( ) ;
266
+ }
230
267
268
+ private RequestDelegate CreateRequestDelegate ( ActionDescriptor action )
269
+ {
270
+ // We don't want to close over the retrieve the Invoker Factory in ActionEndpointFactory as
271
+ // that creates cycles in DI. Since we're creating this delegate at startup time
272
+ // we don't want to create all of the things we use at runtime until the action
273
+ // actually matches.
274
+ //
275
+ // The request delegate is already a closure here because we close over
276
+ // the action descriptor.
277
+ IActionInvokerFactory invokerFactory = null ;
278
+
279
+ return ( context ) =>
280
+ {
281
+ var routeData = context . GetRouteData ( ) ;
282
+ var actionContext = new ActionContext ( context , routeData , action ) ;
283
+
284
+ if ( invokerFactory == null )
285
+ {
286
+ invokerFactory = context . RequestServices . GetRequiredService < IActionInvokerFactory > ( ) ;
287
+ }
288
+
289
+ var invoker = invokerFactory . CreateInvoker ( actionContext ) ;
290
+ return invoker . InvokeAsync ( ) ;
291
+ } ;
292
+ }
293
+
294
+ private void AddMetadataFromActionDescriptor ( EndpointBuilder builder , ActionDescriptor action )
295
+ {
231
296
// Add filter descriptors to endpoint metadata
232
297
if ( action . FilterDescriptors != null && action . FilterDescriptors . Count > 0 )
233
298
{
@@ -263,23 +328,14 @@ private RouteEndpoint CreateEndpoint(
263
328
}
264
329
}
265
330
}
331
+ }
266
332
267
- if ( suppressLinkGeneration )
268
- {
269
- builder . Metadata . Add ( new SuppressLinkGenerationMetadata ( ) ) ;
270
- }
271
-
272
- if ( suppressPathMatching )
273
- {
274
- builder . Metadata . Add ( new SuppressMatchingMetadata ( ) ) ;
275
- }
276
-
277
- for ( var i = 0 ; i < conventions . Count ; i ++ )
333
+ private class InertEndpointBuilder : EndpointBuilder
334
+ {
335
+ public override Endpoint Build ( )
278
336
{
279
- conventions [ i ] ( builder ) ;
337
+ return new Endpoint ( RequestDelegate , new EndpointMetadataCollection ( Metadata ) , DisplayName ) ;
280
338
}
281
-
282
- return ( RouteEndpoint ) builder . Build ( ) ;
283
339
}
284
340
}
285
341
}
0 commit comments