I've been using the Search Engine Optimization Toolkit add-in for IIS 7 to analyse this website. If you've not used it before I recommend you give it a try; it's very good for picking up random bugs, spurious URL routing and dodgy links that you'd never have spotted otherwise. For example it would report issues on this site whereby the same content was accessible by two different URLs. Turns out some links would have a trailing slash, some not. (Mainly due to ASP.NET MVC's ActionLink being rubbish and missing off trailing slashes. Why does it not return links in the format specified by the route???) The SEO toolkit picked them up and now they're hopefully all fixed.
One set of problems I had trouble fixing was the root URL to this site, http://www.mrkwatkins.co.uk/. Currently I only have a blog so that URL redirects to the root URL of my blog, http://www.mrkwatkins.co.uk/Blog/. However the SEO Toolkit would report errors with the content returned from the redirect. For example it would tell me that there was no <h1> tag in the content. Now whether or not the toolkit should be analysing the content returned from a redirect or not I have no idea. What I do know is that they were the last errors on my report and needed removing!
The redirect was done using the standard RedirectResult in ASP.NET MVC. This in turn uses the standard Response.Redirect from ASP.NET. Trouble is that Response.Redirect spits out the following content as well as the 302 status code and location HTTP header:
<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href="http://www.mrkwatkins.co.uk/Blog/">here</a>.</h2>
</body></html>
So to fix the problems we need to change this XHTML. Turns out that is pretty simple to do. You can return whatever content you like from the request as normal. All you need to do is set the status code to 302 using the Response.StatusCode property and set the Location HTTP header using the Response.RedirectLocation property. I've wrapped all this up in a custom ViewResult class:
using System.Web;
using System.Web.Mvc;
using KWatkins.Validation;
namespace KWatkins.MrKWatkins.Web.Mvc
{
/// <summary>
/// A <see cref="ViewResult" /> that redirects the user to another location; allows
/// you to customize the XHTML returned by the redirection rather than use the
/// standard (invalid XHTML) retured by <see cref="HttpResponse.Redirect(string)" />.
/// </summary>
/// <remarks>
/// The redirect location is added to the <see cref="ViewRedirectResult.ViewData" />
/// with the key specified by <see cref="ViewRedirectResult.RedirectLocationViewDataKey" />.
/// </remarks>
public sealed class ViewRedirectResult : ViewResult
{
/// <summary>
/// The key used to store the <see cref="RedirectLocation" /> in the
/// <see cref="ViewRedirectResult.ViewData" />.
/// </summary>
public const string RedirectLocationViewDataKey = "RedirectLocation";
private readonly string redirectLocation;
/// <summary>
/// Initializes a new instance of the <see cref="ViewRedirectResult"/> class.
/// </summary>
/// <param name="redirectLocation">The redirect location.</param>
public ViewRedirectResult(string redirectLocation)
{
Validate.Argument(redirectLocation, "redirectLocation").IsNotNull().IsNotEmpty();
this.redirectLocation = redirectLocation;
ViewData[RedirectLocationViewDataKey] = redirectLocation;
}
/// <summary>
/// When called by the action invoker, renders the view to the response.
/// </summary>
/// <param name="context">The context within which the result is executed.</param>
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.StatusCode = 302;
context.HttpContext.Response.RedirectLocation = redirectLocation;
base.ExecuteResult(context);
}
/// <summary>
/// Gets the redirect location.
/// </summary>
/// <value>The redirect location.</value>
public string RedirectLocation
{
get
{
return redirectLocation;
}
}
}
}
You can then setup a new Redirect method in your base controller to create a ViewRedirectResult for you:
/// <summary>
/// Returns a <see cref="ViewRedirectResult" /> that redirects to the specified URL.
/// </summary>
/// <param name="url">The URL to redirect to.</param>
/// <returns>The <see cref="ViewRedirectResult" /> that redirects to the specified URL.</returns>
protected static new ViewRedirectResult Redirect(string url)
{
var result = new ViewRedirectResult(url)
{
ViewName = "Redirect"
};
return result;
}
All that remains is to create the Redirect view. We need this to be valid XHTML with a few extras to keep the SEO Toolkit happy; we need a content type, a description and a <h1>:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>MrKWatkins - Object Moved</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<meta name="description" content="This object has been moved." />
</head>
<body>
<div>
<h1>Object Moved</h1>
<p>
Object moved to <a href="<%= HttpUtility.HtmlAttributeEncode((string)ViewData["RedirectLocation"]) %>"><%= ViewData["RedirectLocation"] %></a>.
</p>
</div>
</body>
</html>
Et voila. The content of your redirects is now valid XHTML and the spurious warnings from your SEO Toolkit report go away.