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 m
The accepted answer although it provides a good way to Produce Canonical url's.
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)
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.
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()
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.