Home » Field Control

Category Archives: Field Control

Omit HTML Breaks From Rendered Multi-Line Text Fields in Sitecore

Earlier today while preparing a training session on how to add JavaScript from a Multi-Line Text field to a rendered Sitecore page, I encountered something I had seen in the past but forgot about: Sitecore FieldControls and the FieldRenderer Web Control will convert newlines into HTML breaks.

For example, suppose you have the following JavaScript in a Multi-Line Text field:

javascript-in-field

You could use a Text FieldControl to render it:

<%@ Control Language="c#" AutoEventWireup="true" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
<sc:Text ID="scJavaScript" Field="JavaScript" runat="server" />

Unfortunately, your JavaScript will not work since it will contain HTML breaks:

html-breaks-in-rendered-value

Why does this happen? In the RenderField() method in Sitecore.Web.UI.WebControls.FieldRenderer — this lives in Sitecore.Kernel.dll, and is called by all FieldControls — passes a “linebreaks” parameter to the <renderField> pipeline:

field-renderer-html-breaks

The Process() method in Sitecore.Pipelines.RenderField.GetMemoFieldValue — this serves as one of the “out of the box” processors of the <renderField> pipeline — converts all carriage returns, line feeds, and newlines into HTML breaks:

get-memo-field-value

What can we do to prevent this from happening? Well, you could spin up a new class with a Process() method to serve as a new <renderField> pipeline processor, and use that instead of Sitecore.Pipelines.RenderField.GetMemoFieldValue:

using System;

using Sitecore.Diagnostics;
using Sitecore.Pipelines.RenderField;

namespace Sitecore.Sandbox.Pipelines.RenderField
{
    public class GetRawMemoFieldValueWhenApplicable
    {
        public void Process(RenderFieldArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            if(!AreEqualIgnoreCase(args.FieldTypeKey, "memo") && !AreEqualIgnoreCase(args.FieldTypeKey, "multi-line text"))
            {
                return;
            }

            bool omitHtmlBreaks;
            if (bool.TryParse(args.Parameters["omitHtmlBreaks"], out omitHtmlBreaks))
            {
                return;
            }

            Assert.IsNotNull(DefaultGetMemoFieldValueProcessor, "DefaultGetMemoFieldValueProcessor must be set in your configuration!");
            DefaultGetMemoFieldValueProcessor.Process(args);
        }

        private static bool AreEqualIgnoreCase(string stringOne, string stringTwo)
        {
            return string.Equals(stringOne, stringTwo, StringComparison.CurrentCultureIgnoreCase);
        }

        private GetMemoFieldValue DefaultGetMemoFieldValueProcessor { get; set; }
    }
}

The Process() method in the class above looks for an “omitHtmlBreaks” parameter, and just exits out of the Process() method when it is set to true — it leaves the field value “as is”.

If the “omitHtmlBreaks”parameter is not found in the RenderFieldArgs instance, or it is set to false, the Process() method delegates to the Process() method of its DefaultGetMemoFieldValueProcessor property — this would be an instance of the “out of the box” Sitecore.Pipelines.RenderField.GetMemoFieldValue, and this is passed to the new <renderField> pipeline processor via the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <renderField>
        <processor patch:instead="processor[@type='Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel']"
                   type="Sitecore.Sandbox.Pipelines.RenderField.GetRawMemoFieldValueWhenApplicable, Sitecore.Sandbox">
          <DefaultGetMemoFieldValueProcessor type="Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel" />
        </processor>  
      </renderField>
    </pipelines>
  </sitecore>
</configuration>

Let’s test this.

I added the “omitHtmlBreaks” parameter to the control I had shown above:

<%@ Control Language="c#" AutoEventWireup="true" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
<sc:Text ID="scJavaScript" Field="JavaScript" Parameters="omitHtmlBreaks=true" runat="server" />

When I loaded my page, I was given a warm welcome:

alert-box-welcome

When I viewed my page’s source, I no longer see HTML breaks:

javascript-no-html-breaks

If you have any thoughts on this, or know of another way to do this, please share in a comment.

Tailor Sitecore Item Web API Field Values On Read

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:

sitecore-item-webapi-raw-field-value

This is an example of a raw value for an image field — it does not include the alt text for the image:

image-xml-content-editor

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:

image-xml-out-of-the-box

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:

alt-image-xml

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:

image-html-returned

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!

Yes — You, Too, Can Build a Custom Field Control!

Ever since I started building websites in Sitecore over five years ago, I had an itch to build a custom Sitecore Field Control. Sadly, I never put my nose to the grindstone in doing so until a few weeks ago. Armed with .NET Reflector and the Sitecore.Kernel.dll, I sat down and finally built one. This post highlights the fruits of my endeavor.

First, let me give you a quick description on what a Sitecore Field Control is. A Sitecore Field Control is an ASP.NET Web Control that presents data set in a Sitecore field according to logic defined by the control. Sitecore offers a few Field Controls out of the box — these include Image, Link, Text, Date — and all are defined within the namespace Sitecore.Web.UI.WebControls. How to use these Field Controls is illustrated during Sitecore Developer Training and certification.

In this example, I created a custom Field Control that takes a value set within a Single-Line Text, Multi-Line Text, Rich Text, or text field — a deprecated field type used before Sitecore v5.3.1. — and wraps the value around HTML specified by client code. This HTML tag is defined by the HtmlTag attribute of the HtmlTagFieldControl below.

First, I created a new Field Control:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;

using Sitecore.Collections;
using Sitecore.Web.UI.WebControls;

namespace Sitecore.Sandbox.FieldControls
{
    public class HtmlTagFieldControl : FieldControl
    {
        public string HtmlTag { get; set; }

        protected override void PopulateParameters(SafeDictionary<string> parameters)
        {
            base.PopulateParameters(parameters);

            if (!string.IsNullOrEmpty(HtmlTag))
            {
                parameters.Add("htmlTagName", HtmlTag);
            }
            
            foreach (string attributeName in base.Attributes.Keys)
            {
                string attributeValue = base.Attributes[attributeName];
                parameters.Add(attributeName, attributeValue);
            }
        }
    }
}

Next, I built a renderer — including a Data transfer object for passing arguments to the renderer — for the field control defined above:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Xml.Xsl;

namespace Sitecore.Sandbox.Renderers.Base
{
    public interface IHtmlTagRenderer
    {
        RenderFieldResult Render();
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

using Sitecore;
using Sitecore.Collections;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Xml.Xsl;

using Sitecore.Sandbox.Renderers.Base;
using Sitecore.Sandbox.Renderers.DTO;

namespace Sitecore.Sandbox.Renderers
{
    public class HtmlTagRenderer : FieldRendererBase, IHtmlTagRenderer
    {
        private const string OpenHtmlTagFormat = "<{0}>";
        private const string CloseHtmlTagFormat = "</{0}>";

        private HtmlTagRendererArgs Arguments { get; set; }

        private HtmlTagRenderer(HtmlTagRendererArgs arguments)
            : base()
        {
            SetArguments(arguments);
        }

        private void SetArguments(HtmlTagRendererArgs arguments)
        {
            Assert.ArgumentNotNull(arguments, "arguments");
            Arguments = arguments;
        }

        public virtual RenderFieldResult Render()
        {
            string fieldHtml = CreateFieldHtml();
            return new RenderFieldResult(fieldHtml);
        }

        private string CreateFieldHtml()
        {
            string openHtmlTag = GetOpenHtmlTag();
            string closeHtmlTag = GetCloseHtmlTag();

            return string.Concat(openHtmlTag, Arguments.FieldValue, closeHtmlTag);
        }

        private string GetOpenHtmlTag()
        {
            return GetTagHtml(OpenHtmlTagFormat, Arguments.HtmlTagName);
        }

        private string GetCloseHtmlTag()
        {
            return GetTagHtml(CloseHtmlTagFormat, Arguments.HtmlTagName);
        }

        private static string GetTagHtml(string tagFormat, string tagName)
        {
            if (!string.IsNullOrEmpty(tagName))
            {
                return string.Format(tagFormat, tagName);
            }

            return string.Empty;
        }

        public static IHtmlTagRenderer Create(HtmlTagRendererArgs arguments)
        {
            return new HtmlTagRenderer(arguments);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sitecore.Sandbox.Renderers.DTO
{
    public class HtmlTagRendererArgs
    {
        public string HtmlTagName { get; set; }
        public string FieldName { get; set; }
        public string FieldValue { get; set; }
    }
}

Last, but not least, I defined a Render Field pipeline to glue all the pieces together — including the pivotal piece of wrapping the field’s value around the wrapper HTML tag:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.RenderField;
using Sitecore.Xml.Xsl;

using Sitecore.Sandbox.Renderers;
using Sitecore.Sandbox.Renderers.Base;
using Sitecore.Sandbox.Renderers.DTO;

namespace Sitecore.Sandbox.Pipelines.RenderField
{
    public class GetHtmlTagFieldControlHtml
    {
        private static IEnumerable<string> supportedFieldTypes = new string[] { "text", "rich text", "single-line text", "multi-line text" };

        protected virtual IHtmlTagRenderer CreateRenderer(HtmlTagRendererArgs htmlTagRendererArgs)
        {
            return HtmlTagRenderer.Create(htmlTagRendererArgs);
        }

        public void Process(RenderFieldArgs renderFieldArgs)
        {
            if (CanFieldBeProcessed(renderFieldArgs))
            {
                SetRenderFieldArgsResults(renderFieldArgs);
            }
        }

        private bool CanFieldBeProcessed(RenderFieldArgs renderFieldArgs)
        {
            string fieldTypeKey = renderFieldArgs.FieldTypeKey.ToLower();
            return supportedFieldTypes.Contains(fieldTypeKey);
        }

        private void SetRenderFieldArgsResults(RenderFieldArgs renderFieldArgs)
        {
            RenderFieldResult renderFieldResult = GetRenderFieldResult(renderFieldArgs);
            SetRenderFieldArgsResultHtmlProperties(renderFieldResult, renderFieldArgs);
        }

        private RenderFieldResult GetRenderFieldResult(RenderFieldArgs renderFieldArgs)
        {
            HtmlTagRendererArgs htmlTagRendererArgs = CreateHtmlTagRendererArgs(renderFieldArgs);
            IHtmlTagRenderer htmlTagRenderer = CreateRenderer(htmlTagRendererArgs);
            return htmlTagRenderer.Render();
        }

        private HtmlTagRendererArgs CreateHtmlTagRendererArgs(RenderFieldArgs renderFieldArgs)
        {
            string htmlTagName = string.Empty;

            if (renderFieldArgs.Parameters.ContainsKey("htmlTagName"))
            {
                htmlTagName = renderFieldArgs.Parameters["htmlTagName"];
            }

            return new HtmlTagRendererArgs
            {
                HtmlTagName = htmlTagName,
                FieldName = renderFieldArgs.FieldName,
                FieldValue = renderFieldArgs.FieldValue
            };
        }

        private void SetRenderFieldArgsResultHtmlProperties(RenderFieldResult renderFieldResult, RenderFieldArgs renderFieldArgs)
        {
            renderFieldArgs.Result.FirstPart = renderFieldResult.FirstPart;
            renderFieldArgs.Result.LastPart = renderFieldResult.LastPart;
        }
    }
}

I added four fields to a data template — each being of a type supported by my custom Field Control:

I added some content:

I wrote some code for the test sublayout used in this example — notice how I’m having a little fun by using the marquee tag :):

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="My Little Sublayout.ascx.cs" Inherits="Sitecore650rev120706.layouts.sublayouts.MyLittleSublayout" %>
<div>
	<strong>Field Name:</strong>&nbsp;title<br />
	<strong>Field Type:</strong>&nbsp;text<br />
	<strong>Html Tag:</strong>&nbsp;h1<br />
	<strong>Field Control Html:</strong><br />
    <sj:HtmlTagFieldControl ID="h1Title" HtmlTag="h1" Field="Title" runat="server" />
    <hr />
</div>
<div>
	<strong>Field Name:</strong>&nbsp;Subtitle<br />
	<strong>Field Type:</strong>&nbsp;Single-Line Text<br />
	<strong>Html Tag:</strong>&nbsp;h2<br />
	<strong>Field Control Html:</strong><br />
    <sj:HtmlTagFieldControl ID="h2Subtitle" HtmlTag="h2" Field="Subtitle" runat="server" />
    <hr />
</div>
<div>
	<strong>Field Name:</strong>&nbsp;Blurb<br />
	<strong>Field Type:</strong>&nbsp;Multi-Line Text<br />
	<strong>Html Tag:</strong>&nbsp;blockquote<br />
	<strong>Field Control Html:</strong>
    <sj:HtmlTagFieldControl ID="HtmlTagFieldControl1" HtmlTag="blockquote" Field="Blurb" runat="server" />
    <hr />
</div>
<div>
	<strong>Field Name:</strong>&nbsp;Text<br />
	<strong>Field Type:</strong>&nbsp;Rich Text<br />
	<strong>Html Tag:</strong>&nbsp;marquee<br />
	<strong>Field Control Html:</strong>
    <sj:HtmlTagFieldControl ID="marqueeText" HtmlTag="marquee" Field="Text" runat="server" />
</div>

Rendered HTML:

Sitecore offering the ability to create custom Field Controls is just another example of its extensibility. I don’t know of many developers that have taken advantage of creating their own custom Field Controls. However, I hope this post kindles the want for doing so, or adds some armament to your arsenal on how to present Sitecore data.

Addendum

Recently, the following query was asked on twitter:

Custom field controls – would you write your own from scratch, or inherit e.g. sc:text and add what you need (and why)?

The Text FieldControl doesn’t offer any logic over it’s FieldControl base — it inherits directly from the FieldControl class without any overrides. Inheriting from it will not add any extra benefit.

Further, the only method that might be of interest in overriding in a FieldControl base class — a class other than Text — would be the PopulateParameters method — albeit an argument could be made for overriding the DoRender method, but I will not discuss this method here. The PopulateParameters method sets parameters that are used by the RendererField pipeline for passing arguments to the Renderer object — the latter object creates the HTML for the control.

Plus, as a general best practice, I would caution against inheriting from classes unless absolutely necessary — more specific FieldControl classes within Sitecore.Web.UI.WebControls are no exceptions. You don’t want to introduce a steep class hierarchy, or cause class explosion — a situation where you create a new class just to add new functionality over a base class (Vlissides, Helm, Johnson, Gamma, 1995; Shalloway and Trott, 2004). Utilizing composition is recommended over inheritance — a model that faciliates in code maintenance, refactoring efforts, and adding new functionality (Vlissides, Helm, Johnson, & Gamma, 1995; Shalloway & Trott, 2004).

The real magic though occurs within the Renderer and RenderField pipeline classes, not the FieldControl classes, per se.

References

Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1995). Design patterns: Elements of reusable object-oriented software. Reading: Addison-Wesley.

Shalloway, A., Trott, J. A. (2004) Design Patterns Explained. A new Perspective on Object-Oriented Design (2nd edition). Reading: Addison-Wesley.

Labels and Literals and Field Controls, Oh My!

I wish I could tell you I’ve discovered the panacea to extirpate common issues inherent in many Sitecore development projects and this article would enumerate a recipe for curtailing these, or how I’ve discovered a hidden gem within Sitecore.Kernel.dll that your future projects cannot live without.

Sadly, this article will do none of these things — it will not knock your socks off, and may even leave you saying to yourself “come on Mike, tell me something I don’t already know”.

Yet, I feel adamantly compelled to pen this article given an egregious practice I’ve seen employed by numerous Sitecore developers at multiple Sitecore development shops over the span of five years.

What is this foul practice?  It’s the act of building HTML in your C# code using content from Sitecore fields and displaying this HTML via ASP.NET Literal or Label Web Controls.

No doubt, you have seen some code like the following in a Sitecore solution, or have even written code like this yourself (please note these code examples haven’t been tested, aren’t robust, and are only given as pedagogical illustrations):

An ASP.NET User Control containing a Literal Web Control:

Code-behind for the User Control:

In the above code-behind, the author composed some link HTML using content within a Sitecore General Link field named ‘My Link’.

Compare that with the following:

An ASP.NET User Control containing Sitecore Link Field Control:

Code-behind for the User Control:

Notice how much less code was needed to wire-up the Link Field Control.  The Link Field Control will render link HTML if the current Item has a link set on the ‘My Link’ General Link field — including the target attribute, if a target is set on the General Link field.

You might be saying “Mike, this is great, but we don’t want this working in the Page Editor — we want this field to be completely locked down since it’s in a global Item”.  Well, you could use the following to disable your Field Control in the Page Editor, and have the ability to change it later without having to do a full code build (assuming you’re using the Web Application model in ASP.NET):

If you are uncertain about which Field Control to use, you could always poke around in Sitecore.Web.UI.WebControls using .NET Reflector, or could opt to use a FieldRenderer Web Control — all Sitecore Field Controls use an instance of a FieldRenderer to output their HTML:

Using a FieldRenderer would also aid in allowing you to change a field’s type later without breaking its presentation in the UI — as long as the field type can be rendered by the FieldRenderer.

That’s it for now.  Happy coding!

-Mike