AspNetCore.Docs
AspNetCore.Docs copied to clipboard
Add documentation for DynamicRouteValuesTransformer
This is a new feature that allows users to migrate IRouter
code that dynamically manipulates route values to endpoint routing.
https://github.com/aspnet/AspNetCore/issues/4221
Summary
DynamicRouteValueTransformer
is a new feature in Endpoint Routing that supports create of slug-style routes. Implementing a DynamicRouteValueTransformer
allows an application to programmatically select a controller or page to handle the request, usually based on some external data.
Writing a transformer
First, subclass DynamicRouteValueTransformer
, and override the TransformAsync
method. Inside TransformAsync
the transformer should compare route values and details of the request to map the request to an appropriate handler.
Usually this process entails reading some known route value from the current route values, and performing a database lookup to get a new set of values.
The DynamicRouteValueTransformer
implementation can access services from dependency injection through the constructor.
Example:
public class Transformer : DynamicRouteValueTransformer
{
private readonly ArticleRepository _respository;
public Transformer(ArticleRepository repository)
{
_respository = repository;
}
public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
var slug = values["article"] as string;
var article = await _respository.GetArticleBySlug(slug);
if (article == null)
{
return null;
}
return new RouteValueDictionary()
{
{ "page", article.Page },
{ "id", article.Id },
};
}
}
Returning null
from TransformAsync
will treat the route as a failure (did not match anything).
Returning route values from TransformAsync
will perform a further lookup to find matching pages or controllers. The matching logic is similar to how conventional routing selects controllers, by matching the area
, action
, and controller
route values (or area
, and page
in the case of pages).
Registering a transformer
A DynamicRouteValueTransformer
must be registered with the service collection in ConfigureServices
using its type name.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ArticleRepository>();
services.AddScoped<Transformer>();
services.AddRazorPages();
}
The service registration can use any lifetime.
Additionally, the transformer needs to be attached to a route inside of UseEndpoints()
Example (using a transformer with pages):
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapDynamicPageRoute<Transformer>("blog/{**article}");
});
Example (using a transformer with controllers):
app.UseEndpoints(endpoints =>
{
endpoints.MapDynamicControllerRoute<Transformer>("blog/{**article}");
});
@Rick-Anderson can this be picked up by you?
@Rick-Anderson can this be picked up by you?
Yes, assigned to @scottaddie. @scottaddie can probably do this after the high priority 3.0 docs issues are completed.
@serpent5 are you interested in writing a sample using the above code? Sometime next year? No hurry.
Yeah, this looks good. I'll add it to my list.
@serpent5 we'd really appreciate a PR from you on this.
Where should this content live? I'm not sure that it fits into any of the existing topics.
@JamesNK @scottaddie please advise where documentation for DynamicRouteValuesTransformer
should go.
Going into detail on DynamicRouteValueTransformer (along with MapDynamicPageRoute and MapDynamicControllerRoute) isn't useful without knowing when it should be used. First need to discuss with you would want to use dynamic endpoint routing verses traditional endpoint routing. Talk about some scenarios like translation.
Content should either be a new section on the routing page - https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.1 - or a sub-article. "Dynamic routing"? Can also cover https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.routing.idynamicendpointmetadata?view=aspnetcore-3.1. I think routing is an enormous topic and should be split up into multiple pages. This could be step 1 😄
It should also be mentioned that dynamic routing offers an migration path for some custom IRouter
implementations that can't be implemented using endpoint routing's more static model.
Routing is in PR now #16456
@Rick-Anderson It looks like there's explanation needed around why this would be needed, etc, that I just can't cover nearly well enough. I think this would be better tackled by one of the experts.
It is not clear what IDynamicEndpointMetadata is used for, when using a dynamic route, it is not added.
I'm using this to replace my ASP.NET Core 2.2 LoginRouter that basically redirects (internally) all requests over to the login page unless the user is already authenticated. This keeps the original URL intact and after login the user will just see the intended page. No need for ugly and insecure returnToUrl parameters or other hacks commonly in place. Request the URL, you get either a login form or the requested resource. No other URLs involved.
So this covers all URLs in the entire application. What should I specify for the pattern in the MapDynamicControllerRoute
call? "*" or ""? The existing explanation only covers blog article style URLs in a separate path but nothing really dynamic.
Also, this whole things fails because the parameter RouteValueDictionary values
is null, always. What should I do?
Update: I could fix(?) that null thing with a dummy pattern. Now my Transform method is called for every request (as far as I could see). But there's the next problem: httpContext.User.Claims
is always empty in this method. It contains the expected data elsewhere but the copy that's available here is useless. That makes the whole routing idea useless. Where can I get a proper claims object in this Transform method?
Update 2: No, it's only called for the start page (URL: /), no other URLs. Somehow this whole dynamic routing thing doesn't want to work for me. Additional documentation is really needed to make use of it.
Here's some code:
public class LoginRouter : DynamicRouteValueTransformer
{
public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
await Task.CompletedTask; // Fake async
// Route unauthenticated users to the login action
if (values != null && !httpContext.User.Claims.Any())
{
values["controller"] = "Account";
values["action"] = "Login";
}
return values;
}
}
public class Startup
{
// ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
// ...
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
// Globally re-route unauthenticated requests
endpoints.MapDynamicControllerRoute<LoginRouter>("{whatever?}");
// Default routes
endpoints.MapControllerRoute(
name: "defaultNoAction",
pattern: "{controller}/{id:int}",
defaults: new { action = "Index" });
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
public class AccountController : Controller
{
public IActionResult Login()
{
return View(new AccountLoginViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(AccountLoginViewModel login)
{
// ...
// Everything is checked, perform the login
var claims = new List<Claim>
{
new Claim("UserId", user.Id.ToString()),
new Claim(ClaimTypes.Name, user.LoginName)
};
var claimsIdentity = new ClaimsIdentity(
claims,
CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true
});
return Redirect(Request.Path);
}
}
@ygoe it doesn't work anyway as far as I can tell (I raised this a while ago, but haven't had time to try the suggested alternative): https://github.com/dotnet/aspnetcore/issues/18688 ). Not sure if your issue in "Update 2" is the same as I was seeing or not.
It doesn't work for me either, and values is also null so I create a new dictionary. The transformer gets hit on a request, but then 404. Almost a year later y'all, and still no documentation that works. 😕
return ValueTask.FromResult(new RouteValueDictionary
{
{ "controller", controller },
{ "action", action }
});
it also does not work for me. My project is still using .NET core 2.1 :)
Any updates on this? Proper documentation is really needed