Episerver

Episerver and Alternate Text for Images in the TinyMCE Rich Text Editor

Hands typing at keyboard

Last week I received a nasty bug report regarding Accessibility and Episerver.  Within Rich Text areas in Episerver, the file name is injected by default for alternate text.  This hurts your accessibility score and is a detrimental impact to visually impaired users.  Coincidentally, others in the community have written about and questioned how to solve this very issue:

I can’t take full credit for my solution because Tomas Hensrud Gulla’s solution provided me with the secret sauce: parsing XhtmlString fragments.  His blog post inspired my solution.  Also keep in mind that this behavior is recognized by Episerver as a bug, and may be fixed in future releases: https://world.episerver.com/support/Bug-list/bug/CMS-16633

Injecting Alt Text into Episerver ‘by the book’

If you’re new to Episerver, or have been working in the platform for some time, you’ll inevitably come across the need to display Alternate text on Image assets.  Alternate text is a mechanism for providing additional metadata about imagery to visually impaired users as well as providing some minimal SEO benefit for search engines.  If you search around or take a look at Alloy (one of the Episerver demo sites), you’ll see a pretty standard approach to providing alternate text in images:

  1. Extend ImageFile.cs with a new class that has a string property for AltText
  2. Create a Display Template for Image.cshtml that uses your custom class as a model, and injects alternate text accordingly.

If you follow along in Alloy, you can see this being done here:

The Problem

SAS IDC Episerver Commerce Guide
Delivering Customer-Centric Experiences for Digital Commerce

The IDC Technology Spotlight explores the rapidly changing commerce landscape and the top 5 benefits of a modern DXP.

Get the Guide

The issue arises when your content authors want to put Images inside of Rich Text fields.  If you’re using XhtmlString’s (rich text) on your Episerver solution, one of the default options is to allow either drag and drop of Media assets into the rich text editor, or to allow users to select an image through specific TinyMCE controls.  When this happens, however, Episerver uses the Name of the image (defaulted to the file name at upload time) as the alternate text.  This results in markup which doesn’t help accessible users at all: <img src="somefile.jpg" alt="somefile.jpg" />

The Solution

The solution is quite simple.  At render time, we can intercept XhtmlString (the backing property type for rich text), and modify what is rendered.  Sounds easy enough, right?  Here’s how to do it…

First, create a Display Template for XhtmlString.cshtml, and ensure that this is wired up properly for your solution:

@using EPiServer.Core
@model XhtmlString

@Html.Raw(Model.FixAlternateText(ViewContext))

Secondly, let’s create the extension method for FixAlternateText().  Within this extension method, we’ll need the ViewContext to be able to ask Episerver to render the results out for modification.  This is because Episerver does some magic to apply personalization, render blocks, and so forth within the contents of the XhtmlString fields, and needs the ViewContext to do it.  Once we have the Episerver HTML result, we can use HtmlAgilityPack to parse the resulting HTML and adjust it as necessary.

My extension method looks something like this:

public static string FixAlternateText(this XhtmlString html, ViewContext context)
{
    // html is null when the TinyMce is not initialize (creating new block etc.), so just return
    if (html == null) return string.Empty;

    // generate a dictionary of <Url, AltText> as strings.  Default epi behavior
    // is to inject the ContentName as the alternate text of the image, and ContentName
    // defaults to the File Name when uploaded through the UI.  This leads to alt text
    // being set to the file name, which is basically like having no alt text at all.
    var altTextDictionary = GenerateAltTextDictionary(html.Fragments);

    // Load up Epi's HtmlHelper and ask it to render results
    var hh = new HtmlHelper(context, new ViewPage());
    string epiRenderingResult;
    using (var writer = new StringWriter())
    {
        hh.ViewContext.Writer = writer;
        hh.RenderXhtmlString(html);
        writer.Flush();
        epiRenderingResult = writer.ToString();
    }

    // we don't care about edit mode, just return the HTML result out
    if (PageEditing.PageIsInEditMode)
        return epiRenderingResult;

    // once results are rendered, load up HtmlAgilityPack and have it parse results
    var doc = new HtmlDocument();
    doc.LoadHtml(epiRenderingResult);

    // find all image tags, if there are none just return out- otherwise we need to adjust them
    var imgs = doc.DocumentNode.SelectNodes("//img");
    if (imgs == null)
        return epiRenderingResult;

    // for each image, swap alt="name" for alt="alt text" for accessibility
    foreach (var img in imgs)
    {
        var src = img.Attributes["src"].Value;

        // if we know this alt text by key, replace it by the key's value
        // remember, the dictionary contains <Url, AltText>
        if (altTextDictionary.ContainsKey(src))
        {
            img.SetAttributeValue("alt", altTextDictionary[src]);
        }
    }

    var outerHtml = doc.DocumentNode.OuterHtml;
    return outerHtml; // return out the new resulting HTML
}

private static Dictionary<string, string> GenerateAltTextDictionary(StringFragmentCollection htmlFragments)
{
    var _contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>(); // yuck, but what can you do? :)

    Dictionary<string, string> retVal = new Dictionary<string, string>();

    foreach (var urlFragment in htmlFragments.Where(x => x is UrlFragment))
    {
        foreach (var guid in urlFragment.ReferencedPermanentLinkIds)
        {
            // assuming our custom ImageData model is called "ImageFile" with a property of "AltText"
            if (_contentLoader.TryGet(guid, out ImageFile image))
            {
                var key = image.ContentLink.GetPublicUrl();
                if (!retVal.ContainsKey(key))
                {
                    retVal.Add(key, image.AltText); // you may want to cleanse AltText for proper HTML markup
                }
            }
        }
    }

    return retVal;
}

After deploying this change up, any time an XhtmlString is rendered, we are now intercepting it and adjusting it’s alternate text accordingly.

Hope this helps you on your Episerver journey!

 

About the Author

I am a certified Sitecore developer, code monkey, and general nerd. I hopped into the .NET space 10 years ago to work on enterprise-class applications and never looked back. I love building things—everything from from Legos to software that solves real problems. Did I mention I love video games?

More from this Author

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to the Weekly Blog Digest:

Sign Up