Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

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 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 stays the same - it 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. A redirect is an HTTP header response to the client that instructs the client to: Go to the URL I specify in this response header.

    HTTP/1.1 302 Moved
    Content-Type: text/html; charset=UTF-8
    Location: https://west-wind.com/wwhelp
    

    Redirects can also use 301 Moved Permanently to let search engines know that the old URL is essentially deprecated.
    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.

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

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.

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

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.


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