Skip to content

Instantly share code, notes, and snippets.

@stefanolsen
Last active August 6, 2017 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stefanolsen/85393dbe740383e9312640d0c1212b62 to your computer and use it in GitHub Desktop.
Save stefanolsen/85393dbe740383e9312640d0c1212b62 to your computer and use it in GitHub Desktop.
Code listings for blog post about rendering EPiServer CMS properties with dynamic HTML attributes. Read about it here: https://stefanolsen.com/posts/adding-dynamic-html-to-property-templates-in-episerver/
@model EPiServer.Core.ContentReference
@Html.ContentLink(Model, null, Html.LinkAttributes(Model))
public static class HtmlHelperExtensions
{
public static RouteValueDictionary LinkAttributes(this HtmlHelper helper, ContentReference contentLink)
{
var resolver = ServiceLocator.Current.GetInstance<LinkAttributesResolver>();
var attributes = resolver.ToLinkAttributes(contentLink);
return attributes;
}
public static RouteValueDictionary LinkAttributes(this HtmlHelper helper, Url url, string action = null)
{
var resolver = ServiceLocator.Current.GetInstance<LinkAttributesResolver>();
var attributes = resolver.ToLinkAttributes(url, action);
return attributes;
}
public static MvcHtmlString InsertLinkAttributes(this HtmlHelper helper, ContentReference contentLink)
{
var resolver = ServiceLocator.Current.GetInstance<LinkAttributesResolver>();
var attributes = resolver.ToLinkAttributes(contentLink);
return MvcHtmlString.Create(string.Join(" ", attributes.Select(a => $"{a.Key}=\"{a.Value}\"")));
}
public static MvcHtmlString InsertLinkAttributes(this HtmlHelper helper, Url url, string action = null)
{
var resolver = ServiceLocator.Current.GetInstance<LinkAttributesResolver>();
var attributes = resolver.ToLinkAttributes(url, action);
return MvcHtmlString.Create(string.Join(" ", attributes.Select(a => $"{a.Key}=\"{a.Value}\"")));
}
public static MvcHtmlString InsertLinkAttributes(this HtmlHelper helper, MvcHtmlString xhtmlString)
{
var resolver = ServiceLocator.Current.GetInstance<LinkAttributesResolver>();
return MvcHtmlString.Create(resolver.InsertLinkAttributes(xhtmlString));
}
}
public class LinkAttributesResolver
{
private const string DataCategory = "data-category";
private const string DataLabel = "data-label";
private const string DataAction = "data-action";
private const string Download = "download";
private const string Target = "target";
private const string AttributesCategoryExternal = "External";
private const string AttributesCategoryDownload = "Downloads";
private const string AttributeDownload = "download";
private const string AttributesTargetBlank = "_blank";
private static readonly Regex ExternalLinkRegex =
new Regex(@"<a(?:.*?)href=""(?<linkUrl>(?:(https?|mailto):)(?:.*?))""(?:.*?)>", RegexOptions.Compiled);
private static readonly Regex MediaLinkRegex =
new Regex(@"<a(?:.*?)href=""(?<linkUrl>(?:(\/globalassets\/|\/siteassets\/|\/contentassets\/))(?:.*?))""(?:.*?)(?:target=""(?<target>.*)"")?>", RegexOptions.Compiled);
private readonly IContentLoader _contentLoader;
private readonly ISiteDefinitionResolver _siteDefinitionResolver;
private readonly UrlResolver _urlResolver;
public LinkAttributesResolver(
IContentLoader contentLoader,
ISiteDefinitionResolver siteDefinitionResolver,
UrlResolver urlResolver)
{
_contentLoader = contentLoader;
_siteDefinitionResolver = siteDefinitionResolver;
_urlResolver = urlResolver;
}
public RouteValueDictionary ToLinkAttributes(ContentReference contentLink)
{
var result = new RouteValueDictionary();
if (ContentReference.IsNullOrEmpty(contentLink))
{
return result;
}
IContent content;
if (!_contentLoader.TryGet(contentLink, out content))
{
return result;
}
IContent shortcutContent;
if (TryGetShortcut(content, out shortcutContent))
{
content = shortcutContent;
}
string action = string.Empty;
string category = string.Empty;
string label = string.Empty;
string download = string.Empty;
var mediaData = content as MediaData;
if (mediaData != null)
{
category = AttributesCategoryDownload;
label = mediaData.Name;
action = GetFileExtension(mediaData);
if (mediaData is ImageFile)
{
download = mediaData.Name;
}
}
else
{
var siteDefinition = _siteDefinitionResolver.GetByContent(content.ContentLink, false, false);
if (siteDefinition == SiteDefinition.Empty || siteDefinition != SiteDefinition.Current)
{
category = AttributesCategoryExternal;
label = GetFriendlyUrl(contentLink);
action = AttributesCategoryExternal;
}
}
result.Add(DataCategory, category);
result.Add(DataLabel, label);
result.Add(DataAction, action);
if (!string.IsNullOrEmpty(download))
{
result.Add(Download, download);
}
return result;
}
public RouteValueDictionary ToLinkAttributes(Url url, string action)
{
var result = new RouteValueDictionary();
if (url == null || url.IsEmpty())
{
return result;
}
if (!url.IsAbsoluteUri)
{
IContent content = GetContent(url);
return content == null ? result : ToLinkAttributes(content.ContentLink);
}
string category = string.Empty;
string label = string.Empty;
string download = string.Empty;
if (url.ToString().StartsWith("mailto:"))
{
category = "Contact";
label = url.ToString().Substring(7);
action = "mailto";
}
else
{
category = AttributesCategoryExternal;
label = GetFriendlyUrl(url);
action = !string.IsNullOrEmpty(action) ? action : AttributesCategoryExternal;
}
result.Add(DataCategory, category);
result.Add(DataLabel, label);
result.Add(DataAction, action);
if (!string.IsNullOrEmpty(download))
{
result.Add(Download, download);
}
return result;
}
/// <summary>
/// Inserts computed attributes for all links in an HTML string.
/// </summary>
public string InsertLinkAttributes(MvcHtmlString xhtmlString)
{
string htmlString = xhtmlString.ToHtmlString();
var externalLinkMatchEvaluator = new MatchEvaluator(ExternalLinkMatchEvaluator);
string replaced = ExternalLinkRegex.Replace(htmlString, externalLinkMatchEvaluator);
var mediaLinkMatchEvaluator = new MatchEvaluator(MediaLinkMatchEvaluator);
replaced = MediaLinkRegex.Replace(replaced, mediaLinkMatchEvaluator);
return replaced;
}
/// <summary>
/// Tries to lookup an external looking URL, and add the computed attributes.
/// </summary>
private string ExternalLinkMatchEvaluator(Match match)
{
string linkTag = match.Value;
string linkUrl = match.Groups["linkUrl"].Value;
bool hasTarget = match.Groups["target"].Success;
RouteValueDictionary attributes = ToLinkAttributes(new Url(linkUrl), AttributesCategoryExternal);
if (hasTarget)
{
attributes.Remove(Target);
}
var attributeString = MvcHtmlString.Create(string.Join(" ", attributes.Select(a => $"{a.Key}=\"{a.Value}\"")));
linkTag = linkTag.Replace(">", $" {attributeString}>");
return linkTag;
}
/// <summary>
/// Tries to lookup a media looking URL, and add the computed attributes.
/// </summary>
private string MediaLinkMatchEvaluator(Match match)
{
string linkTag = match.Value;
string linkUrl = match.Groups["linkUrl"].Value;
bool hasTarget = match.Groups["target"].Success;
RouteValueDictionary attributes =
ToLinkAttributes(new Url(linkUrl), AttributesCategoryDownload);
if (hasTarget)
{
attributes.Remove(Target);
}
var attributeString =
MvcHtmlString.Create(string.Join(" ", attributes.Select(a => $"{a.Key}=\"{a.Value}\"")));
linkTag = linkTag.Replace(">", $" {attributeString}>");
return linkTag;
}
private bool TryGetShortcut(IContent content, out IContent shortcut)
{
shortcut = null;
var page = content as PageData;
if (page == null || page.LinkType != PageShortcutType.Shortcut)
{
return false;
}
var propertyContentReference = page.Property["PageShortcutLink"] as PropertyContentReference;
if (propertyContentReference != null &&
!ContentReference.IsNullOrEmpty(propertyContentReference.ContentLink))
{
_contentLoader.TryGet(propertyContentReference.ContentLink, out shortcut);
return true;
}
return false;
}
private string GetFriendlyUrl(Url internalUrl)
{
if (internalUrl == null || internalUrl.IsEmpty())
{
return string.Empty;
}
var url = new UrlBuilder(internalUrl);
return _urlResolver.GetUrl(url, new VirtualPathArguments { ContextMode = ContextMode.Default }) ?? string.Empty;
}
private string GetFriendlyUrl(ContentReference contentReference)
{
if (ContentReference.IsNullOrEmpty(contentReference))
{
return string.Empty;
}
return _urlResolver.GetUrl(contentReference);
}
private IContent GetContent(Url internalUrl)
{
if (internalUrl == null)
{
return default(IContent);
}
var url = new UrlBuilder(internalUrl);
return _urlResolver.Route(url);
}
private string GetFileExtension(MediaData mediaData)
{
if (mediaData == null)
{
return string.Empty;
}
string url = GetFriendlyUrl(mediaData.ContentLink);
return url.Split('.').LastOrDefault();
}
}
@model EPiServer.Url
@Html.ContentLink(Model, Html.LinkAttributes(Model))
@model EPiServer.Core.XhtmlString
@{
MvcHtmlString xhtmlString = Html.XhtmlString(Model);
xhtmlString = Html.InsertLinkAttributes(xhtmlString);
}
@xhtmlString
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment