In my previous posts I showed how you can extend the Layout Service and implement your own custom Contents Resolvers. Until now I simply wanted to serialize all non-standard Sitecore fields with its values. Although there could be a case where you need to include or exclude fields from the Layout Service response.
By default Sitecore provides the following item serializers:
DefaultItemSerializer
- Excludes all the standard Sitecore fields (field names starting with
__
)
- Excludes all the standard Sitecore fields (field names starting with
AllFieldsItemSerializer
- As you could expect it includes all fields, including the Sitecore standard fields
JssItemSerializer
- Inherited from the
DefaultItemSerializer
, but depedning on theContext.PageMode
- If page mode is
IsExperienceEditorEditing
then it includes empty fields in the serialization to able to edit in Experience Editor
- Inherited from the
When you install the Sitecore Headless module it comes with the following configuration which is related to the item serializers:
... | |
<config name="default"> | |
<rendering type="Sitecore.LayoutService.Configuration.DefaultRenderingConfiguration, Sitecore.LayoutService"> | |
... | |
<itemSerializer type="Sitecore.LayoutService.Serialization.ItemSerializers.DefaultItemSerializer, Sitecore.LayoutService" resolve="true"> | |
<AlwaysIncludeEmptyFields>true</AlwaysIncludeEmptyFields> | |
</itemSerializer> | |
... | |
</rendering> | |
</config> | |
<config name="jss"> | |
<rendering type="Sitecore.LayoutService.Configuration.DefaultRenderingConfiguration, Sitecore.LayoutService"> | |
... | |
<itemSerializer type="Sitecore.JavaScriptServices.ViewEngine.LayoutService.JssItemSerializer, Sitecore.JavaScriptServices.ViewEngine" resolve="true"> | |
<AlwaysIncludeEmptyFields>true</AlwaysIncludeEmptyFields> | |
</itemSerializer> | |
... | |
</rendering> | |
</config> | |
... |
In the config above, you can see that the DefaultItemSerializer
and JssItemSerializer
is used for the Layout Service response. If you would like to overwrite the serialization behavior for all content resolvers you should do it here.
But what if…
I want to use a different serialization only for specific content resolvers?
Then you should do a trick, let me explain it how. The ItemSerializer
object is part of the IRenderingConfiguration
which is passed as a parameter for all contents resolvers (IRenderingContentsResolver
). The only problem is that the IRenderingConfiguration.ItemSerializer
is a read only object (not sure why, but this is how it is). So to able inject your custom ItemSerializer
you need pick up the original IRenderingConfiguration
and create a new object from it, copy all properties except the one which you want to overwrite, in our case the ItemSerializer
. Let me show the code to make it clear.
#1 Custom item serializer, in this example it excludes specific fields of the item for serialization. If you don’t need a custom serializer because you can use one of the built in serializers you can skip this step:
public interface ICustomItemSerializer : IItemSerializer | |
{ | |
} | |
public class CustomItemSerializer : DefaultItemSerializer, ICustomItemSerializer | |
{ | |
/// <summary> | |
/// TemplateId - FieldIds to exclude | |
/// </summary> | |
private static readonly IDictionary<ID, ID[]> _fieldsToExclude = new Dictionary<ID, ID[]>() | |
{ | |
{ | |
new ID(Constants.Template1.TemplateId), | |
new ID[] | |
{ | |
Constants.Template1.Fields.Field1, | |
Constants.Template1.Fields.Field2, | |
} | |
} | |
}; | |
public CustomItemSerializer(IGetFieldSerializerPipeline getFieldSerializerPipeline) : base(getFieldSerializerPipeline) | |
{ | |
} | |
protected override IEnumerable<Field> GetItemFields(Item item) | |
{ | |
if (!_fieldsToExclude.TryGetValue(item.TemplateID, out var fields)) | |
{ | |
return base.GetItemFields(item); | |
} | |
return base.GetItemFields(item).Where(f => !fields.Contains(f.ID)); | |
} | |
} |
#2 Rendering configuration factory which can be reused if you have multiple custom item serializers:
public interface IRenderingConfigFactory | |
{ | |
IRenderingConfiguration Create<T>(IRenderingConfiguration renderingConfig, T itemSerializer) where T : class, IItemSerializer; | |
} | |
public class RenderingConfigFactory : IRenderingConfigFactory | |
{ | |
public IRenderingConfiguration Create<T>(IRenderingConfiguration renderingConfig, T itemSerializer) where T : class, IItemSerializer | |
{ | |
return new DefaultRenderingConfiguration | |
{ | |
IncludeServerUrlInContextItemMediaUrls = renderingConfig.IncludeServerUrlInContextItemMediaUrls, | |
ItemSerializer = itemSerializer, // Custom item serializer | |
Name = renderingConfig.Name, | |
PlaceholdersResolver = renderingConfig.PlaceholdersResolver, | |
RenderingContentsResolver = renderingConfig.RenderingContentsResolver, | |
}; | |
} | |
} |
#3 Custom contents resolver which is using the RenderingConfigFactory
to create a new rendering configuration based on the original config:
public class CustomContentsResolver : RenderingContentsResolver | |
{ | |
private readonly ICustomItemSerializer _customItemSerializer; | |
private readonly IRenderingConfigFactory _renderingConfigFactory; | |
public CustomContentsResolver( | |
ICustomItemSerializer customItemSerializer, | |
IRenderingConfigFactory renderingConfigFactory) | |
{ | |
_customItemSerializer = customItemSerializer; | |
_renderingConfigFactory = renderingConfigFactory; | |
} | |
public override object ResolveContents(Rendering rendering, IRenderingConfiguration renderingConfig) | |
{ | |
var customRenderingConfig = _renderingConfigFactory.Create(renderingConfig, _customItemSerializer); | |
return base.ResolveContents(rendering, customRenderingConfig); | |
} | |
} |
#4 To make it work, the custom dependencies needs to be registered in the Sitecore DI container, but to keep the post short and focused on item serializer implementation I skip that.
Conclusion
This is an implementation of the idea of having unique item serializers for a contents resolver but it can be implemented in a more generic way, e.g. using the contents resolver Sitecore parameters to define which fields to exclude or include, therefore we don’t need to implement a contents resolver for each item serializer.