Last week Sitecore MVP Kamruz Jaman asked me in this tweet if I could answer this question on Stack Overflow.
The person asking the question wanted to know why alt text for images aren’t returned in responses from the Sitecore Item Web API, and was curious if it were possible to include these.
After digging around the Sitecore.ItemWebApi.dll and my local copy of /App_Config/Include/Sitecore.ItemWebApi.config — this config file defines a bunch of pipelines and their processors that can be augmented or overridden — I learned field values are returned via logic in the Sitecore.ItemWebApi.Pipelines.Read.GetResult class, which is exposed in /configuration/sitecore/pipelines/itemWebApiRead/processor[@type=”Sitecore.ItemWebApi.Pipelines.Read.GetResult, Sitecore.ItemWebApi”] in /App_Config/Include/Sitecore.ItemWebApi.config:
This is an example of a raw value for an image field — it does not include the alt text for the image:
I spun up a copy of the console application written by Kern Herskind Nightingale — Director of Technical Services at Sitecore UK — to show the value returned by the above pipeline processor for an image field:
The Sitecore.ItemWebApi.Pipelines.Read.GetResult class exposes a virtual method hook — the protected method GetFieldInfo() — that allows custom code to change a field’s value before it is returned.
I wrote the following class as an example for changing an image field’s value:
using System; using System.IO; using System.Web; using System.Web.UI; using Sitecore.Data.Fields; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.ItemWebApi; using Sitecore.ItemWebApi.Pipelines.Read; using Sitecore.Web.UI.WebControls; using HtmlAgilityPack; namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Read { public class EnsureImageFieldAltText : GetResult { protected override Dynamic GetFieldInfo(Field field) { Assert.ArgumentNotNull(field, "field"); Dynamic dynamic = base.GetFieldInfo(field); AddAltTextForImageField(dynamic, field); return dynamic; } private static void AddAltTextForImageField(Dynamic dynamic, Field field) { Assert.ArgumentNotNull(dynamic, "dynamic"); Assert.ArgumentNotNull(field, "field"); if(IsImageField(field)) { dynamic["Value"] = AddAltTextToImages(field.Value, GetAltText(field)); } } private static string AddAltTextToImages(string imagesXml, string altText) { if (string.IsNullOrWhiteSpace(imagesXml) || string.IsNullOrWhiteSpace(altText)) { return imagesXml; } HtmlDocument htmlDocument = new HtmlDocument(); htmlDocument.LoadHtml(imagesXml); HtmlNodeCollection images = htmlDocument.DocumentNode.SelectNodes("//image"); foreach (HtmlNode image in images) { if (image.Attributes["src"] != null) { image.SetAttributeValue("src", GetAbsoluteUrl(image.GetAttributeValue("src", string.Empty))); } image.SetAttributeValue("alt", altText); } return htmlDocument.DocumentNode.InnerHtml; } private static string GetAbsoluteUrl(string url) { Assert.ArgumentNotNullOrEmpty(url, "url"); Uri uri = HttpContext.Current.Request.Url; if (url.StartsWith(uri.Scheme)) { return url; } string port = string.Empty; if (uri.Port != 80) { port = string.Concat(":", uri.Port); } return string.Format("{0}://{1}{2}/~{3}", uri.Scheme, uri.Host, port, VirtualPathUtility.ToAbsolute(url)); } private static string GetAltText(Field field) { Assert.ArgumentNotNull(field, "field"); if (IsImageField(field)) { ImageField imageField = field; if (imageField != null) { return imageField.Alt; } } return string.Empty; } private static bool IsImageField(Field field) { Assert.ArgumentNotNull(field, "field"); return field.Type == "Image"; } } }
The class above — with the help of the Sitecore.Data.Fields.ImageField class — gets the alt text for the image, and adds a new alt XML attribute to the XML before it is returned.
The class also changes the relative url defined in the src attribute in to be an absolute url.
I then swapped out /configuration/sitecore/pipelines/itemWebApiRead/processor[@type=”Sitecore.ItemWebApi.Pipelines.Read.GetResult, Sitecore.ItemWebApi”] with the class above in /App_Config/Include/Sitecore.ItemWebApi.config:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <!-- Lots of stuff here --> <!-- Handles the item read operation. --> <itemWebApiRead> <processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Read.EnsureImageFieldAltText, Sitecore.Sandbox" /> </itemWebApiRead> <!--Lots of stuff here too --> </pipelines> <!-- Even more stuff here --> </sitecore> </configuration>
I then reran the console application to see what the XML now looks like, and as you can see the new alt attribute was added:
You might be thinking “Mike, image field XML values are great in Sitecore’s Content Editor, but client code consuming this data might have trouble with it. Is there anyway to have HTML be returned instead of XML?
You bet!
The following subclass of Sitecore.ItemWebApi.Pipelines.Read.GetResult returns HTML, not XML:
using System; using System.IO; using System.Web; using System.Web.UI; using Sitecore.Data.Fields; using Sitecore.Data.Items; using Sitecore.Diagnostics; using Sitecore.ItemWebApi; using Sitecore.ItemWebApi.Pipelines.Read; using Sitecore.Web.UI.WebControls; using HtmlAgilityPack; namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Read { public class TailorFieldValue : GetResult { protected override Dynamic GetFieldInfo(Field field) { Assert.ArgumentNotNull(field, "field"); Dynamic dynamic = base.GetFieldInfo(field); TailorValueForImageField(dynamic, field); return dynamic; } private static void TailorValueForImageField(Dynamic dynamic, Field field) { Assert.ArgumentNotNull(dynamic, "dynamic"); Assert.ArgumentNotNull(field, "field"); if (field.Type == "Image") { dynamic["Value"] = SetAbsoluteUrlsOnImages(GetImageHtml(field)); } } private static string SetAbsoluteUrlsOnImages(string html) { if (string.IsNullOrWhiteSpace(html)) { return html; } HtmlDocument htmlDocument = new HtmlDocument(); htmlDocument.LoadHtml(html); HtmlNodeCollection images = htmlDocument.DocumentNode.SelectNodes("//img"); foreach (HtmlNode image in images) { if (image.Attributes["src"] != null) { image.SetAttributeValue("src", GetAbsoluteUrl(image.GetAttributeValue("src", string.Empty))); } } return htmlDocument.DocumentNode.InnerHtml; } private static string GetAbsoluteUrl(string url) { Assert.ArgumentNotNullOrEmpty(url, "url"); Uri uri = HttpContext.Current.Request.Url; if (url.StartsWith(uri.Scheme)) { return url; } string port = string.Empty; if (uri.Port != 80) { port = string.Concat(":", uri.Port); } return string.Format("{0}://{1}{2}{3}", uri.Scheme, uri.Host, port, VirtualPathUtility.ToAbsolute(url)); } private static string GetImageHtml(Field field) { return GetImageHtml(field.Item, field.Name); } private static string GetImageHtml(Item item, string fieldName) { Assert.ArgumentNotNull(item, "item"); Assert.ArgumentNotNullOrEmpty(fieldName, "fieldName"); return RenderImageControlHtml(new Image { Item = item, Field = fieldName }); } private static string RenderImageControlHtml(Image image) { Assert.ArgumentNotNull(image, "image"); string html = string.Empty; using (TextWriter textWriter = new StringWriter()) { using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(textWriter)) { image.RenderControl(htmlTextWriter); } html = textWriter.ToString(); } return html; } } }
The class above uses an instance of the Image field control (Sitecore.Web.UI.WebControls.Image) to do all the work for us around building the HTML for the image, and we also make sure the url within it is absolute — just as we had done above.
I then wired this up to my local Sitecore instance in /App_Config/Include/Sitecore.ItemWebApi.config:
<?xml version="1.0" encoding="utf-8"?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <!-- Lots of stuff here --> <!-- Handles the item read operation. --> <itemWebApiRead> <processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Read.TailorFieldValue, Sitecore.Sandbox" /> </itemWebApiRead> <!--Lots of stuff here too --> </pipelines> <!-- Even more stuff here --> </sitecore> </configuration>
I then executed the console application, and was given back HTML for the image:
If you can think of other reasons for manipulating field values in subclasses of Sitecore.ItemWebApi.Pipelines.Read.GetResult, please drop a comment.
Addendum
Kieran Marron — a Lead Developer at Sitecore — wrote another Sitecore.ItemWebApi.Pipelines.Read.GetResult subclass example that returns an image’s alt text in the Sitecore Item Web API response via a new JSON property. Check it out!