Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.Net core get RouteData value from url

I'm wokring on a new Asp.Net core mvc app. I defined a route with a custom constraint, which sets current app culture from the url. I'm trying to manage localization for my app by creating a custom IRequestCultureProvider which looks like this :

public class MyCustomRequestCultureProvider : IRequestCultureProvider
    {
        public Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
        {
            var language= httpContext.GetRouteValue("language");

            var result = new ProviderCultureResult(language, language);
            return Task.FromResult(result);
        }
    }

My MyCustomRequestCultureProvider is hit on every request, which is ok. My problem is that in the MVC pipeline, DetermineProviderCultureResult method from my provider is hit before the routing process, so httpContext.GetRouteValue("language") always return null.

In previous version of MVC, I had the possiblity to manually process my url through the routing process by doing this

var wrapper = new HttpContextWrapper(HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(wrapper);
var language = routeData.GetValue("language")

I can't find a way to do the same thing in the new framewrok right now. Also, I want to use the route data to find out my langugae, analysing my url string with some string functions to find the language is not an option.

like image 909
Nicolas Boisvert Avatar asked Jul 21 '16 13:07

Nicolas Boisvert


2 Answers

There isn't an easy way to do this, and the ASP.Net team hasn't decided to implement this functionality yet. IRoutingFeature is only available after MVC has completed the request.

I was able to put together a solution that should work for you though. This will setup the routes you're passing into UseMvc() as well as all attribute routing in order to populate IRoutingFeature. After that is complete, you can access that class via httpContext.GetRouteValue("language");.

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    // setup routes
    app.UseGetRoutesMiddleware(GetRoutes);

    // add localization
    var requestLocalizationOptions = new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture("en-US")
    };
    requestLocalizationOptions.RequestCultureProviders.Clear();
    requestLocalizationOptions.RequestCultureProviders.Add(
        new MyCustomRequestCultureProvider()
    );
    app.UseRequestLocalization(requestLocalizationOptions);

    // add mvc
    app.UseMvc(GetRoutes);
}

Moved the routes to a delegate (for re-usability), same file/class:

private readonly Action<IRouteBuilder> GetRoutes =
    routes =>
    {
        routes.MapRoute(
            name: "custom",
            template: "{language=fr-FR}/{controller=Home}/{action=Index}/{id?}");

        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    };

Add new middleware:

public static class GetRoutesMiddlewareExtensions
{
    public static IApplicationBuilder UseGetRoutesMiddleware(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        var routes = new RouteBuilder(app)
        {
            DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
        };
        configureRoutes(routes);
        routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
        var router = routes.Build();

        return app.UseMiddleware<GetRoutesMiddleware>(router);
    }
}

public class GetRoutesMiddleware
{
    private readonly RequestDelegate next;
    private readonly IRouter _router;

    public GetRoutesMiddleware(RequestDelegate next, IRouter router)
    {
        this.next = next;
        _router = router;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var context = new RouteContext(httpContext);
        context.RouteData.Routers.Add(_router);

        await _router.RouteAsync(context);

        if (context.Handler != null)
        {
            httpContext.Features[typeof (IRoutingFeature)] = new RoutingFeature()
            {
                RouteData = context.RouteData,
            };
        }

        // proceed to next...
        await next(httpContext);
    }
}

You may have to define this class as well...

public class RoutingFeature : IRoutingFeature
{
    public RouteData RouteData { get; set; }
}
like image 154
Ashley Lee Avatar answered Oct 22 '22 16:10

Ashley Lee


Based on Ashley Lee's answer, here is an optimized approach that prevents duplicate route configuration.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
   // setup routes
   var mvcRouter = BuildMvcRouter(app, routes =>
   {
       routes.MapRoute(
           name: "custom",
           template: "{language=fr-FR}/{controller=Home}/{action=Index}/{id?}");
       routes.MapRoute(
           name: "default",
           template: "{controller=Home}/{action=Index}/{id?}");
    });

    // add route data initialization middleware
    app.Use(next => SetRouteData(next, mvcRouter));

    // add localization middleware
    var requestLocalizationOptions = new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture("en-US")
    };
    requestLocalizationOptions.RequestCultureProviders.Clear();
    requestLocalizationOptions.RequestCultureProviders.Add(
        new MyCustomRequestCultureProvider()
    );
    app.UseRequestLocalization(requestLocalizationOptions);

    // add mvc routing middleware
    app.UseRouter(mvcRouter);
}

This depends on the following two methods that must be added to the StartUp class:

private static IRouter BuildMvcRouter(IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
    if (app == null) throw new ArgumentNullException(nameof(app));
    if (configureRoutes == null) throw new ArgumentNullException(nameof(configureRoutes));

    app.ApplicationServices.GetRequiredService<MiddlewareFilterBuilder>().ApplicationBuilder = app.New();
    var routeBuilder = new RouteBuilder(app)
    {
        DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>()
    };
    configureRoutes(routeBuilder);
    routeBuilder.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));

    return routeBuilder.Build();
}

private static RequestDelegate SetRouteData(RequestDelegate next, IRouter router)
{
    return async context =>
    {
        var routeContext = new RouteContext(context);
        await router.RouteAsync(routeContext);

        if (routeContext.Handler != null)
        {
            context.Features[typeof(IRoutingFeature)] = new RoutingFeature
            {
                RouteData = routeContext.RouteData
            };
        }

        await next(context);
    };
}
like image 4
Gerke Geurts Avatar answered Oct 22 '22 16:10

Gerke Geurts