Last active
June 12, 2022 16:00
-
-
Save timw255/8aa9d805912f64640c6016d5ee927c61 to your computer and use it in GitHub Desktop.
Simultaneously support alternate (hreflang) and canonical links for Sitefinity pages and content
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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.