When using the W3C Markup Validation Service, some annoying messages pop up because Optimizely CMS adds the trailing slash to self-closing tags, that are not necessary.

The relevant methods are:
@Html.CanonicalLink()
@Html.AlternateLinks()
Unfortunately the tag style can not be configured, but we can create our own implementations of the methods above.
public static class HtmlHelperExtensions
{
public static IHtmlContent CanonicalLinkV2(this IHtmlHelper html)
{
return html.CanonicalLinkV2(null, null, null);
}
public static IHtmlContent CanonicalLinkV2(this IHtmlHelper html, ContentReference contentLink, string language, string action)
{
var services = html.ViewContext.HttpContext.RequestServices;
var routeHelper = services.GetRequiredService<IContentRouteHelper>();
var urlResolver = services.GetRequiredService<IUrlResolver>();
var contentLoader = services.GetRequiredService<IContentLoader>();
var languageSettings = services.GetRequiredService<IContentLanguageSettingsHandler>();
contentLink ??= routeHelper.ContentLink;
language ??= routeHelper.LanguageID;
action ??= html.ViewContext.HttpContext.GetRouteValue(RoutingConstants.ActionKey) as string;
if (ContentReference.IsNullOrEmpty(contentLink))
{
return HtmlString.Empty;
}
// If the content is loaded due to a fallback or replacement settings,
// then the URLs should be to the original content
if (language is not null)
{
var loaderOptions = new LoaderOptions
{
LanguageLoaderOption.FallbackWithMaster(CultureInfo.GetCultureInfo(language))
};
if (contentLoader.TryGet(contentLink, loaderOptions, out IContent content) &&
content is ILocale localizable &&
languageSettings.MatchLanguageSettings(content, language).FallbackOrReplacement())
{
language = localizable.Language.Name;
}
}
var contentUrl = urlResolver.GetUrl(
contentLink,
language,
new VirtualPathArguments
{
ForceCanonical = true,
ForceAbsolute = true,
Action = action
});
if (string.IsNullOrEmpty(contentUrl))
{
return HtmlString.Empty;
}
return new TagBuilder("link")
{
TagRenderMode = TagRenderMode.StartTag,
Attributes =
{
{ "rel", "canonical" },
{ "href", contentUrl }
}
};
}
public static IHtmlContent AlternateLinksV2(this IHtmlHelper html)
{
return html.AlternateLinksV21(null, null);
}
public static IHtmlContent AlternateLinksV21(this IHtmlHelper html, ContentReference contentLink, string action = null)
{
var services = html.ViewContext.HttpContext.RequestServices;
var routeHelper = services.GetRequiredService<IContentRouteHelper>();
var urlResolver = services.GetRequiredService<IUrlResolver>();
contentLink ??= routeHelper.ContentLink;
action ??= html.ViewContext.HttpContext.GetRouteValue(RoutingConstants.ActionKey) as string;
List<string> alternateLanguages = GetAlternateLanguages(html, contentLink);
if (alternateLanguages.Count < 2)
{
return HtmlString.Empty;
}
VirtualPathArguments virtualPathArguments = new VirtualPathArguments
{
ForceCanonical = true,
ForceAbsolute = true,
Action = action
};
HtmlContentBuilder htmlContentBuilder = new HtmlContentBuilder();
foreach (string item in alternateLanguages)
{
string url = urlResolver.GetUrl(contentLink, item, virtualPathArguments);
if (!string.IsNullOrEmpty(url))
{
TagBuilder tagBuilder = new TagBuilder("link")
{
TagRenderMode = TagRenderMode.StartTag
};
tagBuilder.Attributes.Add("rel", "alternate");
tagBuilder.Attributes.Add("href", UrlEncoder.Encode(url));
tagBuilder.Attributes.Add("hreflang", item);
htmlContentBuilder.AppendHtml(tagBuilder);
}
}
return htmlContentBuilder;
}
private static List<string> GetAlternateLanguages(this IHtmlHelper html, ContentReference contentLink)
{
var services = html.ViewContext.HttpContext.RequestServices;
var contentRepository = services.GetRequiredService<IContentRepository>();
var languageBranchRepository = services.GetRequiredService<ILanguageBranchRepository>();
var publishedStateAssessor = services.GetRequiredService<IPublishedStateAssessor>();
HashSet<CultureInfo> enabledLanguages = new HashSet<CultureInfo>(from x in languageBranchRepository.ListEnabled()
select x.Culture);
return (from x in (from x in contentRepository.GetLanguageBranches<IContent>(contentLink)
where publishedStateAssessor.IsPublished(x) && HasValidLinkType(x)
select x).OfType<ILocalizable>()
where x.Language != null && enabledLanguages.Contains(x.Language)
select x.Language.Name).ToList().ToList();
}
private static bool HasValidLinkType(IContent content)
{
if (content is PageData pageData && pageData.LinkType != 0)
{
return pageData.LinkType == PageShortcutType.FetchData;
}
return true;
}
}
Simply decompile Optimizely's implementation and make sure to use TagRenderMode.StartTag instead of TagRenderMode.SelfClosing.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.