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; }