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?