Shared field references in Glass Mapper
This is a repost from our german written Sitecore Vibes blog. It covers the topic to load referenced items without checking for a valid language version for a specific field. This was initially one of my questions on Stack Overflow where Mike Edwards, developer of Glass Mapper, answers my question and helped me also with continuous problems, thanks again!
Our projects often have some configuration values which we configure with
Sitecore items. For all these configurations (which are shared, so the same in
every language), we have a Specification
template with only one field for the
configuration value:
Our content item then gets a reference to one of these configuration items via a Droplink (i.e. to configure the container type). Our Glass Mapper class for this field would look like this
[SitecoreField(FieldName = "Container Type")]
public virtual Specification ContainerType { get; set; }
Now by default, this only works if the Specification
item has a language
version in the current Context.Language
. While this is ok for our content
item, we don’t want to check this for the container type reference. We need to
add several parts to make this possible.
Custom attribute
We need the attribute to say Glass Mapper that we want to do some special things with the property. The attribute should only identify the property’s mapping action and create an own configuration for the property (more on this later):
public class SitecoreSharedFieldAttribute : SitecoreFieldAttribute
{
public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
{
var config = new SitecoreSharedFieldConfiguration();
this.Configure(propertyInfo, config);
return config;
}
}
Attribute configuration
As we saw before, the attribute returns a custom configuration. We also need to
implement this class. It’s important that Glass Mapper can identify the
attribute with it’s own configuration, hence we need to override the Copy()
method:
public class SitecoreSharedFieldConfiguration : SitecoreFieldConfiguration
{
public override SitecoreFieldConfiguration Copy()
{
return new SitecoreSharedFieldConfiguration
{
CodeFirst = this.CodeFirst,
FieldId = this.FieldId,
FieldName = this.FieldName,
FieldSource = this.FieldSource,
FieldTitle = this.FieldTitle,
FieldType = this.FieldType,
IsShared = this.IsShared,
IsUnversioned = this.IsUnversioned,
PropertyInfo = this.PropertyInfo,
ReadOnly = this.ReadOnly,
SectionName = this.SectionName,
Setting = this.Setting
};
}
}
Custom data handler to implement the functionality
We already told Glass Mapper to do some magic with our custom attribute. But
what is the magic? The magic happens in the data handler, which maps the data
from the Sitecore item to the code. While initializing, Glass Mapper assigns a
data handler to each property. The CanHandle()
method checks if the
configuration of the property maps with the current data handler. The
GetFieldValue()
simply returns the value from the default field type mapper,
with disabled language version check:
public class SitecoreSharedFieldTypeMapper : SitecoreFieldTypeMapper
{
public override object GetFieldValue(string fieldValue, SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
{
using (new VersionCountDisabler())
{
return base.GetFieldValue(fieldValue, config, context);
}
}
public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
{
return configuration is SitecoreSharedFieldConfiguration && !configuration.PropertyInfo.PropertyType.IsGenericType;
}
}
More informations about custom data handlers can be found in this tutorial.
Configure the data handler
The custom data handler must be registered while initializing Glass Mapper. With
the default installation (and the usage of Castle
Windsor), this can be done in the
GlassMapperScCustom
class in the App_Start
folder:
public static void CastleConfig(IWindsorContainer container)
{
var config = new Config();
container.Register(Component.For<AbstractDataMapper>().ImplementedBy<SitecoreSharedFieldTypeMapper>().LifeStyle.Transient);
container.Install(new SitecoreInstaller(config));
}
At the end we just need to change the attribute from our property to the
SitecoreSharedFieldAttribute
:
[SitecoreSharedField(FieldName = "Container Type")]
public virtual Specification ContainerType { get; set; }
The same behavior also works for list types (i.e. a Multilist). You only need to configure our new attribute on your list:
[SitecoreSharedField(FieldName = "Available Languages")]
public virtual IEnumerable<Specification> AvailableLanguages { get; set; }