@@ -34,7 +34,8 @@ public void AddEndpoints(
3434 List < Endpoint > endpoints ,
3535 ActionDescriptor action ,
3636 IReadOnlyList < ConventionalRouteEntry > routes ,
37- IReadOnlyList < Action < EndpointBuilder > > conventions )
37+ IReadOnlyList < Action < EndpointBuilder > > conventions ,
38+ bool createInertEndpoints )
3839 {
3940 if ( endpoints == null )
4041 {
@@ -56,6 +57,17 @@ public void AddEndpoints(
5657 throw new ArgumentNullException ( nameof ( conventions ) ) ;
5758 }
5859
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+
5971 if ( action . AttributeRouteInfo == null )
6072 {
6173 // In traditional conventional routing setup, the routes defined by a user have a static order
@@ -181,32 +193,54 @@ private RouteEndpoint CreateEndpoint(
181193 bool suppressPathMatching ,
182194 IReadOnlyList < Action < EndpointBuilder > > conventions )
183195 {
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 )
195197 {
196- var routeData = context . GetRouteData ( ) ;
197- var actionContext = new ActionContext ( context , routeData , action ) ;
198+ DisplayName = action . DisplayName ,
199+ } ;
198200
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 )
200205 {
201- invokerFactory = context . RequestServices . GetRequiredService < IActionInvokerFactory > ( ) ;
206+ builder . Metadata . Add ( d ) ;
202207 }
208+ }
203209
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+ }
207216
208- var builder = new RouteEndpointBuilder ( requestDelegate , routePattern , order )
217+ builder . Metadata . Add ( new RouteNameMetadata ( routeName ) ) ;
218+
219+ AddMetadataFromActionDescriptor ( builder , action ) ;
220+
221+ if ( suppressLinkGeneration )
209222 {
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 ) ,
210244 DisplayName = action . DisplayName ,
211245 } ;
212246
@@ -221,13 +255,44 @@ private RouteEndpoint CreateEndpoint(
221255
222256 builder . Metadata . Add ( action ) ;
223257
224- if ( dataTokens != null )
258+ AddMetadataFromActionDescriptor ( builder , action ) ;
259+
260+ for ( var i = 0 ; i < conventions . Count ; i ++ )
225261 {
226- builder . Metadata . Add ( new DataTokensMetadata ( dataTokens ) ) ;
262+ conventions [ i ] ( builder ) ;
227263 }
228264
229- builder . Metadata . Add ( new RouteNameMetadata ( routeName ) ) ;
265+ return builder . Build ( ) ;
266+ }
230267
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+ {
231296 // Add filter descriptors to endpoint metadata
232297 if ( action . FilterDescriptors != null && action . FilterDescriptors . Count > 0 )
233298 {
@@ -263,23 +328,14 @@ private RouteEndpoint CreateEndpoint(
263328 }
264329 }
265330 }
331+ }
266332
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 ( )
278336 {
279- conventions [ i ] ( builder ) ;
337+ return new Endpoint ( RequestDelegate , new EndpointMetadataCollection ( Metadata ) , DisplayName ) ;
280338 }
281-
282- return ( RouteEndpoint ) builder . Build ( ) ;
283339 }
284340 }
285341}
0 commit comments