Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating an canonical automatically for mvc3 webapplication

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.

like image 337
Rob Avatar asked Mar 22 '11 19:03

Rob


3 Answers

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()
like image 116
BrunoLM Avatar answered Oct 23 '22 17:10

BrunoLM


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)
like image 35
Anestis Kivranoglou Avatar answered Oct 23 '22 16:10

Anestis Kivranoglou


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.

like image 3
Muhammad Rehan Saeed Avatar answered Oct 23 '22 18:10

Muhammad Rehan Saeed