4

I've got a TreeList field on one of my templates that allows me to select multiple templates. I'm having trouble mapping this to my model with Glass Mapper.

[SitecoreType(TemplateId = "{Template-Id-Here}")]
public class MyModel
{
    private const string FieldId = "{Field-Id-Here}";

    [SitecoreField(Id = FieldId]
    public virtual IEnumerable<Template> Templates { get; set; }

    [SitecoreField(Id = FieldId)]
    public virtual IEnumerable<TemplateItem> TemplateItems { get; set; }

    [SitecoreField(Id = FieldId)]
    public virtual IEnumerable<Item> Items { get; set; }
}

With the above model, the Items property always gets populated with the selected templates from my field, but the Templates and TemplateItems properties throw exceptions when accessed.

I've tried this with TreeListEx and Multilist fields with the same results. Can Glass Mapper not map Template or TemplateItem?

8

No, Glass Mapper can't map Template or TemplateItem out of the box. However, it's easy to create field mappers so that it can.

Field Mappers

To map Template, create the following field mapper:

public class SitecoreFieldTemplateMapper : AbstractSitecoreFieldMapper
{
    public SitecoreFieldTemplateMapper()
        : base(typeof(Template))
    {
    }

    public override object GetFieldValue(
        string fieldValue, 
        SitecoreDataMappingContext context)
    {
        if (!ID.TryParse(fieldValue, out var templateId)) return null;

        var templateEngine = context.Service.Database.Engines.TemplateEngine;
        var template = templateEngine.GetTemplate(templateId);
        return template;
    }

    public override string SetFieldValue(
        object value, 
        SitecoreFieldConfiguration config, 
        SitecoreDataMappingContext context)
    {
        if (value == null) null;

        var template = value as Template;
        if (template == null)
            throw new MapperException($"The value is not of type {typeof(Template)}");

        return template.ID.ToString();
    }
}

To map TemplateItem, create the following mapper:

public class SitecoreFieldTemplateItemMapper : AbstractSitecoreFieldMapper
{
    public SitecoreFieldTemplateItemMapper()
        : base(typeof(TemplateItem))
    {
    }

    public override object GetFieldValue(
        string fieldValue,
        SitecoreFieldConfiguration config,
        SitecoreDataMappingContext context)
    {
        if (!ID.TryParse(fieldValue, out var templateId)) return null;

        var item = context.Service.Database.GetItem(templateId);
        if (item?.TemplateID != Sitecore.TemplateIDs.Template) return null;

        return new TemplateItem(item);
    }

    public override string SetFieldValue(
        object value, 
        SitecoreFieldConfiguration config, 
        SitecoreDataMappingContext context)
    {
        if (value == null) return null;

        var templateItem = value as TemplateItem;
        if (templateItem == null)
            throw new MapperException($"The value is not of type {typeof(TemplateItem)}");

        return templateItem.ID.ToString();
    }
}

In both cases, you pass the type (e.g., typeof(Template)) to the base constructor to instruct Glass Mapper which property types it should use the field mapper for (e.g., Template, IEnumerable<Template>). The GetFieldValue method tells Glass Mapper how to read from Sitecore to your property; the SetFieldValue method tells Glass Mapper how to write from your property back to Sitecore.

You don't need to write separate mappers for IEnumerable<Template> or IEnumerable<TemplateItem> because the built-in SitecoreFieldIEnumerableMapper will handle that for you.

Add to Glass Mapper

Glass Mapper 5 offers a slightly cleaner way to register custom mappers than 4 through pipeline processors. Although the method for Glass Mapper 4 technically works with 5, I recommend the processor approach for flexibility and future-proofing.

Notice that in both versions the mappers are added to the DataMapperFactory with First and not Add. This is important--if the custom mappers are added after the SitecoreFieldIEnumerableMapper in the DataMapperFactory, the custom mappers will never get invoked.

Glass Mapper 5

For Glass Mapper 5, create the following pipeline processor to wire up the field mappers:

public class AddTemplateMappers : CreateResolverProcessor
{
    public override void Process(CreateResolverPipelineArgs args)
    {
        var dependencyResolver = args.DependencyResolver;
        dependencyResolver.DataMapperFactory.First(
            () => new SitecoreFieldTemplateMapper());
        dependencyResolver.DataMapperFactory.First(
            () => new SitecoreFieldTemplateItemMapper());
    }
}

and add it to the glassMapper.createResolver pipeline:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <glassMapper.createResolver>
        <processor type="YourAssembly.AddTemplateMappers, YourAssembly" />
      </glassMapper.createResolver>
    </pipelines>
  </sitecore>
</configuration>

Glass Mapper 4

For Glass Mapper 4, modify the GlassMapperScCustom in your solution as follows:

public static class GlassMapperScCustom
{
    public static IDependencyResolver CreateResolver()
    {
        var config = new Glass.Mapper.Sc.Config();

        var dependencyResolver = new DependencyResolver(config);
        dependencyResolver.DataMapperFactory.First(
            () => new SitecoreFieldTemplateMapper());
        dependencyResolver.DataMapperFactory.First(
            () => new SitecoreFieldTemplateItemMapper());
        return dependencyResolver;
    }

    // other methods omitted for brevity
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.