Quantcast
Viewing all articles
Browse latest Browse all 10

Solving the Trailing Slash Problem in ASP.Net MVC using a Custom UrlRoutingModule

There’s quite some annoyence about how ASP.Net routing handles trailing slashes. I agree, it would have been nice if one could enforce whether or not to use a trailing slash.

I’ve always taken good care of my trailing slashes. Almost every link comes from my custom UrlHelper or extension methods (for convenience – e.g. Model.Product.GetThumbnailUrl()). Anyway, that doesn’t mean it stopped bugging me! For one, external links (SEO) are not under your control, I don’t really know how serious this duplicate content issue is, but I’m not willing to take a chance on it. Another unsolved annoyence is when you add content pages. You always have to think twice about trailing slashes in every little link you include.

This week, I thought it was about time to go for a more generic solution. I started by googling for it and almost ended up using the new IIS7 URL Rewrite Module just like Scott suggested in his post. The main reason I didn’t was because I hate managing things in two places. Especially when their so closely related.

And then I thought: Why not let the system do what you ask it to do? How hard can it be to make the system respect you route mappings? If I map a route with url "{category}/" or "{category}/{product}" I want to make sure category urls end with a slash and products don’t. That simple.

It seemed to me that tuning ASP.Net MVC’s UrlRoutingModule was the most abvious thing to do, so here’s what I’ve done:

public class UrlRoutingModule : System.Web.Routing.UrlRoutingModule
{
	private const string SLASH = "/";

	public override void PostResolveRequestCache(HttpContextBase context)
	{
		var routeData = RouteCollection.GetRouteData(context);

		var redirecturi = GetRedirectUri(routeData, context.Request.Url);

		if (redirecturi != null)
		{
			context.Response.RedirectPermanently(redirecturi);
			return;
		}

		base.PostResolveRequestCache(context);
	}

	private static Uri GetRedirectUri(RouteData routeData, Uri requestedUri)
	{
		if (routeData == null) return null;

		var route = routeData.Route as Route;
		if (route == null) return null;

		var requestedSegments = GetSegmentCount(requestedUri.AbsolutePath);
		var mappedSegements = GetSegmentCount(route.Url);

		if (requestedSegments == mappedSegements)
		{
			var slashRequired = route.Url.EndsWith(SLASH);

			if (slashRequired && !requestedUri.AbsolutePath.EndsWith(SLASH))
				return requestedUri.Append(SLASH);

			if (!slashRequired && requestedUri.AbsolutePath.EndsWith(SLASH))
				return requestedUri.TrimPathEnd(SLASH[0]);
		}
		else if (!requestedUri.AbsolutePath.EndsWith(SLASH)) // requestedSegments < mappedSegements
		{
			return requestedUri.Append(SLASH);
		}

		return null;
	}

	private static int GetSegmentCount(string path)
	{
		return path.Split(SLASH.ToCharArray(), StringSplitOptions.RemoveEmptyEntries).Length;
	}
}

Note: If your route ends with optional segments, urls that don’t include them always end up with a trailing slash.

Another note: The Append and TrimPathEnd are Uri extension methods:

public static class UriExtensions
{
	public static Uri Append(this Uri uri, string value)
	{
		return new Uri(GetAbsoluteUriWithoutQuery(uri) + value + uri.Query);
	}

	public static Uri TrimPathEnd(this Uri uri, params char[] trimChars)
	{
		return new Uri(GetAbsoluteUriWithoutQuery(uri).TrimEnd(trimChars) + uri.Query);
	}

	private static string GetAbsoluteUriWithoutQuery(Uri uri)
	{
		var ret = uri.AbsoluteUri;
		if (uri.Query.Length > 0) ret = ret.Substring(0, ret.Length - uri.Query.Length);
		return ret;
	}
}

One more note:: I’m fully aware that this module alone does not solve the entire trailing slash problem, but minor additional (possibly project-specific) things like a custom UrlHelper (what I did/had to do, I spare you the details) or solutions like this method are easy enough to make it complete.


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 10

Trending Articles