Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Back to Basics: Rewriting a URL in ASP.NET Core


:P
On this page:

URL rewriting is the concept of changing the currently executing URL and pointing it at some other URL to continue processing the current request or redirecting to an external URL.

I ran into a few discussions and a StackOverflow question recently that asked how to do a URL Rewrite in ASP.NET Core. In classic ASP.NET you could use HttpContext.RewritePath() but that doesn't exist in .NET Core. Turns out however, that it's even easier in .NET Core to rewrite a URL if you know where to update the path.

In this back to basics post I'll talk about the difference between a Rewrite and Redirect and when and how you can use them in ASP.NET Core.

A few Rewrite Scenarios

There a few common scenarios for re-writing URLs:

  • Re-linking legacy content
  • Creating 'prettier URLs'
  • Handling custom URLs that need to actually process something else
  • Redirecting from one Operation to another as part of Application code

The first two are pretty obvious as they are simple transformations - go from one URL to another because either some content has moved or as part of a change of state that requires to user to see something different. This is quite common especially in applications that have been around for a while and have changed around a bit.

A less common but arguably more useful use case are URL transformations for tools that render custom content. For example, my westwind.aspnetcore.markdown page processing middleware, lets you access either an .md 'page' or an extensionless folder with a specified .md file inside of it. When one of these monitored URLs is accessed, a rewrite middleware actually routes the original request to a common markdown controller endpoint that renders the Markdown into a page template while the original URL stays the same. This is a classic rewrite scenario.

The most common rewrite scenario however, likely is at the application level. If you're building applications you've almost certainly had a need to redirect to another endpoint at some point. Think Login and Authentication - you hit a Login URL, the URL logs you in and as part of the login routine - once successful - you're redirected to a start page, or a redirect URL that was passed in. Most HTML applications and some REST services that requires identity have a handful of requests like this that require explicit redirects. This is a classic redirect scenario.

Rewriting vs. Redirecting a URL

As the above examples are meant to show there are two different ways to change the current requests endpoint:

  • Rewrite the current URL
  • Redirect to an external URL

The two tasks are similar but yet different in their execution:

  • Rewriting
    Rewriting actually changes the current request's path internally and continues processing the current request with all of it's existing state through the middleware pipeline. Any middleware registered after the rewrite sees the new URL and processes the remainder of the request with the new path. All of this happens as a part single server request.

    The URL of the request displayed in the address bar stays the same - the browser location doesn't change to the rewritten URL.
    Uses context.Request.Path

  • Redirecting
    Redirecting actually fires a new request on the server by triggering a new HTTP request in the browser via an 302 Moved or 301 Moved Permanently HTTP Response header that contains the redirection URL. A redirect is an HTTP header response to the client that instructs the client to:
    Go to the URL specified in this response header.

    HTTP/1.1 302 Moved
    Location: https://west-wind.com/wwhelp
    

    Redirects can also use 301 Moved Permanently to let search engines know that the old URL is deprecated and the new URL should be used instead.
    Uses context.Response.Redirect()

As you can imagine, if you have a choice between re-writing and a redirect, the rewrite tends to be more efficient as it avoids a server round trip by simply re-writing the URL as part of the current request. The caveat is that the browser's location/address bar does not reflect the new rewritten URL - if that's important (and often it is) then a redirect is required.

A rewrite can also keep request information, so if you have POST or PUT operation that has data associated with it, that data stays intact.

A Redirect() on the other hand is always reissued as an HTTP GET operation by the browser so you can't redirect form input.

Intercepting URLS in ASP.NET Core

If you plan to intercept requests and rewrite them , the most likely place you'd want to do this is in ASP.NET Core is in Middleware. Rewrite components tend to look at incoming request paths or headers and determine whether they need to re-write the URL to something else.

If you want to do this in ASP.NET Core the easiest way to do this is to use app.Use() inline middleware which you can add to your Startup.Configure() method.

Re-Writing a URL

Here's how to handle a Rewrite operation in app.Use() middleware:

app.Use(async (context,next) =>
{
    var url = context.Request.Path.Value;

    // Rewrite to index
    if (url.Contains("/home/privacy"))
    {
        // rewrite and continue processing
        context.Request.Path = "/home/index";
    }

    await next();
});

This intercepts every incoming request and checks for a URL to rewrite and when it finds one, change the context.Request.Path and continues processing through the rest of the middleware pipeline. All subsequent middleware components now see the updated path.

You can use a similar approach for Redirecting, but the logic is slightly different because a Redirect is a new request and you'll want to terminate the middleware pipeline:

app.Use(async (context,next) =>
{
    var url = context.Request.Path.Value;

    // Redirect to an external URL
    if (url.Contains("/home/privacy"))
    {
        context.Response.Redirect("https://markdownmonster.west-wind.com")
        return;   // short circuit
    }

    await next();
});

Response.Redirect() in ASP.NET Core doesn't do automatic path fixups as classic ASP.NET did. You can't use: Response.Redirect("~/docs/MarkdownDoc.md") but you have to specify the whole full site relative path or absolute Url.

Unless your target URL includes application external URLs I'd argue there's no good reason to use a Redirect in middleware. It only makes sense for external, non-application URLs in that scenario.

However, Redirects are more commonly used when you need to redirect as part of your application/controller logic, where you can't use a rewrite operation because the path has already been routed to your application endpoint/controller method.

Notice also in the code above that it's a good idea to short-circuit the Response when redirecting, rather than continuing through the rest of the middleware pipeline.

Routing Order is Important!

If you plan on using Url Rewriting or generic redirection, it's important that you do it at the right time in the routing and request handling sequence during start up. You want to make sure you do your rewriting before the operations you need to rewrite to but after anything that you don't want to have routed (like static files most likely).

Specifically, rewrites should be declared before

  • app.UseRouting(),
  • app.UseRazorPages()
  • app.UseMvcControllers()
  • or any other end point route handlers

You may also need to decide how static pages are handled before or after your rewriting. Ideally if your static content is not affected you'd want to declare it prior to your rewrites, but if paths are affected then put it after.

Here's what a typical rewrite and routing setup might look like:

// don't rewrite static content - if you do put after app.Use()
app.UseStaticFiles();

// do your rewrites against anything that FOLLOWS
app.Use(async (context, next) => 
   // ... rewrite logic
});

// IMPORTANT: Do after custom redirects so rewrites
//            are applied here
app.UseRouting();

The ASP.NET Core Rewrite Middleware Module

For more complex rewrite and redirect scenarios you can also use the full-fledged ASP.NET Core Rewrite Middleware. It's beyond the scope of this article to go into the details of this complex module, but basically it provides the ability to set regEx based rewrites and redirects and a number of different and some common rewrite operations.

Using the middleware is great if you have complex rules for many URLs or need follow specific patterns to re-route content. There are also helper for doing common things like routing http:// to https:// and routing the www. url to the root domain.

Here's what the middleware looks like (from the docs):

var options = new RewriteOptions()
            .AddRedirectToHttpsPermanent();
            .AddRedirect("redirect-rule/(.*)", "redirected/$1")
            .AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2", 
                skipRemainingRules: true)
            
        app.UseRewriter(options);

Summary

URL Rewriting in ASP.NET Core is easy and simply changing context.Request.Path is all that it takes to re-write the URL to route to some different endpoint. For external URLs you can use context.Response.Redirect() just as you could in older versions, but be aware that Response.Redirect() doesn't automatically fix up virtual path (~/) syntax.

For simple use cases I prefer handling rewrites using explicit middleware as shown above. I think an inline app.Use() snippet is fine because for this scenario, it actually fits nicely into the config pipeline. Unless you think you need to reuse your rewrite logic in other applications there's rarely a need to write dedicated middleware for this.

For more complex use cases that require rules based evaluation there's no need to reinvent the wheel as ASP.NET core provides rewrite middleware that uses all the common regex expansions you would expect from HTTP based rewrite modules.

Now excuse me, while I redirect back to my regular scheduled programming...

Resources

this post created and published with the Markdown Monster Editor
Posted in ASP.NET Core  

The Voices of Reason


 

Rich Ellis
March 13, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Doesn’t ASP.NET Core 3 (if not earlier) have a URL rewriting middleware package implicitly included?

What are your thoughts on where this kind of thing should ideally be handled? It’s reliable to set it up in the middleware chain, but I always cringe a little β€” it seems like this sort of thing ought to be dealt with in front of the app, like in IIS or nginx or whatever. But then that behavior is tied to a specific platform, and you lose the ability to push the app elsewhere without having to recreate it.


Rick Strahl
March 13, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Yes the re-write Middleware can be used for setting up more full fledged rules and that's very useful.

This post is about one off rewrite operations. I find that in most cases having a whole rules engine is overkill if all you have a couple of URLs you need to rewrite. To me it's actually much cleaner to define rules in code instead of some special rule or regEx format with funky if/then logic πŸ˜ƒ But that makes good sense if you need to deal with lots of URLs and legit URL patterns. For one offs - not so much.

But then that behavior is tied to a specific platform

Yup, but it depends on what you're doing. If you're dealing with content that the front end Web server deals with (like Static Page handling or toplevel path routing) then definitely that has to be handled at the server level.

However, if you have application rewrites I would prefer to have them in app as opposed to on the server level. But these days with proper configuration pipelines that's probably less an issue then forgetting to add your re-write rules into a web.config πŸ˜ƒ


Rick Strahl
March 13, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

@Rich - added a section at the end pointing at the ASP.NET Core Middleware. Thanks for the reminder.


Rich Ellis
March 13, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Good points. I agree the regex stuff gets annoying.

Forgetting to update web.config is something I would never do, what is being implied here? πŸ˜‰


Arian
April 27, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Great article as always. Question, say I have an asp.net core web app servicing www.mainsite.com, is it possible to use URL Redirect to have all paths (ie. www.mainsite.com/api/*) to be serviced by a second asp.net core web app?


Rick Strahl
April 27, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

@Arian - sure. But most likely you'd want to handle that at the Web Server front end, not in your application. So if you're hosting on Windows with IIS you'd use IIS Rewrite to perform that task. On Linux you'd use the nginX or whatever to do the same in the nginX configuration before it hits your application.

That said, you can can do this inside of your app too - after all it's a Redirect which is a valid HTTP response from your application and it can go across domains if necessary.


Laurent Bugnion
April 30, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Hey Rick πŸ˜ƒ small typo in your last code snippet, check the semicolon πŸ˜‰

Take care Laurent


Giorgiy
May 28, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Hey, Rick! Can you help me?

I'm started base template Asp.Net Core 3.1 WebApplication with MVC template. And add Request.Path changing code from this post. But isnt working.

Context.GetEndpoint is null...


Steven Rothwell
June 23, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

One thing you may want to note in the article is where the app.Use() needs to be in the Startup.cs. I had to add it above app.UseRouting(); but below app.UseStaticFiles(); for it to property rewrite the url.


Perustaja
August 12, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Thanks Steven, this was causing mine to not work. Interestingly, the routing attributes work still prior to that call, I may not know enough but that sounds a bit odd.

I'm using it for a global 404 redirect.


Lachlan B
October 04, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Just to be pedantic. The rewrite middleware only contains an option for redirecting non www traffic to a www address, not the other way around. Which is annoying because who uses www anymore? πŸ˜ƒ Looks like it might be done in dotnet 5 though - https://github.com/dotnet/aspnetcore/issues/12978


Scott Beam
December 21, 2020

# re: Back to Basics: Rewriting a URL in ASP.NET Core

i spent 2 days on trying to redirect subdomains in core 3.1 digging thru posts on stackexchange and then found this post and had it working within 5 minutes. thanks huge. donation on the way


Randy Burden
November 09, 2021

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Thanks. I used your redirect example to come up with the following:

private void HandleRedirects(IApplicationBuilder app)
{
    // Old Url, New Url
    var redirects = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
    {
        {"/Reports/BatchReport", "/Reports/BatchDetailReport" }
    };

    app.Use(async (context, next) =>
    {
        if (redirects.TryGetValue(context.Request.Path, out var redirectUrl))
        {
            context.Response.Redirect(redirectUrl);
            return;
        }

        await next();
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    HandleRedirects(app);

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    // ...
}

Rick Strahl
December 23, 2021

# re: Back to Basics: Rewriting a URL in ASP.NET Core

@Randy - I like it. Nice and simple. The Rewrite middleware will do this for you as well though albeit with a little more cryptic syntax πŸ˜„


Harvey Berman
December 23, 2021

# re: Back to Basics: Rewriting a URL in ASP.NET Core

I'm new to ASP.NET Core, and am not a professional programmer. I struggled for 2 days trying to figure out how to rewrite URL's using the endpoint routing system in ASP.NET Core. Never got it to do what I wanted it to do. Then, I found your clear, easy-to-understand explanation. Just what I needed. I am very happy to send a donation your way. Many thanks.


Andy
April 26, 2022

# re: Back to Basics: Rewriting a URL in ASP.NET Core

You mind adding in big, black, bold, letters that app.UseRouting(); has to be placed after app.use

yea, it's "obvious" in retrospect, but it's not something one might actually notice for several hours of testing...


Terry
February 20, 2023

# re: Back to Basics: Rewriting a URL in ASP.NET Core

Great article. Floundering on a bug and came across this post. Struggling with redirect to "path", "./path", "/path", or "~/path".

I started to write the full description/question here, but then realized maybe I should put it on stackoverflow. If you see this comment and have the time, I've posted my question at:

https://stackoverflow.com/questions/75510599/net-core-response-redirect-works-in-development-but-not-on-iis-usage-includ

Thanks


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024