Slava Knyazev
Slava Knyazev

Reputation: 6081

Retrieving Route Data from Virtual Path in MVC Router

I am working on an .NET Core MVC application which requires alternative controller/action names to be allowed. To accomplish this, I am using my own Router on a MapRoute:

app.UseMvc(routes =>
        {
            routes.Routes.Add(new CustomRouter(routes.DefaultHandler));
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

My custom router observes the requested controller and action, and based on it places a new value into the RouteData in the request:

public async Task RouteAsync(RouteContext context)
{
   [...]
   if (requestedAction == "fakeAction")
    context.RouteData.Values["action"] = "realAction";

However, to determine the value of the requestedAction, I am basically taking the requested path, splitting it and getting the value of it that way. This seems suboptimal.

What I would like to do would look something like this:

var rr = new RouteBuilder(app);
var myRoute = rr.MapRoute(...).Build();
var myRouteData = myRoute.GetRouteData(context);
myRouteData["action"] == "fakeAction";

Another solution to this problem which I would very much enjoy is if I could do the following:

   app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "something",
                template: "{controller=Home}/{action=Index}/{id?}");
            routes.Routes.Add(new CustomRouter(routes.DefaultHandler));
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

But NOT have my something route actually route anything and only serve as a way to define the RouteData for my CustomRouter.

Is either of these possible? I do not like the idea us uncleanly implementing existing functionality as it is a both a code smell and a potential maintenance difficulty in the future.

Upvotes: 1

Views: 736

Answers (1)

Hossein
Hossein

Reputation: 3113

I hope this help you:

public async Task RouteAsync(RouteContext context)
{
    var requestPath = context.HttpContext.Request.Path.Value;

    if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
    {
        // Trim the leading slash
        requestPath = requestPath.Substring(1);
    }

    // Get the page that matches.
    var page = GetPageList()
        .Where(x => x.VirtualPath.Equals(requestPath))
        .FirstOrDefault();

    // If we got back a null value set, that means the URI did not match
    if (page == null)
    {
        return;
    }


    //Invoke MVC controller/action
    var oldRouteData = context.RouteData;
    var newRouteData = new RouteData(oldRouteData);
    newRouteData.Routers.Add(this.target);

    // TODO: You might want to use the page object (from the database) to
    // get both the controller and action, and possibly even an area.
    // Alternatively, you could create a route for each table and hard-code
    // this information.
 if (context.RouteData.Values["action"] == "fakeAction")
    newRouteData.Values["controller"] = "realController";
    newRouteData.Values["action"] = "realAction";

    // This will be the primary key of the database row.
    // It might be an integer or a GUID.
    newRouteData.Values["id"] = page.Id;

    try
    {
        context.RouteData = newRouteData;
        await this.target.RouteAsync(context);
    }
    finally
    {
        // Restore the original values to prevent polluting the route data.
        if (!context.IsHandled)
        {
            context.RouteData = oldRouteData;
        }
    }
}

app.UseMvc(routes =>
{
    routes.Routes.Add(
       new CustomRoute(routes.ServiceProvider.GetRequiredService<IMemoryCache>(), 
                       routes.DefaultHandler));

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


});

Upvotes: 1

Related Questions