@@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
2020 internal class ActionEndpointFactory
2121 {
2222 private readonly RoutePatternTransformer _routePatternTransformer ;
23+ private readonly RequestDelegate _requestDelegate ;
2324
2425 public ActionEndpointFactory ( RoutePatternTransformer routePatternTransformer )
2526 {
@@ -29,14 +30,16 @@ public ActionEndpointFactory(RoutePatternTransformer routePatternTransformer)
2930 }
3031
3132 _routePatternTransformer = routePatternTransformer ;
33+ _requestDelegate = CreateRequestDelegate ( ) ;
3234 }
3335
3436 public void AddEndpoints (
3537 List < Endpoint > endpoints ,
3638 HashSet < string > routeNames ,
3739 ActionDescriptor action ,
3840 IReadOnlyList < ConventionalRouteEntry > routes ,
39- IReadOnlyList < Action < EndpointBuilder > > conventions )
41+ IReadOnlyList < Action < EndpointBuilder > > conventions ,
42+ bool createInertEndpoints )
4043 {
4144 if ( endpoints == null )
4245 {
@@ -63,6 +66,26 @@ public void AddEndpoints(
6366 throw new ArgumentNullException ( nameof ( conventions ) ) ;
6467 }
6568
69+ if ( createInertEndpoints )
70+ {
71+ var builder = new InertEndpointBuilder ( )
72+ {
73+ DisplayName = action . DisplayName ,
74+ RequestDelegate = _requestDelegate ,
75+ } ;
76+ AddActionDataToBuilder (
77+ builder ,
78+ routeNames ,
79+ action ,
80+ routeName : null ,
81+ dataTokens : null ,
82+ suppressLinkGeneration : false ,
83+ suppressPathMatching : false ,
84+ conventions ,
85+ Array . Empty < Action < EndpointBuilder > > ( ) ) ;
86+ endpoints . Add ( builder . Build ( ) ) ;
87+ }
88+
6689 if ( action . AttributeRouteInfo == null )
6790 {
6891 // Check each of the conventional patterns to see if the action would be reachable.
@@ -81,18 +104,21 @@ public void AddEndpoints(
81104
82105 // We suppress link generation for each conventionally routed endpoint. We generate a single endpoint per-route
83106 // to handle link generation.
84- var builder = CreateEndpoint (
107+ var builder = new RouteEndpointBuilder ( _requestDelegate , updatedRoutePattern , route . Order )
108+ {
109+ DisplayName = action . DisplayName ,
110+ } ;
111+ AddActionDataToBuilder (
112+ builder ,
85113 routeNames ,
86114 action ,
87- updatedRoutePattern ,
88115 route . RouteName ,
89- route . Order ,
90116 route . DataTokens ,
91117 suppressLinkGeneration : true ,
92118 suppressPathMatching : false ,
93119 conventions ,
94120 route . Conventions ) ;
95- endpoints . Add ( builder ) ;
121+ endpoints . Add ( builder . Build ( ) ) ;
96122 }
97123 }
98124 else
@@ -109,18 +135,21 @@ public void AddEndpoints(
109135 throw new InvalidOperationException ( "Failed to update route pattern with required values." ) ;
110136 }
111137
112- var endpoint = CreateEndpoint (
138+ var builder = new RouteEndpointBuilder ( _requestDelegate , updatedRoutePattern , action . AttributeRouteInfo . Order )
139+ {
140+ DisplayName = action . DisplayName ,
141+ } ;
142+ AddActionDataToBuilder (
143+ builder ,
113144 routeNames ,
114145 action ,
115- updatedRoutePattern ,
116146 action . AttributeRouteInfo . Name ,
117- action . AttributeRouteInfo . Order ,
118147 dataTokens : null ,
119148 action . AttributeRouteInfo . SuppressLinkGeneration ,
120149 action . AttributeRouteInfo . SuppressPathMatching ,
121150 conventions ,
122151 perRouteConventions : Array . Empty < Action < EndpointBuilder > > ( ) ) ;
123- endpoints . Add ( endpoint ) ;
152+ endpoints . Add ( builder . Build ( ) ) ;
124153 }
125154 }
126155
@@ -262,49 +291,17 @@ private static (RoutePattern resolvedRoutePattern, IDictionary<string, string> r
262291 return ( attributeRoutePattern , resolvedRequiredValues ?? action . RouteValues ) ;
263292 }
264293
265- private RouteEndpoint CreateEndpoint (
294+ private void AddActionDataToBuilder (
295+ EndpointBuilder builder ,
266296 HashSet < string > routeNames ,
267297 ActionDescriptor action ,
268- RoutePattern routePattern ,
269298 string routeName ,
270- int order ,
271299 RouteValueDictionary dataTokens ,
272300 bool suppressLinkGeneration ,
273301 bool suppressPathMatching ,
274302 IReadOnlyList < Action < EndpointBuilder > > conventions ,
275303 IReadOnlyList < Action < EndpointBuilder > > perRouteConventions )
276304 {
277-
278- // We don't want to close over the retrieve the Invoker Factory in ActionEndpointFactory as
279- // that creates cycles in DI. Since we're creating this delegate at startup time
280- // we don't want to create all of the things we use at runtime until the action
281- // actually matches.
282- //
283- // The request delegate is already a closure here because we close over
284- // the action descriptor.
285- IActionInvokerFactory invokerFactory = null ;
286-
287- RequestDelegate requestDelegate = ( context ) =>
288- {
289- var routeData = new RouteData ( ) ;
290- routeData . PushState ( router : null , context . Request . RouteValues , dataTokens ) ;
291-
292- var actionContext = new ActionContext ( context , routeData , action ) ;
293-
294- if ( invokerFactory == null )
295- {
296- invokerFactory = context . RequestServices . GetRequiredService < IActionInvokerFactory > ( ) ;
297- }
298-
299- var invoker = invokerFactory . CreateInvoker ( actionContext ) ;
300- return invoker . InvokeAsync ( ) ;
301- } ;
302-
303- var builder = new RouteEndpointBuilder ( requestDelegate , routePattern , order )
304- {
305- DisplayName = action . DisplayName ,
306- } ;
307-
308305 // Add action metadata first so it has a low precedence
309306 if ( action . EndpointMetadata != null )
310307 {
@@ -399,8 +396,47 @@ private RouteEndpoint CreateEndpoint(
399396 {
400397 perRouteConventions [ i ] ( builder ) ;
401398 }
399+ }
402400
403- return ( RouteEndpoint ) builder . Build ( ) ;
401+ private static RequestDelegate CreateRequestDelegate ( )
402+ {
403+ // We don't want to close over the Invoker Factory in ActionEndpointFactory as
404+ // that creates cycles in DI. Since we're creating this delegate at startup time
405+ // we don't want to create all of the things we use at runtime until the action
406+ // actually matches.
407+ //
408+ // The request delegate is already a closure here because we close over
409+ // the action descriptor.
410+ IActionInvokerFactory invokerFactory = null ;
411+
412+ return ( context ) =>
413+ {
414+ var endpoint = context . GetEndpoint ( ) ;
415+ var dataTokens = endpoint . Metadata . GetMetadata < IDataTokensMetadata > ( ) ;
416+
417+ var routeData = new RouteData ( ) ;
418+ routeData . PushState ( router : null , context . Request . RouteValues , new RouteValueDictionary ( dataTokens ? . DataTokens ) ) ;
419+
420+ // Don't close over the ActionDescriptor, that's not valid for pages.
421+ var action = endpoint . Metadata . GetMetadata < ActionDescriptor > ( ) ;
422+ var actionContext = new ActionContext ( context , routeData , action ) ;
423+
424+ if ( invokerFactory == null )
425+ {
426+ invokerFactory = context . RequestServices . GetRequiredService < IActionInvokerFactory > ( ) ;
427+ }
428+
429+ var invoker = invokerFactory . CreateInvoker ( actionContext ) ;
430+ return invoker . InvokeAsync ( ) ;
431+ } ;
432+ }
433+
434+ private class InertEndpointBuilder : EndpointBuilder
435+ {
436+ public override Endpoint Build ( )
437+ {
438+ return new Endpoint ( RequestDelegate , new EndpointMetadataCollection ( Metadata ) , DisplayName ) ;
439+ }
404440 }
405441 }
406442}
0 commit comments