Skip to content

Instantly share code, notes, and snippets.

@timw255
Last active June 12, 2022 16:00
Show Gist options
  • Save timw255/8aa9d805912f64640c6016d5ee927c61 to your computer and use it in GitHub Desktop.
Save timw255/8aa9d805912f64640c6016d5ee927c61 to your computer and use it in GitHub Desktop.
Simultaneously support alternate (hreflang) and canonical links for Sitefinity pages and content
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Telerik.Sitefinity.Abstractions;
using Telerik.Sitefinity.Services;
using Telerik.Sitefinity.Web.Events;
using System.Web.UI.HtmlControls;
using Telerik.Sitefinity.Web;
using System.Globalization;
using Telerik.Sitefinity.Data;
using Telerik.Sitefinity.Web.UI.ContentUI;
using Telerik.Sitefinity.Model;
using Telerik.Sitefinity.DynamicModules.Web.UI.Frontend;
using Telerik.Sitefinity.Localization;
using System.Web.UI;
using Telerik.Sitefinity.Localization.UrlLocalizationStrategies;
namespace SitefinityWebApp
{
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
Bootstrapper.Initialized += Bootstrapper_Initialized;
}
protected void Bootstrapper_Initialized(object sender, ExecutedEventArgs e)
{
if (e.CommandName == "Bootstrapped")
{
// subscribe to OnPagePreRenderComplete
EventHub.Subscribe<IPagePreRenderCompleteEvent>(this.OnPagePreRenderCompleteEventHandler);
}
}
private void OnPagePreRenderCompleteEventHandler(IPagePreRenderCompleteEvent e)
{
if (!e.PageSiteNode.IsBackend)
{
// is there a canonical link on this page?
var canonicalLink = GetCanonicalLink(e.Page.Header.Controls);
bool includeCurrentLanguage = false;
// if there is a canonical link on this page, and it doesn't point to itself, we don't want to include the alternate link
if (canonicalLink == null || IsSameUrl(canonicalLink.Href, HttpContext.Current.Request.Url.ToString()))
{
includeCurrentLanguage = true;
}
var alternateLinks = GetAlternateLinks(e, includeCurrentLanguage);
foreach (var link in alternateLinks)
{
e.Page.Header.Controls.Add(link);
}
}
}
private HtmlLink GetCanonicalLink(ControlCollection controls)
{
HtmlLink canonicalLink = null;
foreach (var control in controls)
{
if (control.GetType() == typeof(HtmlLink))
{
var htmlLink = control as HtmlLink;
if (htmlLink != null && IsCanonicalLink(htmlLink))
{
canonicalLink = htmlLink;
break;
}
}
}
return canonicalLink;
}
private List<HtmlLink> GetAlternateLinks(IPagePreRenderCompleteEvent e, bool includeCurrentLanguage)
{
var alternateLinks = new List<HtmlLink>();
// are we looking at a detail page?
var contentViewSource = e.Page.GetBreadcrumbExtender() as ContentView;
var dynamicContentViewSource = e.Page.GetBreadcrumbExtender() as DynamicContentViewDetail;
if (contentViewSource != null || dynamicContentViewSource != null)
{
var item = (contentViewSource != null ? contentViewSource.DetailItem : dynamicContentViewSource.DataItem) as ILocalizable;
if (item != null)
{
var cultures = includeCurrentLanguage ? item.AvailableCultures : item.AvailableCultures.Where(c => c != CultureInfo.CurrentUICulture);
foreach (var culture in cultures.Where(c => !string.IsNullOrWhiteSpace(c.Name)))
{
var defaultLocation = GetItemDefaultLocation((IDataItem)item, culture);
if (!string.IsNullOrWhiteSpace(defaultLocation))
{
var alternateControl = new HtmlLink();
alternateControl.Attributes.Add("rel", "alternate");
alternateControl.Attributes.Add("href", defaultLocation);
alternateControl.Attributes.Add("hreflang", culture.Name);
alternateLinks.Add(alternateControl);
}
}
}
}
else
{
// we're looking at a static page
var ulService = ObjectFactory.Resolve<UrlLocalizationService>();
var cultures = includeCurrentLanguage ? e.PageSiteNode.AvailableLanguages : e.PageSiteNode.AvailableLanguages.Where(c => c != CultureInfo.CurrentUICulture);
foreach (var culture in cultures.Where(c => !string.IsNullOrWhiteSpace(c.Name)))
{
var defaultLocation = UrlPath.ResolveUrl(ulService.ResolveUrl(e.PageSiteNode.GetUrl(culture, false), culture), true, true);
if (!string.IsNullOrWhiteSpace(defaultLocation))
{
var alternateControl = new HtmlLink();
alternateControl.Attributes.Add("rel", "alternate");
alternateControl.Attributes.Add("href", defaultLocation);
alternateControl.Attributes.Add("hreflang", culture.Name);
alternateLinks.Add(alternateControl);
}
}
}
return alternateLinks;
}
private bool IsCanonicalLink(HtmlLink link)
{
bool flag = false;
if (link != null)
{
string item = link.Attributes["rel"];
if (!string.IsNullOrWhiteSpace(item) && item == "canonical")
{
flag = true;
}
}
return flag;
}
private bool IsSameUrl(string url1, string url2)
{
var u1 = new Uri(url1);
var u2 = new Uri(url2);
return u1.Equals(u2);
}
public string GetItemDefaultLocation(IDataItem item, CultureInfo culture = null)
{
var clService = SystemManager.GetContentLocationService();
var location = clService.GetItemDefaultLocation(item, culture);
if (location != null)
{
return location.ItemAbsoluteUrl;
}
return string.Empty;
}
}
}
@grumpycatsaysno
Copy link

grumpycatsaysno commented May 31, 2019

@timw255: This is great!

One small remark: I think you need to include <link rel="alternate" href="https://domain.com" hreflang="x-default" /> for languages that should fall back to the default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment