Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more
Five New Optimizely Certifications are Here! Validate your expertise and advance your career with our latest certification exams. Click here to find out more
We had the same issue and had to write our own logic to handle this.
Try this code
using System;
using System.Globalization;
using System.Linq;
using EPiServer.Core;
using EPiServer.Web;
using EPiServer.Web.Routing;
namespace yadayada;
public class CanonicalUrlFactory
{
private readonly IUrlResolver urlResolver;
private readonly ISiteDefinitionResolver siteDefinitionResolver;
public CanonicalUrlFactory(
IUrlResolver urlResolver,
ISiteDefinitionResolver siteDefinitionResolver
)
{
this.urlResolver = urlResolver ?? throw new ArgumentNullException(nameof(urlResolver));
this.siteDefinitionResolver = siteDefinitionResolver ?? throw new ArgumentNullException(nameof(siteDefinitionResolver));
}
//Modified and copied from http://www.dodavinkeln.se/post/how-to-get-the-external-url-to-content
//This method also works for MediaData.
public virtual string Create(
ContentReference contentLink,
CultureInfo contentLanguage,
bool absoluteUrl = true
)
{
var result = this.urlResolver.GetUrl(
contentLink,
contentLanguage.Name,
new VirtualPathArguments
{
ContextMode = ContextMode.Default,
ForceCanonical = absoluteUrl
});
// HACK: Temprorary fix until GetUrl and ForceCanonical works as expected,
// i.e returning an absolute URL even if there is a HTTP context that matches the content's site definition and host.
if(!absoluteUrl)
{
return result;
}
if(!Uri.TryCreate(result, UriKind.RelativeOrAbsolute, out var relativeUri))
{
return result;
}
if(relativeUri.IsAbsoluteUri)
{
return result;
}
var siteDefinition = this.siteDefinitionResolver.GetByContent(
contentLink: contentLink,
fallbackToWildcard: true,
fallbackToEmpty: true);
var hosts = siteDefinition.GetHosts(
language: contentLanguage,
fallbackToUnmapped: true)
.ToList();
var host = hosts.FirstOrDefault(h => h.Type == HostDefinitionType.Primary) ??
hosts.FirstOrDefault(h => h.Type == HostDefinitionType.Undefined);
var baseUri = siteDefinition.SiteUrl;
// Avoid exception if host is missing, i.e. page without startpage
if(baseUri == null)
{
return string.Empty;
}
if(host != null && host.Name.Equals("*") == false)
{
// Try to create a new base URI from the host with the site's URI scheme. Name should be a valid
// authority, i.e. have a port number if it differs from the URI scheme's default port number.
Uri.TryCreate(siteDefinition.SiteUrl.Scheme + "://" + host.Name, UriKind.Absolute, out baseUri);
}
var absoluteUri = new Uri(baseUri, relativeUri);
return absoluteUri.AbsoluteUri;
}
}
It seems this is working out pretty well. So I wrote my own method for doing the same for alternate links, not sure it's the best solution but it seems to work! Here it is if anyone needs it :)
public Dictionary<string, string> GetAlternateLinks(ContentReference contentReference)
{
var isEditMode = _contextModeResolver.CurrentMode.EditOrPreview();
if (isEditMode)
{
return null;
}
var alternates = new Dictionary<string, string>();
var allLanguages = _languageBranchRepository.ListEnabled().Select(x => x.Culture);
var startPage = _contentLoader.Get<StartPage>(ContentReference.StartPage);
var masterLanguage = startPage.MasterLanguage;
foreach (var lang in allLanguages)
{
var url = GetCanonicalUrl(contentReference, lang);
if (!string.IsNullOrEmpty(url))
{
var urlBuilder = new UrlBuilder(url);
var routedContent = _urlResolver.Route(urlBuilder);
var isPublished = _publishedStateAssessor.IsPublished(routedContent, PublishedStateCondition.None);
if (routedContent != null && !ContentReference.IsNullOrEmpty(routedContent.ContentLink) && isPublished)
{
alternates[lang.Name] = url;
if (lang == masterLanguage)
{
alternates["x-default"] = url;
}
}
}
}
return alternates;
}
Canonical links should not contain fallback or replacement languages/URLs. The purpose of the canonical links is to tell search engines where the original content is located so it doesn't index duplicates. Es-es and es-mx are duplicates in this case, hence es-es should be the canonical URL of this page.
If you navigate to /es-es/sobre-xxx/sustainability/, is there an alternate link rendered to /es-mx/sobre-xxx/sustainability/ with hreflang set to 'es-mx'?
Hello!
We've recently implemented the standard Optimizely methods (see below) for generating canonicals and alternate URL:s and it works pretty well.
However, we're wondering why we're not getting links for the pages which have replacement language / fallback?
For instance we have this page about sustainability which exists in Spanish (see below)
When visiting this page in the browser, on the mexican site i works fine and shows the spanish fallback / replacement content
But the alternate / canonicals are not using the es-mx urls, but the es-es URL instead, and the alternate links do not even include the link(s) that have fallback.
Are we missing something?