The problem

Imagine the following ContentType:

public class PostPage : PageData
{
    ....
    [Display(Order = 400)]
    [CultureSpecific]
    [AllowedTypes(typeof(AuthorBlock))]
    public virtual ContentReference Author { get; set; }
    ...
}

As you can see, it's only possible to select AuthorBlocks in the Author-property.
The default implementation of ContentReferencePropertyHandler looks like this:

public class ContentReferencePropertyHandler : IPropertyHandler<ContentReference>
{
    private readonly IUrlHelper _urlHelper;
    private readonly IContentSerializerSettings _contentSerializerSettings;

    public ContentReferencePropertyHandler(IUrlHelper urlHelper, IContentSerializerSettings contentSerializerSettings)
    {
        _urlHelper = urlHelper;
        _contentSerializerSettings = contentSerializerSettings ?? throw new ArgumentNullException(nameof(contentSerializerSettings));
    }

    public object Handle(ContentReference contentReference, PropertyInfo propertyInfo, IContentData contentData)
    {
        if (contentReference == null || contentReference == ContentReference.EmptyReference)
        {
            return null;
        }

        var url = new Uri(this._urlHelper.ContentUrl(contentReference, this._contentSerializerSettings.UrlSettings));

        if (this._contentSerializerSettings.UrlSettings.UseAbsoluteUrls && url.IsAbsoluteUri)
        {
            return url.AbsoluteUri;
        }

        return url.PathAndQuery;
    }
}

What will happen when we run .ToJson?
Well, since it's impossible to link directly to a block, the output will look like this:

{
    ...
    "author": "http://localhost:54321/"
    ...
}

That's not nice, it would be better if we could display the actual block instead.

The solution

Simply create a new class implementing the IPropertyHandler<ContentReference> interface and then register it in the DI container.

Improved ContentReferenceHandler

public class CustomContentReferencePropertyHandler : IPropertyHandler<ContentReference>
{
    private readonly IUrlHelper _urlHelper;
    private readonly IContentSerializerSettings _contentSerializerSettings;
    private readonly IContentLoader _contentLoader;
    private readonly IPropertyHandler<BlockData> _blockDataPropertyHandler;

    public CustomContentReferencePropertyHandler(
        IUrlHelper urlHelper,
        IContentSerializerSettings contentSerializerSettings,
        IContentLoader contentLoader,
        IPropertyHandler<BlockData> blockDataPropertyHandler)
    {
        _urlHelper = urlHelper ?? throw new ArgumentNullException(nameof(urlHelper));
        _contentSerializerSettings = contentSerializerSettings ?? throw new ArgumentNullException(nameof(contentSerializerSettings));
        _contentLoader = contentLoader ?? throw new ArgumentNullException(nameof(contentLoader));
        _blockDataPropertyHandler = blockDataPropertyHandler ?? throw new ArgumentNullException(nameof(blockDataPropertyHandler));
    }

    public object Handle(ContentReference contentReference, PropertyInfo property, IContentData contentData)
    {
        if (contentReference == null || contentReference == ContentReference.EmptyReference)
        {
            return null;
        }

        if (IsReferenceToBlock(property))
        {
            if (this._contentLoader.TryGet<BlockData>(contentReference, out var blockData))
            {
                return this._blockDataPropertyHandler.Handle(blockData, property, contentData);
            }
        }

        var url = new Uri(this._urlHelper.ContentUrl(contentReference, this._contentSerializerSettings.UrlSettings));

        if (this._contentSerializerSettings.UrlSettings.UseAbsoluteUrls && url.IsAbsoluteUri)
        {
            return url.AbsoluteUri;
        }

        return url.PathAndQuery;
    }

    private static bool IsReferenceToBlock(MemberInfo property)
    {
        var allowedTypesAttribute = property.GetCustomAttribute<AllowedTypesAttribute>();

        if (allowedTypesAttribute?.AllowedTypes == null || !allowedTypesAttribute.AllowedTypes.Any())
        {
            return false;
        }

        return allowedTypesAttribute.AllowedTypes.All(x => typeof(BlockData).IsAssignableFrom(x));
    }
}

Replace the default implementation

Replace for all properties of type ContentReference

[InitializableModule]
[ModuleDependency(typeof(JOS.ContentSerializer.Internal.ContentSerializerInitalizationModule))]
public class ContentSerializerInitializationModule : IConfigurableModule
{
    public void Initialize(InitializationEngine context) {}

    public void Uninitialize(InitializationEngine context) {}

    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.RemoveAll<IPropertyHandler<ContentReference>>();
        context.Services.AddSingleton<IPropertyHandler<ContentReference>, CustomContentReferencePropertyHandler>();
    }
}

Replace for specific property

[Display(Order = 400)]
[CultureSpecific]
[AllowedTypes(typeof(AuthorBlock))]
[ContentSerializerPropertyHandler(typeof(CustomContentReferencePropertyHandler))]
public virtual ContentReference Author { get; set; }