Generating an canonical automatically for mvc3 webapplication
Asked Answered
A

4

14

I want to use canonical url's in my website. I read a few things about it on the internet, but i'm looking for a solution which will automatically generate the canonical for me runtime and add it in the html-code returned to the browser.

I've already found an example on the internet using an attribute, but this is not what i'm looking for. Using an attribute i'm still deciding which page should get an canonical or not myself, I want every page to have one generated automatically. I take it there should be (existing) solutions? I'm struggling finding an good example to work on, so any help is appreciated.

Alesiaalessandra answered 22/3, 2011 at 19:29 Comment(0)
B
23

For Razor:

I made one extension method for HtmlHelper:

public static MvcHtmlString CanonicalUrl(this HtmlHelper html, string path)
{
    if (String.IsNullOrWhiteSpace(path))
    {
        var rawUrl = html.ViewContext.RequestContext.HttpContext.Request.Url;
        path = String.Format("{0}://{1}{2}", rawUrl.Scheme, rawUrl.Host, rawUrl.AbsolutePath);
    }

    path = path.ToLower();

    if (path.Count(c => c == '/') > 3)
    {
        path = path.TrimEnd('/');
    }

    if (path.EndsWith("/index"))
    {
        path = path.Substring(0, path.Length - 6);
    }

    var canonical = new TagBuilder("link");
    canonical.MergeAttribute("rel", "canonical");
    canonical.MergeAttribute("href", path);
    return new MvcHtmlString(canonical.ToString(TagRenderMode.SelfClosing));
}

To get current URL

public static MvcHtmlString CanonicalUrl(this HtmlHelper html)
{
    var rawUrl = html.ViewContext.RequestContext.HttpContext.Request.Url;

    return CanonicalUrl(html, String.Format("{0}://{1}{2}", rawUrl.Scheme, rawUrl.Host, rawUrl.AbsolutePath));
}

Calling on Razor View:

@Html.CanonicalUrl()
Byler answered 26/3, 2011 at 1:42 Comment(6)
This is an great solution! Works perfectly! Replaced my own extension with your first solution.Alesiaalessandra
I realize I'm commenting on a question that is a year old, but all this does is copy the current URL in the address bar to a rel=canonical tag. That does nothing to help search engines determine the proper URL for that page since whatever the current URL is is what will be displayed as the canonical URL for that page. It's sort of self-defeating. What's wrong with the attribute approach? That seems to be the logical way to do it. Define once, for each Action in your controller, what the URL should be for it and that's what it is, no matter what the current URL is.Yuma
@Yuma I think you've missed the point. The purpose of canonical URLs it to tell indexing engines (ie: google) that several URIs are actually the same thing and that you have one preffered index URI. The above code will drop any query string data, deduplicate index URIs and enforce NO trailing slashes past the root domain in the URI (more deduplication). Before you go telling everyone what you think it does/doesn't do, go try it out for yourself! Plus, you could extend the above to preserve certain query strings or enforce a subdomain like www. Very useful indeed!Sadness
Actually I have tried it myself and this solution was implemented in a live site that I wrote. :) After some experimentation, it doesn't work at all as intended. Bringing up the same site with a different host header causes the new URL to be displayed as the canonical URL (in other words, the canonical URL is whatever is in the address bar, not a static value that doesn't change).Yuma
@Yuma you could change the host in the extension method?Borchers
Used this solution bug modified it so the scheme and host are read from webconfig, don't use it as is because it will only remove query string from canonical it will not respond correctly to the change of urlNathanson
A
4

The accepted answer although it provides a good way to Produce Canonical url's.

Its a shortcut to shoot yourself in the foot!!

It completely destroys the meaning of using the canonical tag!

Why the canonical tag exists?

When google crawls your website and finds duplicate content penalizes you.

The same page in your website can be accessed through various paths.

http://yourdomain.com/en
https://yourClientIdAt.YourHostingPacket.com/
http://195.287.xxx.xxx  //Your server Ip
https://yourdomain.com/index
http://www.yourdomain.com/
http://www.yourdomain.com/index  .....etc... etc..

Google will find the same content within various paths, thus duplicate content, thus penalty.

While the best practice is to use 301 redirects and have ONLY 1 link point to the same web page that's a pain......

That's why rel="canonical" has been created. Its a way to tell the crawler

"Hey this isn't a different page, this is the www.mydomain.index page you searched before.... the link in the canonical tag is the correct one!"

And then the same webpage won't be crawled multiple times as a different one.

By dynamically generating the canonical link from the url you are just saying....

<link href="http://yourdomain.com" rel="canonical"> Yes....this is a different page crawl this one also.... <link href="http://www.yourdomain.com/index" rel="canonical">and this is a different one.... and this one...

So in order to have a working canonical tag you have to generate the same exact link for every page that has different content. Decide your primary Domain(www.etc.com), Protocol (Https/Http) and Letter Casing(/Index,/index) and produce links with the only thing that identifies a single page. And this is your Controller/Action (And maybe language) Combinations. So you can extract those values from your route data.

public static TagBuilder GetCanonicalUrl(RouteData route,String host,string protocol)
{
    //These rely on the convention that all your links will be lowercase! 
    string actionName = route.Values["action"].ToString().ToLower();
    string controllerName = route.Values["controller"].ToString().ToLower();
    //If your app is multilanguage and your route contains a language parameter then lowercase it also to prevent EN/en/ etc....
    //string language = route.Values["language"].ToString().ToLower();
    string finalUrl = String.Format("{0}://{1}/{2}/{3}/{4}", protocol, host, language, controllerName, actionName);

    var canonical = new TagBuilder("link");
    canonical.MergeAttribute("href", finalUrl);
    canonical.MergeAttribute("rel", "canonical");
    return canonical;
}

In order your HtmlHelper to produce consistent links with your convention @Muhammad Rehan Saeed answered that.

Then in order to generate your Canonical tags for all pages you have to either make a HtmlHelper extension

public static MvcHtmlString CanonicalUrl(this HtmlHelper html,string host,string protocol)
{
    var canonical = GetCanonicalUrl(HttpContext.Current.Request.RequestContext.RouteData,host,protocol);
    return new MvcHtmlString(canonical.ToString(TagRenderMode.SelfClosing));
}


@Html.CanonicalUrl("www.mydomain.com", "https");

Or Implement an action filter attribute for your controllers . (I used this approach in order to handle more complex scenarios with multiple domains on the same app etc...)

    public class CanonicalUrl : ActionFilterAttribute
    {
        private string _protocol;
        private string _host;
        public CanonicalUrl(string host, string protocol)
        {
            this._host = host;
            this._protocol = protocol;
        }
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            var canonical = GetCanonicalUrl(filterContext.RouteData,_host,_protocol);
            filterContext.Controller.ViewBag.CanonicalUrl = canonical.ToString();
        }
    }
}

Usage within controller

[CanonicalUrl("www.yourdomain.com","https")]
public class MyController : Controller

Then i used it on my _Layout.chtml and done!

@Html.Raw(ViewBag.CanonicalUrl)
Amargo answered 15/12, 2016 at 16:41 Comment(0)
K
3

MVC 5 has the new option of generating lower case URL's for your routes. My route config is shown below:

public static class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        // Imprive SEO by stopping duplicate URL's due to case or trailing slashes.
        routes.AppendTrailingSlash = true;
        routes.LowercaseUrls = true;

        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
    }
}

With this code, you should no longer need the canonicalize the URL's as this is done for you. The only problem can occur if you are using HTTP and HTTPS URL's and want a canonical URL for this. In this case, it's pretty easy to use the above approach and replace HTTP with HTTPS or vice versa.

Klansman answered 30/9, 2014 at 14:28 Comment(1)
This makes your site generate canonical URLs but it doesn't stop other people linking to your site using a non-canonical URL. You still need the rel=canonical tag.Nganngc
A
0

Problem is solved. Fixed it writing my own html helper which generates the canonical url by taking the urls from the request. Did this using information from this topic.

Alesiaalessandra answered 22/3, 2011 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.