Nicolas Boisvert
Nicolas Boisvert

Reputation: 1171

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.

Upvotes: 30

Views: 26415

Answers (3)

Ashley Lee
Ashley Lee

Reputation: 3986

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; }
    }

Upvotes: 13

jamiegs
jamiegs

Reputation: 1791

This has been made easier with the addition of the Endpoint Routing feature. This article explains how to do it using the Endpoint Routing feature https://aregcode.com/blog/2019/dotnetcore-understanding-aspnet-endpoint-routing/

    var endpointFeature = context.Features[typeof(Microsoft.AspNetCore.Http.Features.IEndpointFeature)]
                                           as Microsoft.AspNetCore.Http.Features.IEndpointFeature;

    Microsoft.AspNetCore.Http.Endpoint endpoint = endpointFeature?.Endpoint;

    //Note: endpoint will be null, if there was no
    //route match found for the request by the endpoint route resolver middleware
    if (endpoint != null)
    {
        var routePattern = (endpoint as Microsoft.AspNetCore.Routing.RouteEndpoint)?.RoutePattern
                                                                                   ?.RawText;

        Console.WriteLine("Name: " + endpoint.DisplayName);
        Console.WriteLine($"Route Pattern: {routePattern}");
        Console.WriteLine("Metadata Types: " + string.Join(", ", endpoint.Metadata));
    }

Upvotes: 0

Gerke Geurts
Gerke Geurts

Reputation: 632

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);
        };
    }

Upvotes: 4

Related Questions