ASP.NET Core 3 introduced a not so talked about feature which is dynamic routing. In a nutshell, it means that it is possible to decide at runtime the controller, action and route tokens that a request will be dispatched to. The idea is to map a route pattern to a dynamic route handler, like this:
app.UseEndpoints(endpoints => { endpoints.MapDynamicControllerRoute<SearchValueTransformer>("search/{**product}"); });
This maps a route of "/search/<anything>" to a class SearchValueTransformer, which is then responsible for supplying values for the controller and action.
This SeachValueTransformer class must inherit from DynamicRouteValueTransformer, this is the abstract base class for all dynamic route handling, and it only has one method to implement, TransformAsync. The example I am providing here is that of a service that receives some random query for a product on the URL and then decides to which controller it will be forwarded to. A sample implementation of this class would be:
class SearchValueTransformer : DynamicRouteValueTransformer { private readonly IProductLocator _productLocator; public SearchValueTransformer(IProductLocator productLocator) { this._productLocator = productLocator; } public override async ValueTask TransformAsync(
HttpContext httpContext, RouteValueDictionary values) { var productString = values["product"] as string; var id = await this._productLocator.FindProduct("product", out var controller); values["controller"] = controller; values["action"] = "Get"; values["id"] = id; return values; } }
As you can see, it takes an instance of an IProductLocator (I will get to that in a moment) in its constructor, which means that it supports dependency injection. The TransformAsync extracts a "product" parameter from the route – I am skipping validation because if we got here that's because we matched the route – which it then uses to call the stored IProductLocator instance to retrieve the id and controller, which are then set as route parameters. The result route is then returned.
The SearchValueTransformer needs to be registered in the dependency injector too:
services.AddSingleton<SearchValueTransformer>();
The actual lifetime is irrelevant, here I am setting it as singleton because the SearchValueTransformer holds a single IProductLocator which is itself a singleton and nothing else.
As to the IProductLocator, here is it's interface:
public interface IProductLocator { Task<string> FindProduct(string product, out string controller); }
The actual implementation, I leave it up to you, it must apply some logic to the product passed in the URL to decide which controller should be used for search it, and then return some reference and a controller responsible for returning information about it. This interface and its implementation must also be registered:
services.AddSingleton<IProductLocator, ProductLocator>();
And this is it. This may serve as a complement to static routing, other uses include translating route parts (controller, action) on the fly. As always, hope you find this useful!