[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.
This commit is contained in:
parent
340ee72715
commit
a17842a2e4
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -506,18 +506,10 @@ namespace Microsoft.AspNetCore.Builder
|
|||
EnsureControllerServices(endpoints);
|
||||
|
||||
// Called for side-effect to make sure that the data source is registered.
|
||||
GetOrCreateDataSource(endpoints).CreateInertEndpoints = true;
|
||||
|
||||
endpoints.Map(
|
||||
pattern,
|
||||
context =>
|
||||
{
|
||||
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
|
||||
})
|
||||
.Add(b =>
|
||||
{
|
||||
b.Metadata.Add(new DynamicControllerRouteValueTransformerMetadata(typeof(TTransformer), state));
|
||||
});
|
||||
var controllerDataSource = GetOrCreateDataSource(endpoints);
|
||||
|
||||
// The data source is just used to share the common order with conventionally routed actions.
|
||||
controllerDataSource.AddDynamicControllerEndpoint(endpoints, pattern, typeof(TTransformer), state);
|
||||
}
|
||||
|
||||
private static DynamicControllerMetadata CreateDynamicControllerMetadata(string action, string controller, string area)
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
//
|
||||
// Endpoint Routing / Endpoints
|
||||
//
|
||||
services.TryAddSingleton<OrderedEndpointsSequenceProvider>();
|
||||
services.TryAddSingleton<ControllerActionEndpointDataSource>();
|
||||
services.TryAddSingleton<ActionEndpointFactory>();
|
||||
services.TryAddSingleton<DynamicControllerEndpointSelector>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Infrastructure
|
||||
{
|
||||
internal class OrderedEndpointsSequenceProvider
|
||||
{
|
||||
private object Lock = new object();
|
||||
|
||||
// In traditional conventional routing setup, the routes defined by a user have a order
|
||||
// defined by how they are added into the list. We would like to maintain the same order when building
|
||||
// up the endpoints too.
|
||||
//
|
||||
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
|
||||
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
|
||||
private int _current = 1;
|
||||
|
||||
public int GetNext()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
return _current++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -15,27 +15,19 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
internal class ControllerActionEndpointDataSource : ActionEndpointDataSourceBase
|
||||
{
|
||||
private readonly ActionEndpointFactory _endpointFactory;
|
||||
private readonly OrderedEndpointsSequenceProvider _orderSequence;
|
||||
private readonly List<ConventionalRouteEntry> _routes;
|
||||
|
||||
private int _order;
|
||||
|
||||
public ControllerActionEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actions,
|
||||
ActionEndpointFactory endpointFactory)
|
||||
ActionEndpointFactory endpointFactory,
|
||||
OrderedEndpointsSequenceProvider orderSequence)
|
||||
: base(actions)
|
||||
{
|
||||
_endpointFactory = endpointFactory;
|
||||
|
||||
_orderSequence = orderSequence;
|
||||
_routes = new List<ConventionalRouteEntry>();
|
||||
|
||||
// In traditional conventional routing setup, the routes defined by a user have a order
|
||||
// defined by how they are added into the list. We would like to maintain the same order when building
|
||||
// up the endpoints too.
|
||||
//
|
||||
// Start with an order of '1' for conventional routes as attribute routes have a default order of '0'.
|
||||
// This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world.
|
||||
_order = 1;
|
||||
|
||||
DefaultBuilder = new ControllerActionEndpointConventionBuilder(Lock, Conventions);
|
||||
|
||||
// IMPORTANT: this needs to be the last thing we do in the constructor.
|
||||
|
|
@ -59,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
lock (Lock)
|
||||
{
|
||||
var conventions = new List<Action<EndpointBuilder>>();
|
||||
_routes.Add(new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, _order++, conventions));
|
||||
_routes.Add(new ConventionalRouteEntry(routeName, pattern, defaults, constraints, dataTokens, _orderSequence.GetNext(), conventions));
|
||||
return new ControllerActionEndpointConventionBuilder(Lock, conventions);
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +100,27 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
internal void AddDynamicControllerEndpoint(IEndpointRouteBuilder endpoints, string pattern, Type transformerType, object state)
|
||||
{
|
||||
CreateInertEndpoints = true;
|
||||
lock (Lock)
|
||||
{
|
||||
var order = _orderSequence.GetNext();
|
||||
|
||||
endpoints.Map(
|
||||
pattern,
|
||||
context =>
|
||||
{
|
||||
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
|
||||
})
|
||||
.Add(b =>
|
||||
{
|
||||
((RouteEndpointBuilder)b).Order = order;
|
||||
b.Metadata.Add(new DynamicControllerRouteValueTransformerMetadata(transformerType, state));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -385,7 +385,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
|
||||
private protected override ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
{
|
||||
return new ControllerActionEndpointDataSource(actions, endpointFactory);
|
||||
return new ControllerActionEndpointDataSource(actions, endpointFactory, new OrderedEndpointsSequenceProvider());
|
||||
}
|
||||
|
||||
protected override ActionDescriptor CreateActionDescriptor(
|
||||
|
|
|
|||
|
|
@ -337,18 +337,9 @@ namespace Microsoft.AspNetCore.Builder
|
|||
EnsureRazorPagesServices(endpoints);
|
||||
|
||||
// Called for side-effect to make sure that the data source is registered.
|
||||
GetOrCreateDataSource(endpoints).CreateInertEndpoints = true;
|
||||
var dataSource = GetOrCreateDataSource(endpoints);
|
||||
|
||||
endpoints.Map(
|
||||
pattern,
|
||||
context =>
|
||||
{
|
||||
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
|
||||
})
|
||||
.Add(b =>
|
||||
{
|
||||
b.Metadata.Add(new DynamicPageRouteValueTransformerMetadata(typeof(TTransformer), state));
|
||||
});
|
||||
dataSource.AddDynamicPageEndpoint(endpoints, pattern, typeof(TTransformer), state);
|
||||
}
|
||||
|
||||
private static DynamicPageMetadata CreateDynamicPageMetadata(string page, string area)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -8,18 +8,23 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||
{
|
||||
internal class PageActionEndpointDataSource : ActionEndpointDataSourceBase
|
||||
{
|
||||
private readonly ActionEndpointFactory _endpointFactory;
|
||||
private readonly OrderedEndpointsSequenceProvider _orderSequence;
|
||||
|
||||
public PageActionEndpointDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
public PageActionEndpointDataSource(
|
||||
IActionDescriptorCollectionProvider actions,
|
||||
ActionEndpointFactory endpointFactory,
|
||||
OrderedEndpointsSequenceProvider orderedEndpoints)
|
||||
: base(actions)
|
||||
{
|
||||
_endpointFactory = endpointFactory;
|
||||
|
||||
_orderSequence = orderedEndpoints;
|
||||
DefaultBuilder = new PageActionEndpointConventionBuilder(Lock, Conventions);
|
||||
|
||||
// IMPORTANT: this needs to be the last thing we do in the constructor.
|
||||
|
|
@ -47,6 +52,27 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
internal void AddDynamicPageEndpoint(IEndpointRouteBuilder endpoints, string pattern, Type transformerType, object state)
|
||||
{
|
||||
CreateInertEndpoints = true;
|
||||
lock (Lock)
|
||||
{
|
||||
var order = _orderSequence.GetNext();
|
||||
|
||||
endpoints.Map(
|
||||
pattern,
|
||||
context =>
|
||||
{
|
||||
throw new InvalidOperationException("This endpoint is not expected to be executed directly.");
|
||||
})
|
||||
.Add(b =>
|
||||
{
|
||||
((RouteEndpointBuilder)b).Order = order;
|
||||
b.Metadata.Add(new DynamicPageRouteValueTransformerMetadata(transformerType, state));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
private protected override ActionEndpointDataSourceBase CreateDataSource(IActionDescriptorCollectionProvider actions, ActionEndpointFactory endpointFactory)
|
||||
{
|
||||
return new PageActionEndpointDataSource(actions, endpointFactory);
|
||||
return new PageActionEndpointDataSource(actions, endpointFactory, new OrderedEndpointsSequenceProvider());
|
||||
}
|
||||
|
||||
protected override ActionDescriptor CreateActionDescriptor(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -110,7 +110,8 @@ namespace Microsoft.AspNetCore.Mvc.Performance
|
|||
{
|
||||
var dataSource = new ControllerActionEndpointDataSource(
|
||||
actionDescriptorCollectionProvider,
|
||||
new ActionEndpointFactory(new MockRoutePatternTransformer()));
|
||||
new ActionEndpointFactory(new MockRoutePatternTransformer()),
|
||||
new OrderedEndpointsSequenceProvider());
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using RoutingWebSite;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class RoutingDynamicOrderTest : IClassFixture<MvcTestFixture<RoutingWebSite.StartupForDynamic>>
|
||||
{
|
||||
public RoutingDynamicOrderTest(MvcTestFixture<RoutingWebSite.StartupForDynamic> fixture)
|
||||
{
|
||||
Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
}
|
||||
|
||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => builder.UseStartup<RoutingWebSite.StartupForDynamicOrder>();
|
||||
|
||||
public WebApplicationFactory<StartupForDynamic> Factory { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task PrefersAttributeRoutesOverDynamicControllerRoutes()
|
||||
{
|
||||
var factory = Factory
|
||||
.WithWebHostBuilder(b => b.UseSetting("Scenario", RoutingWebSite.StartupForDynamicOrder.DynamicOrderScenarios.AttributeRouteDynamicRoute));
|
||||
|
||||
var client = factory.CreateClient();
|
||||
|
||||
// Arrange
|
||||
var url = "http://localhost/attribute-dynamic-order/Controller=Home,Action=Index";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadFromJsonAsync<RouteInfo>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("AttributeRouteSlug", content.RouteName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DynamicRoutesAreMatchedInDefinitionOrderOverPrecedence()
|
||||
{
|
||||
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", isEnabled: true);
|
||||
var factory = Factory
|
||||
.WithWebHostBuilder(b => b.UseSetting("Scenario", RoutingWebSite.StartupForDynamicOrder.DynamicOrderScenarios.MultipleDynamicRoute));
|
||||
|
||||
var client = factory.CreateClient();
|
||||
|
||||
// Arrange
|
||||
var url = "http://localhost/dynamic-order/specific/Controller=Home,Action=Index";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadFromJsonAsync<RouteInfo>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.True(content.RouteValues.TryGetValue("identifier", out var identifier));
|
||||
Assert.Equal("slug", identifier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutesDefinedEarlierWinOverDynamicControllerRoutes()
|
||||
{
|
||||
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", isEnabled: true);
|
||||
var factory = Factory
|
||||
.WithWebHostBuilder(b => b.UseSetting("Scenario", RoutingWebSite.StartupForDynamicOrder.DynamicOrderScenarios.ConventionalRouteDynamicRoute));
|
||||
|
||||
var client = factory.CreateClient();
|
||||
|
||||
// Arrange
|
||||
var url = "http://localhost/conventional-dynamic-order-before";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadFromJsonAsync<RouteInfo>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.False(content.RouteValues.TryGetValue("identifier", out var identifier));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConventionalRoutesDefinedLaterLooseToDynamicControllerRoutes()
|
||||
{
|
||||
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", isEnabled: true);
|
||||
var factory = Factory
|
||||
.WithWebHostBuilder(b => b.UseSetting("Scenario", RoutingWebSite.StartupForDynamicOrder.DynamicOrderScenarios.ConventionalRouteDynamicRoute));
|
||||
|
||||
var client = factory.CreateClient();
|
||||
|
||||
// Arrange
|
||||
var url = "http://localhost/conventional-dynamic-order-after";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadFromJsonAsync<RouteInfo>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.True(content.RouteValues.TryGetValue("identifier", out var identifier));
|
||||
Assert.Equal("slug", identifier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DynamicPagesDefinedEarlierWinOverDynamicControllers()
|
||||
{
|
||||
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", isEnabled: true);
|
||||
var factory = Factory
|
||||
.WithWebHostBuilder(b => b.UseSetting("Scenario", RoutingWebSite.StartupForDynamicOrder.DynamicOrderScenarios.DynamicControllerAndPages));
|
||||
|
||||
var client = factory.CreateClient();
|
||||
// Arrange
|
||||
var url = "http://localhost/dynamic-order-page-controller-before";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal("Hello from dynamic page: /DynamicPagebefore", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DynamicPagesDefinedLaterLooseOverDynamicControllers()
|
||||
{
|
||||
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", isEnabled: true);
|
||||
var factory = Factory
|
||||
.WithWebHostBuilder(b => b.UseSetting("Scenario", RoutingWebSite.StartupForDynamicOrder.DynamicOrderScenarios.DynamicControllerAndPages));
|
||||
|
||||
var client = factory.CreateClient();
|
||||
|
||||
// Arrange
|
||||
var url = "http://localhost/dynamic-order-page-controller-after";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
var content = await response.Content.ReadFromJsonAsync<RouteInfo>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.True(content.RouteValues.TryGetValue("identifier", out var identifier));
|
||||
Assert.Equal("controller", identifier);
|
||||
}
|
||||
|
||||
private record RouteInfo(string RouteName, IDictionary<string,string> RouteValues);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Mvc.RoutingWebSite.Controllers
|
||||
{
|
||||
public class DynamicOrderController : Controller
|
||||
{
|
||||
private readonly TestResponseGenerator _generator;
|
||||
|
||||
public DynamicOrderController(TestResponseGenerator generator)
|
||||
{
|
||||
_generator = generator;
|
||||
}
|
||||
|
||||
[HttpGet("attribute-dynamic-order/{**slug}", Name = "AttributeRouteSlug")]
|
||||
public IActionResult Get(string slug)
|
||||
{
|
||||
return _generator.Generate(Url.RouteUrl("AttributeRouteSlug", new { slug }));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult Index()
|
||||
{
|
||||
return _generator.Generate(Url.RouteUrl(null, new { controller = "DynamicOrder", action = "Index" }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
@page
|
||||
@model RoutingWebSite.Pages.DynamicPageModel
|
||||
Hello from dynamic page: @Url.Page("")
|
||||
Hello from dynamic page: @Url.Page("")@RouteData.Values["identifier"]
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace RoutingWebSite
|
||||
{
|
||||
// For by tests for dynamic routing to pages/controllers
|
||||
public class StartupForDynamicOrder
|
||||
{
|
||||
public static class DynamicOrderScenarios
|
||||
{
|
||||
public const string AttributeRouteDynamicRoute = nameof(AttributeRouteDynamicRoute);
|
||||
public const string MultipleDynamicRoute = nameof(MultipleDynamicRoute);
|
||||
public const string ConventionalRouteDynamicRoute = nameof(ConventionalRouteDynamicRoute);
|
||||
public const string DynamicControllerAndPages = nameof(DynamicControllerAndPages);
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public StartupForDynamicOrder(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddMvc()
|
||||
.AddNewtonsoftJson()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
|
||||
services.AddTransient<Transformer>();
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
|
||||
// Used by some controllers defined in this project.
|
||||
services.Configure<RouteOptions>(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
var scenario = Configuration.GetValue<string>("Scenario");
|
||||
app.UseRouting();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
// Route order definition is important for all these routes:
|
||||
switch (scenario)
|
||||
{
|
||||
case DynamicOrderScenarios.AttributeRouteDynamicRoute:
|
||||
endpoints.MapDynamicControllerRoute<Transformer>("attribute-dynamic-order/{**slug}", new TransformerState() { Identifier = "slug" });
|
||||
endpoints.MapControllers();
|
||||
break;
|
||||
case DynamicOrderScenarios.ConventionalRouteDynamicRoute:
|
||||
endpoints.MapControllerRoute(null, "{**conventional-dynamic-order-before:regex(^((?!conventional\\-dynamic\\-order\\-after).)*$)}", new { controller = "DynamicOrder", action = "Index" });
|
||||
endpoints.MapDynamicControllerRoute<Transformer>("{conventional-dynamic-order}", new TransformerState() { Identifier = "slug" });
|
||||
endpoints.MapControllerRoute(null, "conventional-dynamic-order-after", new { controller = "DynamicOrder", action = "Index" });
|
||||
break;
|
||||
case DynamicOrderScenarios.MultipleDynamicRoute:
|
||||
endpoints.MapDynamicControllerRoute<Transformer>("dynamic-order/{**slug}", new TransformerState() { Identifier = "slug" });
|
||||
endpoints.MapDynamicControllerRoute<Transformer>("dynamic-order/specific/{**slug}", new TransformerState() { Identifier = "specific" });
|
||||
break;
|
||||
case DynamicOrderScenarios.DynamicControllerAndPages:
|
||||
endpoints.MapDynamicPageRoute<Transformer>("{**dynamic-order-page-controller-before:regex(^((?!dynamic\\-order\\-page\\-controller\\-after).)*$)}", new TransformerState() { Identifier = "before", ForPages = true });
|
||||
endpoints.MapDynamicControllerRoute<Transformer>("{dynamic-order-page-controller}", new TransformerState() { Identifier = "controller" });
|
||||
endpoints.MapDynamicPageRoute<Transformer>("dynamic-order-page-controller-after", new TransformerState() { Identifier = "after", ForPages = true });
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid scenario configuration.");
|
||||
}
|
||||
});
|
||||
|
||||
app.Map("/afterrouting", b => b.Run(c =>
|
||||
{
|
||||
return c.Response.WriteAsync("Hello from middleware after routing");
|
||||
}));
|
||||
}
|
||||
|
||||
private class TransformerState
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
public bool ForPages { get; set; }
|
||||
}
|
||||
|
||||
private class Transformer : DynamicRouteValueTransformer
|
||||
{
|
||||
// Turns a format like `controller=Home,action=Index` into an RVD
|
||||
public override ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
|
||||
{
|
||||
var kvps = ((string)values?["slug"])?.Split("/")?.LastOrDefault()?.Split(",") ?? Array.Empty<string>();
|
||||
|
||||
// Go to index by default if the route doesn't follow the slug pattern, we want to make sure always match to
|
||||
// test the order is applied
|
||||
var state = (TransformerState)State;
|
||||
var results = new RouteValueDictionary();
|
||||
if (!state.ForPages)
|
||||
{
|
||||
results["controller"] = "Home";
|
||||
results["action"] = "Index";
|
||||
}
|
||||
else
|
||||
{
|
||||
results["Page"] = "/DynamicPage";
|
||||
}
|
||||
|
||||
foreach (var kvp in kvps)
|
||||
{
|
||||
var split = kvp.Split("=");
|
||||
if (split.Length == 2)
|
||||
{
|
||||
results[split[0]] = split[1];
|
||||
}
|
||||
}
|
||||
|
||||
results["identifier"] = ((TransformerState)State).Identifier;
|
||||
|
||||
return new ValueTask<RouteValueDictionary>(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue