Sean Holmesby

.NET and Sitecore Developer

By

Sitecore Glass Mapper Data Handler for a Link List field

Here’s a way to map a custom Link List field in Sitecore to Glass.Mapper.Sc, for easy usage.
The following was used for Monoco’s Link List field but can also be used with the Field Suite’s General Links field by Velir, as it stores the same raw value.
(Note: for Monoco’s Link List field, it’s better to download the LinkList package from the Monoco site than the Sitecore Marketplace, as the Marketplace version is outdated at the time of writing this post).
LinkList

For more information on Data Handlers, check out the Data Handler tutorial on the Glass Mapper website here.

The solution came from a challenge I laid down to fellow Hedgehog colleague and Glass Mapper’s creator, Mike Edwards, when working on a project for a client.
The challenge was to be able to use the Link List field in a way where you can simply loop over all the links and just Render them using Glass. This would make for some seriously clean code on the front end.

The Link List field stores the links in XML format, simply as normal <link> nodes within a <links> tag.

<links>
  <link linktype="internal" url="/Home" querystring="" target="" id="{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}" />
  <link text="Google Link" linktype="external" url="http://www.google.com" anchor="" target="_blank" />
  <link linktype="media" url="/System/Simulator Backgrounds/Feature Phone" target="" id="{E6BA438D-B112-4BEF-AE3C-4F0C046868D6}" />
</links>

My first attempt at writing the code went poorly. Without a good knowledge of how Glass works, I wasn’t sure I could map the field to a collection of Link items. Even then, how could I render the link, given it’s not like rendering a normal link field on a Sitecore item. Mike’s solution was fantastic.

Here’s the Data Handler.

namespace Hedgehog.GlassMapper.Handlers
{
    /// Glass Data Handler for the LinkList custom field, from the Monoco Shared Source package.
    /// When fields are mapped to IList<Link> Glass will parse the field value using this class.
    public class LinkListDataHandler : Glass.Mapper.Sc.DataMappers.AbstractSitecoreFieldMapper
    {
        SitecoreFieldLinkMapper _linkMapper = new SitecoreFieldLinkMapper();
 
        public LinkListDataHandler()
            : base(typeof(IList<Link>))
        {
        }
 
        public override string SetFieldValue(object value, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            var links = value as IEnumerable<Link>;
            if (links == null)
            {
                return String.Empty;
            }
 
            StringBuilder sb = new StringBuilder();
            sb.Append("<links>");
 
            try
            {
                // in order to utilize the linkMapper, we need this link to be a field on an item, so we 
                // create a fake item with a fake field, and parse it that way.
                var fakeItem = Glass.Mapper.Sc.Utilities.CreateFakeItem(new HashDictionary<Guid, string>(),
                                                            ID.NewID,
                                                            context.Service.Database);
                using (new SecurityDisabler())
                {
                    var field = new Field(new ID(Guid.Empty), fakeItem);
 
                    fakeItem.Editing.BeginEdit();
 
                    foreach (var link in links)
                    {
                        _linkMapper.SetField(field, link, config, context);
                        sb.Append(field.Value);
                    }
 
                    fakeItem.Editing.CancelEdit();
                }
            }
            catch (Exception ex)
            {
                Log.Error("Parsing of LinkList field failed.", ex);
            }
 
            sb.Append("</links>");
            return sb.ToString();
        }
 
        public override object GetFieldValue(string fieldValue, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            var links = new List<Link>();
            if (String.IsNullOrWhiteSpace(fieldValue))
            {
                return links;
            }
 
            try
            {
                // parse the links from the xml.
                var xDocument = new XmlDocument();
                xDocument.LoadXml(fieldValue);
 
                var xmlLinks = xDocument.SelectNodes("/links/link");
 
                // in order to utilize the linkMapper, we need this link to be a field on an item, so we 
                // create a fake item with a fake field, and parse it that way.
                var fakeItem = Glass.Mapper.Sc.Utilities.CreateFakeItem(new HashDictionary<Guid, string>(),
                                                            ID.NewID,
                                                            context.Service.Database);
 
                using (new SecurityDisabler())
                {
                    Field field = new Field(new ID(Guid.Empty), fakeItem);
                    fakeItem.Editing.BeginEdit();
 
                    foreach (XmlNode xmlLink in xmlLinks)
                    {
                        field.Value = xmlLink.OuterXml;
 
                        var link = _linkMapper.GetField(field, config, context) as Link;
                        if (link != null)
                        {
                            links.Add(link);
                        }
                    }
 
                    fakeItem.Editing.CancelEdit();
                }
            }
            catch (Exception ex)
            {
                Log.Error("Parsing of LinkList field failed.", ex);
            }
 
            return links;
        }
 
        public override void Setup(Glass.Mapper.Pipelines.DataMapperResolver.DataMapperResolverArgs args)
        {
            _linkMapper.Setup(args);
            base.Setup(args);
        }
    }

Note the use the fake Sitecore item in code. It allows you to utilise how Sitecore renders link fields. This was the brilliance that Mike showed me, after I was stumped trying to figure it out without rewriting Sitecore’s link rendering logic.

Anyway, piecing it all together, we now register the Data Handler at Startup within App_Start/GlassMapperScCustom. Note: You must have Glass.Mapper.Sc.CastleWindsor installed for Data Handlers to be registered this way.

        public static void CastleConfig(IWindsorContainer container)
        {
            var config = new Config();
            container.Register(Component.For<AbstractDataMapper>().ImplementedBy<LinkListDataHandler>().LifestyleTransient());
            container.Install(new SitecoreInstaller(config));
        }

And the final piece of the puzzle, the link extension that easily allows you to Render the link in code.

namespace Hedgehog.Extensions
{
    public static class LinkExtensions
    {
        /// Render a Glass Link property on it's own. (Mainly used on individual Links 
        /// in the LinkList field type).
        public static string RenderLink(this Link link, NameValueCollection attributes = null, string contents = null)
        {
            StringBuilder sb = new StringBuilder();
            using (TextWriter writer = new StringWriter(sb))
            {
                var result = GlassHtml.BeginRenderLink(link, attributes, contents, writer);
                result.Dispose();
            }
            return sb.ToString();
        }
    }
}

The end result is the ability to output a set of Links from the Link List field in a very clean way.

<%@ Import Namespace="Hedgehog.Extensions"  %>
<%@ Import Namespace="Glass.Mapper.Sc.Fields"  %>
 
<% if (Model.Links != null && Model.Links.Any())
   { %>
        <ul>
            <% foreach (Link link in Model.Links) 
               { %>
                    <li>
                        <%= link.RenderLink() %>    
                    </li>
            <% } %>
        </ul>
<% } %>

You can now see how easy it is to render each of the Links in this field using Glass Mapper.
Note, each link is not rendered in a Page Editor friendly way, but to solve that, just place an Edit Frame around the whole block, and each link can be edited.

A huge thanks to Mike Edwards who wrote most of the code after I was banging my head against a wall for hours.

Enjoy.

6 Responses to Sitecore Glass Mapper Data Handler for a Link List field

  1. Tom says:

    Great blog post. I leveraged this design for the last few years.

    Have you tried upgrading Glass to v4. I have done so recently and this design has stopped working.

    Thanks

    Tom

  2. Tom says:

    The issue was some incorrect documentation for Glass 4 which is now corrected.

    Thanks

    Tom

  3. Ananth Babu says:

    Hi,

    I am using LinkList field in my project, recently I faced the issue, if I keep the two LinkList field in same content item. When I click the Insert Link button, the popup will open for all LinkList field available in the content item. Is any one faced this issue, kindly me on this.

    Thanks
    Ananth Babu.P

    • sholmesby says:

      Hi Ananth,
      Which module are you using?
      I haven’t seen the issue before, but I would say this sounds like an issue with the original module itself, and not the Glass Mapper rendering of it on the page that’s discussed here.
      Sean

  4. Karan Kumar says:

    We are upgrading from Sitecore 8.2 to Sitecore 10.2.
    This solution is not working in Sitecore 10.2.

Leave a Reply

Your email address will not be published. Required fields are marked *