January 25, 2019

Quickly Redirecting Old URL's with .NET Core

I recently deployed a big new .NET Core website to replace an existing ASP.NET based website. This new site has been optimized for performance, stability, and SEO value. As such, the URL structures for major sections of the website had changed. Many of page URL's were not majorly concerning as the search engines would easily pick up the new URL's in a matter of a few days. However, the old blog URL's that were shared publicly on many external channels became a big concern. I'll dive into how I solved this in a manner that works, quickly, easily and without major impacts to performance etc.

Many Options

Looking for a solution I first started out with looking at what other people are doing. A quick Google search proved that there are many different methods that people have used to solve the problem. Some suggested relying on IIS to do the rewrite, others suggested creating custom middleware, and many other solutions.

My Goal

Looking at the various suggested solutions, I wasn't satisfied with many of the items I found. I was looking for a solution that could meet the following acceptance criteria.

  • Easy to implement - I don't have much time to do this
  • Easy to verify - I don't want it dependent on IIS etc
  • Not introduce new middleware - This is a temporary fix, it should NOT add more to every page load

Looking at this criteria, I was able to discount most of the items I found, but then I got an idea!

ASP.NET Core Routing to the Rescue

In each of our applications we set up our routing for MVC to define our URL structures. Most of our conversations are about what our CURRENT application is doing, but it is important to remember that this pipeline is ALREADY executing on all requests. So if I can inject something here I'm limiting the amount of overhead I'm introducing, as well as the risk associated with making the change.

The implementation

The implementation, using basic Routing to assist was simple and comprised of only two steps.

Step 1: The Route

The first step of the process was to define the route. I was working with the redirection of URL's that started with the format of /Resources/Blog/Post/XX/ZZ. Where XX was an integer value and ZZ was a "SEO Friendly" url fragment. This provided a great setup and I was able to add the following route definition.

routes.MapRoute("oldBlog",
    "Resources/Blog/Post/{id}/{*fragment}",
    new {Controller = "Redirect", Action = "OldBlog"});

As you can see here I created a route called "oldBlog" that matches the format listed creating two parameters, id and fragment. I also define that it should send requests to the Redirect controller and the OldBlog action. I don't really care about the fragment portion of the URL, but needed to be sure to capture it.

Step Two: Lookup the Redirects

Inside of my redirect controller the process of redirecting was quite simple. I wanted to find the new URL based on the old id. If a match was found I want a permanent redirect to the new URL. If no match is found I want a permanent redirect to the blog page. For this I needed two parts, the first was a dictionary for mapping.

private static readonly IDictionary<int, string> 
MappingDictionary = new Dictionary<int, string>()
        {
            {400, "NewURLHere" }
        };

, In this case, I decided to use a private static dictionary due to the limited number of items. If this was a changing list, or if I had a large number of them I could easily adjust to use a different format. The key is that I identify the mapping from old to new here. If your mapping was other than an ID it is easy to adapt. The final piece is to then define the controller action that performs the redirect.

public IActionResult OldBlog(int id)
{
    if (MappingDictionary.TryGetValue(id, out string newPath))    
        return RedirectPermanent(newPath);
    return RedirectPermanent("/insights/blog");
}

And this is all that we need. Lookup from the dictionary, if found, redirect to it. If not, redirect to my default destination.

The Result

After implementing this solution, I was able to map all postings and properly manage the redirects. In addition, it was possible to do this without adding any additional pipeline overhead than the standard ASP.NET Core routing processes, leveraging all of their performance optimizations. I hope this helps you with your projects. Share any questions below.

tags: ASP.NET MVC, Performance, .NET Core, ASP.NET Core
comments powered by Disqus

Content provided in this blog is provided "AS-IS" and the information should be used at your own discretion.  The thoughts and opinions expressed are the personal thoughts of Mitchel Sellers and do not reflect the opinions of his employer.

Content Copyright

Content in this blog is copyright protected.  Re-publishing on other websites is allowed as long as proper credit and backlink to the article is provided.  Any other re-publishing or distribution of this content is prohibited without written permission from Mitchel Sellers.