1
votes

Would it be possibile in asp.net core 2.2 to intercept, for each request, the route data and (possibly) change the values, such as the target controller / action values?

My need is to use a localized url scheme, with two letter culture specification and localized controller / actions names, such as:

/en/contact-us
/it/contatti
/de/kontakte

i don't want to use attribute routing to decorate controllers and actions; instead, i want to capture the route data and basing on culture and controller/action, obtain the target controller and action, maybe with a dictionary association. I've already done such thing in asp.net (not core) mvc.

Until now i'm able to capture routing with this code:

        app.Use(async (context, next) =>
        {
            var routeData = context.GetRouteData();


            routeData.Values["controller"] = ....;
            routeData.Values["action"] = .....;

            await next();
        });

but route data is filled only for urls precisely mapped to an existing controller/action else is null; it seems this is due to the AttributeRouting.CreateAttributeMegaRoute method of asp.net core, which only maps existing controllers/actions.

1
Why would you want to do that?Tobias Tengler
Your custom middleware won't work because there'll be no RouteData before it enters a RouteMiddleware (e.g.UseMvc()). One way to walk around it is to create a Router to map a middleware and use mvc when matched.itminus
@itminus, any working example please?ʞᴉɯ

1 Answers

1
votes

Here's a walkaround that works for me:

// if the route matches this pattern, let's say:
app.UseMvc(routeBuilder => {
    routeBuilder.MapRoute("route1",template: "/{controller=Home}/{action=Index}");
});

// else if the route matches `{culture=en-US}/{controller=Home}/{action=Index}`
app.UseRouter(routeBuilder =>{
    var template = "{culture=en-US}/{controller=Home}/{action=Index}";
    routeBuilder.MapMiddlewareRoute(template, appBuilder =>{
        appBuilder.Use(async(context, next)=>{
            var routeData = context.GetRouteData();
            var controller = routeData.Values["controller"] as string;
            var action= routeData.Values["action"] as string;
            var culture= routeData.Values["culture"] as string;
            // get the real backing path according to current route data
            context.Request.Path = getNormalizedPath(routeData);  
            await next();
        });
        appBuilder.UseRequestLocalization();
        appBuilder.UseMvc(rb=>{
            rb.MapRoute(name:"cultureRoute",template:template);
        });
    });
    // if you have other MVC routes, add them below:
    // routeBuilder.MapRoute(name:"mvcRoutes",template: "{area:exists}/{controller=Home}/{action=Index}");
});

// else if doesn't match the above pattern, let's say:
app.UseMvc(routeBuilder => {
    routeBuilder.MapRoute("route3",template: "/test/mvc/{controller=Home}/{action=Index}");
});
private string getNormalizedPath(RouteData routeData)
{
    var culture= routeData.Values["culture"] as string;
    var controller = routeData.Values["controller"] as string;
    var action= routeData.Values["action"] as string;

    controller = ... real controller according to current culture & controller string
    action = ... real action according to current culture & controller string
    return $"/{culture}/{controller}/{action}";
}

You need custom the getNormalizedPath(routeData) to get the real path that will be routed to the backing controller/action.

To set the Request Localization Feature automatically according to current route path at the same time, you'll need to insert a RouteDataRequestCultureProvider:

services.Configure<RequestLocalizationOptions>(options =>
{
    var supportedCultures = new[]{
        new CultureInfo("en"),
        new CultureInfo("fr"),
        new CultureInfo("de"),
        new CultureInfo("it"),
    };
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;
    options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider());
});

As found by @ʞᴉɯ in the comments, the first UseMvc() doesn't work well for 2.2. We need change the MVC Compatibility to CompatibilityVersion.Version_2_1:

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);