301 Redirects in ASP.NET MVC (SEO and Refactoring)
Building a website is usually an iterative process. Not many individuals or teams have build a website without having requirements change along the way (If you have then I would like to know the secret). When requirements change sometimes this will require refactoring of the application to elegantly satisfy the business needs.
Some of the refactoring will include url/routes changes leaving many of your previous url/routes returning 404 errors. This is of course a definite no no! If you asked the question why, then here is the answer. After a search engine has indexed your website it remembers those urls/routes and serves them up in the search results. Whenever a user clicks through they either get a 404 error or redirected to another friendly not found page. Now ask yourself this, how often have you clicked through from a search engine and offered a page other than the one you requested and you remained on the website (if you remained longer than 5 seconds then this article is not for you)? The problem doesn't just end there. Imagine you have written a wonderful article and posted it on your wesbite/blog. Your readers have found this article very useful and decided to share it with their friends, then you go and change the url/route (possibly by altering the title); now this article cannot be found from wherever around the internet this url has been shared.
Just a few of the negatives:
An elegant and simple solution:
///
/// Base http response result
///
public class HttpResponseResult : ActionResult
{
#region Overrides of ActionResult
public override void ExecuteResult(ControllerContext context)
{}
#endregion
}
///
/// 301 redirect
///
public class PermanentRedirectResult : HttpResponseResult
{
private string _url { get; set; }
public PermanentRedirectResult(string url)
{
_url = url;
}
#region Overrides of ActionResult
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Status = "301 Moved Permanently";
context.HttpContext.Response.StatusCode = 301;
context.HttpContext.Response.AddHeader("Location", _url);
context.HttpContext.Response.Flush();
context.HttpContext.Response.End();
}
#endregion
}
Now the last and critical part of the entire solution. How do you use the PermanentRedirectResult class. Very simple.
public class RedirectsController : Controller
{
// Map the old path to this action
public ActionResult DefaultPage()
{
// Take action
return new PermanentRedirectResult(Url.Action("Index", "Home"));
}
// You can even redirect Html pages
// Simple map the route including the name of the page (somepage.htm)
private ActionResult HtmlPage()
{
return new PermanentRedirectResult(Url.Action("SomePage", "Products"));
}
}
Please feel free to post your comments.
Purely coincidental having the action called DefaultPage. Just simply demonstrating the principle of handling 404 responses. Ideally you should redirect to the new location of the page the user is looking for, but what happens when the page no longer exists? I suppose you can redirect to the landing page for the particular section of the website.
Nice post, Hatim! Excellent explanation, and sthrigat to the point.I would add one caveat about using WindowsIdentity.GetCurrent() to get user credentials in an application. That function doesn't necessarily return information about the logged in user all the time. Rather, it returns the ID of the user that's currently running the app thread.Where I've seen issues happen with using WindowsIdentity.GetCurrent to get the user's credentials is if a developer tries to use WindowsIdentity.GetCurrent to get the logged in user on a web application. In the application that I saw, it returned the NT Authority\Network Service (ID running the thread) rather than the actual user on the application.I was very happy that you mentioned in your post that ASP.NET app users should use HttpContext.Current.User to get the login information about the user on the application.
UngerYes. I've recently been using a wapeprr class to contain functions and properties that access the HttpContext.I use an interface to make the wapeprr class passable into my service and data access layers. The service/data access classes only need to know about the interface (not that it uses HttpContext), so my service/data access layers are not tightly coupled to MVC.I guess it depends on where you need to use the full URL, but either a wapeprr class or extension method sounds like a good place to me! Great suggestion!
another great post
liked how you have a default action for pages that no longer exists