ASP.NET MVC 5 and URL Canonicalization
One of the things that I love the most about development is how it lends itself so well to the notion of continued improvement, continual refinement, and the idea that there are always ways to improve, hone, and optimize solutions that we’ve created in the past. I wanted to touch upon a solution I addressed a few years ago – with an update showing a better, more optimized, and more viable solution for URL Canonicalization within MVC applications.
June 11, 2015
One of the things that I love the most about development is how it lends itself so well to the notion of continual improvement, continual refinement, and continual optimization of solutions that we’ve created in the past. True, if you're not careful, it’s easy to get endlessly mired down in ‘tweaking’ to the point where you can never get anything truly done. But by the same token, if you once found that a hammer would work to solve a problem, you’re missing out if you think said hammer can solve all of your problems – or that your, well, metaphorical hammer doesn’t need modifying from time to time either.
To that end, I wanted to touch upon a solution I addressed a few years ago – with an update showing a better, more optimized, and more viable solution for URL Canonicalization within MVC applications.
The Need for Canonicalization
As a SQL Server Consultant, SEO is an important thing that I need to keep tabs of when it comes to my own website. To put this into perspective: I can easily spend $30 (or more) per click on some of the search terms that might help lead potential clients to my site – simply because there’s a fair degree of competition within the field within which I work. While I’m not afraid to drop a few pesos into helping potential clients find me, I’d still be a fool to not take organic SEO very seriously.
A few years ago I wrote about some techniques for helping boost SEO with ASP.NET MVC applications. Details for why canonicalization makes sense were covered in that previous article – but, again, it was written a few years ago. In the years since, Google (and other search engines are sure to follow) has also put a bigger focus or emphasis on HTTPS over HTTP as a ranking consideration. As such, the filter I wrote previously isn’t as viable as it now should be. Also, I’ve made a few changes, alterations, optimizations, and improvements over the year – so I figured it might be worthwhile to share the new shape and face of the Canonicalization filter I’m using for my own efforts.
Show me the Filter
As before, the Canonicalization filter I’ve built is implemented as an ASP.NET ActionFilter – something that can easily be applied to controllers when/as needed. (I actually implement a ‘SiteController’ for all sites I build, wire that up with a number of extensions, derive most of my other controllers from it and – when SEO is a concern – just drop the my Canonicalization filter/attribute over the top of this SiteController instead of decorating each controller individually.)
Logic for this filter is pretty simple. First, there are three properties defined by the filter itself:
public bool RedirectToWwwHost { get; set; }public bool ForceLowerCase { get; set; }public bool ForceHttps { get; set; }
Each of these, in turn, helps define the logic that’ll be processed when evaluating the current URI each time my canonicalization attribute fires. Granted, you may have different preferences or needs (some developers hate the idea of using www – so they could simply invert the logic shown here – IF they haven’t tackled this already at the DNS level).
Usage of this filter, in turn, is pretty straight-forward – you simply decorate a controller with the attributes you’d like to address or correct – like so:
[Canonicalize(ForceHttps = true, RedirectToWwwHost = true, ForceLowerCase = true)]public class HomeController : Controller{ // etc...}
From there, the logic to tackle those checks is pretty straight-forward – as you can see from the code below:
public override void OnActionExecuting(ActionExecutingContext filterContext){ HttpContextBase context = filterContext.HttpContext; if (context.Request.Url != null && Settings.IsProductionServer) { string path = context.Request.Url.AbsolutePath ?? "/"; string query = context.Request.Url.Query ?? ""; string host = context.Request.Url.Host; string redirectUrl = ""; if (RedirectToWwwHost && (!this.IsAllowedHostName(host))) redirectUrl = GenerateRedirect(Settings.PreferredHostName, path, query); if (ForceHttps && (!context.Request.IsSecureConnection)) redirectUrl = GenerateRedirect(GetRedirectHost(host), path, query); if (ForceLowerCase && (!RequestedPathIsLowerCase(path))) redirectUrl = GenerateRedirect(GetRedirectHost(host), path, query); if (redirectUrl.HasValue()) { filterContext.Result = new RedirectResult(redirectUrl, true); return; } } base.OnActionExecuting(filterContext);}
The main strength of this filter is that it is designed to ensure that if users (or bots) somehow end up on your site at a non-preferred URI that only a SINGLE redirect is needed to get them exactly where they should be (i.e., to a preferred, or canonicalized version of your URI).
Or, in other words, while I know that ASP.NET MVC provides a [RequireHttps] attribute out of the box that already works well, using it alone to ‘force HTTPS’ would result in a single, discrete, HTTP 301 redirect to ‘force’ users over to HTTPS – which is a good thing. But, when it comes to SEO, you also want to AVOID the prospect of ‘chaining’ redirects back to back – as that can actually hurt your ranking. As such, the newer Canonicalization attribute I’ve created is designed to try and catch any and all conditions that you’ve specified – and correct them with a single redirect.
Show me the Code
Granted, this is a pretty trivial and simplistic filter. But, by the same token, one of the fun things about development is creating solutions, which on the surface, look to be obvious and trivial – but which are usually a bit more complex than they might seem. Or, in the case of this particular filter – it’s been tried and tested in a number of sites and has slowly been honed and tuned so that it’s as efficient as it is today – which, in turn, is part of why it looks to be so simple. If you’d like to learn more about this filter, or put it to use, I’ve put a copy of it into a sample MVC 5 website – which you can download here.
About the Author
You May Also Like