This is an interesting question which I wanted to know the answer to. It turns out this is fairly straightforward. There are really 2 things you need in order to achieve this.
- Convert the string to Roman characters.
- Do a 301 redirect to the new URL.
Converting the String to Roman Characters
I found the answer here, but modified it to be an extension method to make it simpler to use.
using System;
using System.Globalization;
using System.Text;
public static class StringExtensions
{
public static string RemoveDiacritics(this string text)
{
var normalizedString = text.Normalize(NormalizationForm.FormD);
var stringBuilder = new StringBuilder();
foreach (var c in normalizedString)
{
var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
}
Doing a 301 Redirect to the New URL
This is a bit more complicated. While you could do a 302 redirect (which all browser's adhere to), a 301 is more SEO-friendly so that is the approach I am showing here. Unfortunately, some browsers do not automatically follow a 301 redirect. So, a few extra steps are necessary to ensure that if the browser doesn't, the user will be redirected via a client-side 302 redirect and, failing that, shown a link where they can jump to the next page.
RedirectToRomanizedUrlPermanent.cs
We start by using a route to clean the URL. If the clean URL is different from the original one (that is, one or more characters were replaced), we send the request to a controller named SystemController
to handle the 301 redirect.
public class RedirectToRomanizedUrlPermanent : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var path = httpContext.Request.Path;
var romanPath = path.RemoveDiacritics();
if (!path.Equals(romanPath, StringComparison.OrdinalIgnoreCase))
{
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values["controller"] = "System";
routeData.Values["action"] = "Status301";
routeData.DataTokens["redirectLocation"] = romanPath;
return routeData;
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
SystemController.cs
Next, we have our controller, whose purpose is to actually provide the 301 status. If the browser doesn't respect the 301, a view is presented to the user.
using System.Net;
using System.Web.Mvc;
public class SystemController : Controller
{
public ActionResult Status301()
{
var redirectLocation = this.Request.RequestContext.RouteData.DataTokens["redirectLocation"] as string;
// IMPORTANT: Don't cache the 301 redirect because browsers tend to keep it around
// meaning you cannot update the browser to a different URL from the server side.
Response.CacheControl = "no-cache";
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = redirectLocation;
ViewBag.RedirectLocation = redirectLocation;
return View();
}
}
/Views/System/Status301.cshtml
The view will attempt to redirect the user automatically via JavaScript and via Meta-Refresh. Both of these can be turned off in the browser, but chances are the user will make it where they are supposed to go. If not, you should tell the user:
- The page has a new location.
- They need to click the link if not automatically redirected.
- They should update their bookmark to the new URL.
@{
ViewBag.Title = "Page Moved";
}
@section MetaRefresh {
<meta http-equiv="refresh" content="5;@ViewBag.RedirectLocation" />
}
<h2 class="error">Page Moved</h2>
<p>
The page has a new location. Click on the following link if you are
not redirected automatically in 5 seconds. Please update your bookmark(s) to the new location.
</p>
<a href="@ViewBag.RedirectLocation">@ViewBag.RedirectLocation</a>.
<script>
//<!--
setTimeout(function () {
window.location = "@ViewBag.RedirectLocation";
}, 5000);
//-->
</script>
Usage
Add a section to the _Layout.cshtml
page(s) of the application so the meta-refresh can be placed in the <head>
section of the page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title - My ASP.NET MVC Application</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<!-- Add this so the view can update this section -->
@RenderSection("MetaRefresh", required: false)
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<!-- layout code omitted -->
</html>
Register the route with MVC. It is important that this happens before any other route is registered (including any call to MapMvcAttributeRoutes
or AreaRegistration.RegisterAllAreas
).
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// IMPORTANT: Register this before any other routes.
routes.Add(new RedirectToRomanizedUrlPermanent());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Now when you enter a URL such as /Ĥőmȩ/Ċőńţãçť
, you are automatically redirected to /Home/Contact
.