Skip to content

Commit f82384c

Browse files
authored
[Mvc] Add support for order in dynamic controller routes (#25073)
* Order defaults to 1 same as conventional routes * An incremental order is applied to dynamic routes as they are defined.
1 parent 802b88e commit f82384c

13 files changed

+425
-48
lines changed

src/Mvc/Mvc.Core/src/Builder/ControllerEndpointRouteBuilderExtensions.cs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -506,18 +506,10 @@ public static void MapDynamicControllerRoute<TTransformer>(this IEndpointRouteBu
506506
EnsureControllerServices(endpoints);
507507

508508
// Called for side-effect to make sure that the data source is registered.
509-
GetOrCreateDataSource(endpoints).CreateInertEndpoints = true;
510-
511-
endpoints.Map(
512-
pattern,
513-
context =>
514-
{
515-
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
516-
})
517-
.Add(b =>
518-
{
519-
b.Metadata.Add(new DynamicControllerRouteValueTransformerMetadata(typeof(TTransformer), state));
520-
});
509+
var controllerDataSource = GetOrCreateDataSource(endpoints);
510+
511+
// The data source is just used to share the common order with conventionally routed actions.
512+
controllerDataSource.AddDynamicControllerEndpoint(endpoints, pattern, typeof(TTransformer), state);
521513
}
522514

523515
private static DynamicControllerMetadata CreateDynamicControllerMetadata(string action, string controller, string area)

src/Mvc/Mvc.Core/src/DependencyInjection/MvcCoreServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ internal static void AddMvcCoreServices(IServiceCollection services)
269269
//
270270
// Endpoint Routing / Endpoints
271271
//
272+
services.TryAddSingleton<OrderedEndpointsSequenceProvider>();
272273
services.TryAddSingleton<ControllerActionEndpointDataSource>();
273274
services.TryAddSingleton<ActionEndpointFactory>();
274275
services.TryAddSingleton<DynamicControllerEndpointSelector>();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.Mvc.Infrastructure
5+
{
6+
internal class OrderedEndpointsSequenceProvider
7+
{
8+
private object Lock = new object();
9+
10+
// In traditional conventional routing setup, the routes defined by a user have a order
11+
// defined by how they are added into the list. We would like to maintain the same order when building
12+
// up the endpoints too.
13+
//
14+
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
15+
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
16+
private int _current = 1;
17+
18+
public int GetNext()
19+
{
20+
lock (Lock)
21+
{
22+
return _current++;
23+
}
24+
}
25+
}
26+
}

src/Mvc/Mvc.Core/src/Routing/ControllerActionEndpointDataSource.cs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -15,27 +15,19 @@ namespace Microsoft.AspNetCore.Mvc.Routing
1515
internal class ControllerActionEndpointDataSource : ActionEndpointDataSourceBase
1616
{
1717
private readonly ActionEndpointFactory _endpointFactory;
18+
private readonly OrderedEndpointsSequenceProvider _orderSequence;
1819
private readonly List<ConventionalRouteEntry> _routes;
1920

20-
private int _order;
21-
2221
public ControllerActionEndpointDataSource(
2322
IActionDescriptorCollectionProvider actions,
24-
ActionEndpointFactory endpointFactory)
23+
ActionEndpointFactory endpointFactory,
24+
OrderedEndpointsSequenceProvider orderSequence)
2525
: base(actions)
2626
{
2727
_endpointFactory = endpointFactory;
28-
28+
_orderSequence = orderSequence;
2929
_routes = new List<ConventionalRouteEntry>();
3030

31-
// In traditional conventional routing setup, the routes defined by a user have a order
32-
// defined by how they are added into the list. We would like to maintain the same order when building
33-
// up the endpoints too.
34-
//
35-
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
36-
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
37-
_order = 1;
38-
3931
DefaultBuilder = new ControllerActionEndpointConventionBuilder(Lock, Conventions);
4032

4133
// IMPORTANT: this needs to be the last thing we do in the constructor.
@@ -59,7 +51,7 @@ public ControllerActionEndpointConventionBuilder AddRoute(
5951
lock (Lock)
6052
{
6153
var conventions = new List<Action<EndpointBuilder>>();
62-
_routes.Add(new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, _order++, conventions));
54+
_routes.Add(new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, _orderSequence.GetNext(), conventions));
6355
return new ControllerActionEndpointConventionBuilder(Lock, conventions);
6456
}
6557
}
@@ -108,6 +100,27 @@ protected override List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor
108100

109101
return endpoints;
110102
}
103+
104+
internal void AddDynamicControllerEndpoint(IEndpointRouteBuilder endpoints, string pattern, Type transformerType, object state)
105+
{
106+
CreateInertEndpoints = true;
107+
lock (Lock)
108+
{
109+
var order = _orderSequence.GetNext();
110+
111+
endpoints.Map(
112+
pattern,
113+
context =>
114+
{
115+
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
116+
})
117+
.Add(b =>
118+
{
119+
((RouteEndpointBuilder)b).Order = order;
120+
b.Metadata.Add(new DynamicControllerRouteValueTransformerMetadata(transformerType, state));
121+
});
122+
}
123+
}
111124
}
112125
}
113126

src/Mvc/Mvc.Core/test/Routing/ControllerActionEndpointDataSourceTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -385,7 +385,7 @@ private static bool SupportsLinkGeneration(RouteEndpoint endpoint)
385385

386386
private protected override ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
387387
{
388-
return new ControllerActionEndpointDataSource(actions, endpointFactory);
388+
return new ControllerActionEndpointDataSource(actions, endpointFactory, new OrderedEndpointsSequenceProvider());
389389
}
390390

391391
protected override ActionDescriptor CreateActionDescriptor(

src/Mvc/Mvc.RazorPages/src/Builder/RazorPagesEndpointRouteBuilderExtensions.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -337,18 +337,9 @@ public static void MapDynamicPageRoute<TTransformer>(this IEndpointRouteBuilder
337337
EnsureRazorPagesServices(endpoints);
338338

339339
// Called for side-effect to make sure that the data source is registered.
340-
GetOrCreateDataSource(endpoints).CreateInertEndpoints = true;
340+
var dataSource = GetOrCreateDataSource(endpoints);
341341

342-
endpoints.Map(
343-
pattern,
344-
context =>
345-
{
346-
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
347-
})
348-
.Add(b =>
349-
{
350-
b.Metadata.Add(new DynamicPageRouteValueTransformerMetadata(typeof(TTransformer), state));
351-
});
342+
dataSource.AddDynamicPageEndpoint(endpoints, pattern, typeof(TTransformer), state);
352343
}
353344

354345
private static DynamicPageMetadata CreateDynamicPageMetadata(string page, string area)

src/Mvc/Mvc.RazorPages/src/Infrastructure/PageActionEndpointDataSource.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -8,18 +8,23 @@
88
using Microsoft.AspNetCore.Mvc.Abstractions;
99
using Microsoft.AspNetCore.Mvc.Infrastructure;
1010
using Microsoft.AspNetCore.Mvc.Routing;
11+
using Microsoft.AspNetCore.Routing;
1112

1213
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
1314
{
1415
internal class PageActionEndpointDataSource : ActionEndpointDataSourceBase
1516
{
1617
private readonly ActionEndpointFactory _endpointFactory;
18+
private readonly OrderedEndpointsSequenceProvider _orderSequence;
1719

18-
public PageActionEndpointDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
20+
public PageActionEndpointDataSource(
21+
IActionDescriptorCollectionProvider actions,
22+
ActionEndpointFactory endpointFactory,
23+
OrderedEndpointsSequenceProvider orderedEndpoints)
1924
: base(actions)
2025
{
2126
_endpointFactory = endpointFactory;
22-
27+
_orderSequence = orderedEndpoints;
2328
DefaultBuilder = new PageActionEndpointConventionBuilder(Lock, Conventions);
2429

2530
// IMPORTANT: this needs to be the last thing we do in the constructor.
@@ -47,6 +52,27 @@ protected override List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor
4752

4853
return endpoints;
4954
}
55+
56+
internal void AddDynamicPageEndpoint(IEndpointRouteBuilder endpoints, string pattern, Type transformerType, object state)
57+
{
58+
CreateInertEndpoints = true;
59+
lock (Lock)
60+
{
61+
var order = _orderSequence.GetNext();
62+
63+
endpoints.Map(
64+
pattern,
65+
context =>
66+
{
67+
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
68+
})
69+
.Add(b =>
70+
{
71+
((RouteEndpointBuilder)b).Order = order;
72+
b.Metadata.Add(new DynamicPageRouteValueTransformerMetadata(transformerType, state));
73+
});
74+
}
75+
}
5076
}
5177
}
5278

src/Mvc/Mvc.RazorPages/test/Infrastructure/PageActionEndpointDataSourceTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -92,7 +92,7 @@ public void Endpoints_AppliesConventions()
9292

9393
private protected override ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
9494
{
95-
return new PageActionEndpointDataSource(actions, endpointFactory);
95+
return new PageActionEndpointDataSource(actions, endpointFactory, new OrderedEndpointsSequenceProvider());
9696
}
9797

9898
protected override ActionDescriptor CreateActionDescriptor(

src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ControllerActionEndpointDatasourceBenchmark.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -110,7 +110,8 @@ private ControllerActionEndpointDataSource CreateDataSource(IActionDescriptorCol
110110
{
111111
var dataSource = new ControllerActionEndpointDataSource(
112112
actionDescriptorCollectionProvider,
113-
new ActionEndpointFactory(new MockRoutePatternTransformer()));
113+
new ActionEndpointFactory(new MockRoutePatternTransformer()),
114+
new OrderedEndpointsSequenceProvider());
114115

115116
return dataSource;
116117
}

0 commit comments

Comments
 (0)