32

How do you redirect a request in ASP.NET MVC to its correct canonical version if part of the URL is missing?

Using Stack Overflow as an example, the site adds the question title to the end of its routes, but uses the question ID in the route to actually find the question. If the title gets omitted you will be redirected to the correct URL.

For example, visiting the URL:

stackoverflow.com/questions/9033 

will redirect to

stackoverflow.com/questions/9033/hidden-features-of-c

How does this work?

random
  • 9,324
  • 10
  • 63
  • 77
wycleffsean
  • 1,357
  • 1
  • 11
  • 16
  • 4
    see this related question and answers http://stackoverflow.com/questions/2174820/how-to-add-page-title-in-url-in-asp-net-mvc-url-generation and http://stackoverflow.com/questions/677158/stack-overflow-question-routing – jao May 19 '11 at 07:51

5 Answers5

43

First create a route:

routes.MapRoute( 
    "ViewProduct", 
    "Products/{id}/{productName}", 
    new { controller = "Product", action = "Details", id = "", productName = "" } 
);

Then create the Action method like so:

public ActionResult Details(int? id, string productName) 
{ 
    Product product = ProductRepository.Fetch(id); 

    string realTitle = UrlEncoder.ToFriendlyUrl(product.Title); 
    string urlTitle = (productName ?? "").Trim().ToLower(); 

    if (realTitle != urlTitle)
    { 
        string url = "/Products/" + product.Id + "/" + realTitle; 
        return new PermanentRedirectResult(url);
    } 

    return View(product); 
}

You're basically comparing the entity title in the URL with the one stored in the database, if they don't match then perform a 301 permanent redirect. Make sure it's a 'permanent' redirect (301 status code) instead of a temp redirect (302). This way search engines will treat it as a permanent change of the URL and will update their indexes accordingly, this might happen if the title of your entity changes after a search engine has indexed it (e.g. someone changes the name of the product).

Another thing to be aware of, if your title allows any free text, you need to strip out any characters that are invalid for a URL, and make it more readable for humans and search engines alike, hence the UrlEncoder.ToFriendlyUrl method in the code above, the implementation is below:

public static class UrlEncoder 
{ 
    public static string ToFriendlyUrl (this UrlHelper helper, 
        string urlToEncode) 
    { 
        urlToEncode = (urlToEncode ?? "").Trim().ToLower(); 

        StringBuilder url = new StringBuilder(); 

        foreach (char ch in urlToEncode) 
        { 
            switch (ch) 
            { 
                case ' ': 
                    url.Append('-'); 
                    break; 
                case '&': 
                    url.Append("and"); 
                    break; 
                case '\'': 
                    break; 
                default: 
                    if ((ch >= '0' && ch <= '9') || 
                        (ch >= 'a' && ch <= 'z')) 
                    { 
                        url.Append(ch); 
                    } 
                    else 
                    { 
                        url.Append('-'); 
                    } 
                    break; 
            } 
        } 

        return url.ToString(); 
    } 
}

So when you write out the URLs into the View, be sure to encode the titles with this method e.g.

<a href="/Products/@Model.Id/@Url.ToFriendlyUrl(Model.Title)">@Model.Title</a>

I've written a blog post about this here http://www.dominicpettifer.co.uk/Blog/34/asp-net-mvc-and-clean-seo-friendly-urls

Sunday Ironfoot
  • 12,268
  • 13
  • 71
  • 89
  • 4
    the creator of Stackoverflow shows his implementation of 'ToFriendlyUrl' here: [http://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486](http://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486) – wycleffsean Nov 07 '11 at 19:12
  • 1
    The standards for UTF-8 characters in URL's has changed. According to [Wikipedia](http://en.wikipedia.org/wiki/Internationalized_domain_name), Mozilla 1.4, Netscape 7.1, Opera 7.11 were among the first applications to support IDNA. A browser plugin is available for Internet Explorer 6 to provide IDN support. Internet Explorer 7.0 and Windows Vista's URL APIs provide native support for IDN. Sounds like removing UTF-8 characters is a waste of time. Long live UTF-8!!! – Muhammad Rehan Saeed Apr 24 '15 at 14:18
  • 1
    I used fiddler and noticed that Stackoverflow.com does a `302` - Temporary Redirect going from `stackoverflow.com/questions/9033` to `stackoverflow.com/questions/9033/hidden-features-of-c` How do explain that, since you are saying the redirect should be `301` - Permanent Redirect? – Shiva May 09 '17 at 05:21
3

While I don't know any specifics of how StackOverflow manage it, here's an overview of how you could do it

  • Retrieve the question from the database using the ID
  • Convert the stored question title into a URL compatible slug
  • If the converted title slug does not match the slug passed in the URL then redirect using the converted title slug.

This ensures the URL is always the correct one and avoids possible embarrassing fake URLs

David Glenn
  • 23,572
  • 17
  • 70
  • 94
  • Updated answer to highlight that the slug is actually made from the question title and why it's important to check the slug passed in the URL is actually the correct slug. – David Glenn May 19 '11 at 09:19
0

@sunday i have tried it , but still i was facing issue . I need to give the url as

Products?id=4&productName=new-blog

Then i got solution .This helped me . We need make sure CUSTOM route is ABOVE the default route

Now it works fine.

Community
  • 1
  • 1
arun
  • 85
  • 2
  • 11
0

You should learn about ASP.net MVC routing mechanisms, as Stackoverflow uses this technology. It is quite a complex question to be answered here, but you will find lots of learning resources out there, like: http://weblogs.asp.net/scottgu/archive/2007/12/03/asp-net-mvc-framework-part-2-url-routing.aspx

Palantir
  • 22,691
  • 9
  • 74
  • 84
0

Here is an example of how they might do it, but I believe your asking how it can be done and this should work.

First stage is to set-up 2 routes in the Global.asax

routes.MapRoute("WithQuestion", "questions/{id}/{name}", new { controller = "Questions", action = "Question", id = "1" });
routes.MapRoute("WithoutQuestion", "questions/{id}", new { controller = "Questions", action = "WithoutQuestion", id="1"});

Now in our Questions controller,

    public ActionResult Question(int id)
    {
        //Load the question as we have the name appended.
        // We could actually do a little validation here as well
        return View();
    }

    public ActionResult WithoutQuestion(int id)
    {
        //Load the question object
        //Generate the full URL and redirect
        return Redirect(FullURL)
    }

This is a very basic example but shows how you could do it.

LiamB
  • 17,263
  • 19
  • 72
  • 112
  • 1
    Your example is not quite correct, if an incorrect slug is used StackOverflow will always redirect to the correct slug e.g. http://stackoverflow.com/questions/6055415/totally-made-up-slug so therefore a check needs to be made that the slug passed in the URL is in fact the correct slug stored in the database. – David Glenn May 19 '11 at 09:08
  • @David // We could actually do a little validation here as well ;) – LiamB May 19 '11 at 12:46