React and EPiServer – Generic pass-through property handler for JOS ContentSerializer

In a previous article I wrote about React and EPiServer – Custom rendering of EPiServer block in XhtmlString field without partial block controller, as we’re using the JOS Content Serializer to make JSON of our EPiServer content. This is in order to pass it to React. We were using the version 2.0.1, and it differs a bit on how things are done now (currently latest version is 4.1.0).

We noticed that most of our custom property types were no longer being included in the JSON. After looking through the source code and notes on Josef’s GitHub repository I created a small handler to sort his.

Supported EPiServer property types in JOS ContentSerializer 4.1.0

So the issue was that the way unknown property types were handled had changed. There was no longer a default case in a switch statement dealing with all of our custom ones, but rather a whole new way of dealing with the properties.

By default the JOS Content Serializer now supports the most common built in properties (you can find a list of them in the current readme.md file). However, if you’d like it to also handle your own properties, or the ones less common, you would have to specify a handler for each. This isn’t really a problem for most people, unless you have loads and loads of customizations. Like us.

// Initialization:

context.Services.AddSingleton<IPropertyHandler<ThePropertyType>, ThePropertyTypePropertyHandler>();

// And the handler:

public class ThePropertyTypePropertyHandler : IPropertyHandler<ThePropertyType>
{
  public object Handle(ThePropertyType value, PropertyInfo property, IContentData contentData)
  {
    return value;
  }
}

You would do something like above.

Generic solution to not register custom property handlers for JOS Content Serializer

I created a simple pass-through property handler to sort this out. However, to get it in place I needed to extend the existing PropertyHandlerService, which Josef pointed out is in the internal namespace (in other words, it may change without warning).

ExtendedPropertyHandlerService.cs

public class ExtendedPropertyHandlerService : PropertyHandlerService
{
  public new object GetPropertyHandler(PropertyInfo property)
  {
    var propertyHandler = base.GetPropertyHandler(property);
    if (propertyHandler != null)
    {
      return propertyHandler;
    }
    ServiceLocator.Current .TryGetExistingInstance(typeof(IPropertyHandler<object>), out propertyHandler);
    return propertyHandler;
  }
}

The JOS Content Serializer’s original PropertyHandlerService looks for a property handler for the type specified in the PropertyInfo object. So in our extension above, we should do nothing if it actually finds one. However, if it doesn’t we would like the generic catch all pass through handler to do the work for us.

PassThroughPropertyHandler.cs

public class PassThroughPropertyHandler : IPropertyHandler<object>
{
  // The pass through handler will return the value if serializable.
  private readonly ILogger _logger;

  public PassThroughPropertyHandler()
  {
    _logger = LogManager.GetLogger(this.GetType());
  }
  public object Handle(object value, PropertyInfo property, IContentData contentData)
  {
    if (value == null || !value.GetType().IsSerializable)
    {
      _logger.Debug("Value not serializable. IsNull='{0}' Type='{1}' PropertyName='{2}'", value == null, value?.GetType().FullName, property?.Name);
      return null;
    }
    return value;
  }
}

The pass through property handler will check if the value will be possible to serialize. There really is no need for us to handle it if it’s not. After that, it just returns the value.

In the initialization we will need to unregister the original property handler service in order to get JOS Content Serializer to use our extended one. We also need to register the pass through handler as a handler for the object type.

context.Services.RemoveAll<IPropertyHandlerService>();
context.Services.AddSingleton<IPropertyHandlerService, ExtendedPropertyHandlerService>();
context.Services.AddSingleton<IPropertyHandler<object>, PassThroughPropertyHandler>();