Dynamic url rewriting with MVC and ASP.Net Core

12,292

Update: ASP.Net Core 1.1

According to the release notes, a new RewriteMiddleware has been created.

This provides several different predefined rewrite options and utility extension methods, which might end up modifying the request path as it has been done in this answer. See for example the implementation of RewriteRule

Specifically to the OP question, you would need to implement your own IRule class (either from scratch or extending an existing one like RewriteRule, which is based on a regex). You would possibly complement it with a new AddMyRule() extension method for RewriteOptions.


You can create your own middleware and add it to the request pipeline before the MVC routing.

This allows you to inject your code into the pipeline before the MVC routes are evaluated. This way you will be able to:

  1. Inspecting the path in the incoming request
  2. Search in the database for an eventId or hostId with the same value
  3. If event or host were found, update the incoming request path to Event/Index/{eventId} or Home/Organization/{hostId}
  4. Let the next middleware (MVC routing) take care of the request. They would see any changes to the request path made by the previous middleware

For example, create your own EventIdUrlRewritingMiddleware middleware that will try to match the incoming request path against an eventId in the database. If matched, it will change the original request path to Event/Index/{eventId}:

public class EventIdUrlRewritingMiddleware
{
    private readonly RequestDelegate _next;        

    //Your constructor will have the dependencies needed for database access
    public EventIdUrlRewritingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        var path = context.Request.Path.ToUriComponent();

        if (PathIsEventId(path))
        {
            //If is an eventId, change the request path to be "Event/Index/{path}" so it is handled by the event controller, index action
            context.Request.Path = "/Event/Index" + path;
        }

        //Let the next middleware (MVC routing) handle the request
        //In case the path was updated, the MVC routing will see the updated path
        await _next.Invoke(context);

    }

    private bool PathIsEventId(string path)
    {            
        //The real midleware will try to find an event in the database that matches the current path
        //In this example I am just using some hardcoded string
        if (path == "/someEventId")
        {
            return true;
        }

        return false;
    }
}

Then create another class HostIdUrlRewritingMiddleware following the same approach.

Finally add your new middlewares to the pipeline in the Startup.Configure method, making sure they are added before the Routing and MVC middleware:

        app.UseMiddleware<EventIdUrlRewritingMiddleware>();
        app.UseMiddleware<HostIdUrlRewritingMiddleware>();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

With this configuration:

  • / goes to the HomeController.Index action
  • /Home/About goes to the HomeController.About action
  • /Event/Index/1 goes to the EventController.Index action id=1
  • /someEventId goes to the EventController.Index action, id=someEventId

Please note there are no http redirects involved. When opening /someEventId in the browser there is a single http request and the browser will show /someEventId in the addess bar. (Even if internally the original path was updated)

Share:
12,292
Kevin Fizz
Author by

Kevin Fizz

Updated on June 05, 2022

Comments

  • Kevin Fizz
    Kevin Fizz almost 2 years

    I am re-writing my FragSwapper.com website (currently in Asp 2.0!) with ASP.Net Core and MVC 6 and I'm trying to do something I would normally have to break out a URL Re-Write tool along with db code and some Redirects but I want to know if there is a "better" way to do it in ASP.Net Core possibly with MVC Routing and Redirecting.

    Here are my scenarios...

    • URL Accessing the site: [root]

      What to do: Go to the usual [Home] Controller and [Index] View (no [ID]). ...it does this now:

      app.UseMvc(routes =>
      {
        routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
      });
      
    • URL Accessing the site: [root]/ControllerName/...yada yada...

      What to do: Go to the Controller, etc...all THIS works too.

    • The tricky one: URL Accessing the site: [root]/SomeString

      What to do: Access the database and do some logic to decide if I find an Event ID. If I do I go to [Event] Controller and [Index] View and an [ID] of whatever I found. If not I try to find a Host ID and go to [Home] Controller and [Organization] View with THAT [ID] I found. If I don't find and Event or Host go to the usual [Home] Controller and [Index] View (no [ID]).

    The big gotcha here is that I want to Redirect to one of three completely different views in 2 different controllers.

    So the bottom line is I want to do some logic when the user comes to my site's root and has a single "/Something" on it and that logic is database driven.

    If you understand the question you can stop reading now...If you feel the need to understand why all this logic is needed you can read on for a more detailed context.

    My site has basically two modes: Viewing an Event and Not Viewing an Event! There are usually 4 or 5 events running at an one time but most users are only interested in one event but it's a DIFFERENT event every 4 months or so..I have a [Host] entity and each Host holds up to 4 events a year, one at a time. Most users only care about one Host's events.

    I'm trying to avoid making a user always go to an event map and find the event and click on it since I have a limit to how many times I can show a map (for free) and it's really not necessary. 99.99% of the time a user is on my site they are on an Event screen, not my Home screens, and are interested in only one event at a time. In the future I want to code it so if they come to my website they go right to their event or a new event from their favorite host so I can avoid a LOT of clicks and focus my [Home] controller pages for newbs...but I don't have auto-login working yet so that's on the back burner.

    But for now I want hosts to always have the same url for their events: FragSwapper.com/[Host Abbreviation] ...and know it will always go to their current event which has a different ID every 4 months!!!

    Crazy...I know...but technically very easy to do, I just don't know how to do it properly in MVC with how things are done.

  • Kevin Fizz
    Kevin Fizz about 8 years
    Well that was certainly more than I expected in an answer and the concept most certainly is what I need! I figured there had to be some way to step in, I just didn't know where to look. I'm still playing with it, but I have no doubt middle-ware (which was a completely unknown concept to me) is what I need to do. I find the biggest problem with playing with .Net Core is that enough people haven't already had the same problems as me and it gives me very few places to look. Thank you very much for your time.
  • Daniel J.G.
    Daniel J.G. about 8 years
    No problem at all! Regarding the framework, I totally agree with you, many things have changed in asp.net core and sometimes is hard to find your way around it. I try to start from the docs when in doubt but even there you find many topics which aren't yet covered.
  • Daniel J.G.
    Daniel J.G. over 7 years
    Have you added the middleware before or after MVC? And you change the path before invoking next? What path are you setting and whats the controller and routes that should handle it? It's hard to know without seeing your code, but the answer above works fine in a new 1.0.1 app. Maybe try a new empty app and slowly start adding your code, if you still have a problem it might be worth opening a new question! (You might also found useful this article I wrote, which expands on the same idea)
  • Jérôme MEVEL
    Jérôme MEVEL over 7 years
    Sorry I just deleted my comment without seeing you already replied. I started again since the beginning and now it works, I don't know what was actually causing the problem. Thanks for your answer. Great code