This blog post explains how you can expose the JSON representation of an Entry retrieved from a headless CMS in an Entry Model class. Having access to the cached JSON can be incredibly valuable and could justify retrieving and caching the entire Entry JSON once rather than retrieving the Fields Values needed for specific purposes on multiple round trips. For example, you can use the JSON to convert from one Entry Model to another.
- Entry Model Classes: https://deliverystack.net/2020/07/20/net-core-headless-cms-entry-models-and-entry-model-classes/
- Uses of Entry JSON: https://deliverystack.net/2020/07/22/convert-entry-json-to-entry-model/
There are a few ways that you could do this. Your Entry Model classes could implement an interface that your layer that accesses the CMS could use to store and expose the JSON. If your models cannot implement an interface, then using reflection, you could look for a named property of JObject type. Or as a .NET Core developer working with RESTful APIs, you may find JsonConverter to be one of your best friends.
The logic is trivial. Your Entry Model has a JObject property or methods that store and expose the JSON. Your JsonConverters simply set the property.
You can see how in the Contentstack default Entry Model class, but you should be able to do the same with any headless CMS. I generally open the vendor’s SDK projects in Visual Studio or use a disassembly tool such as JetBrains dotPeek (https://www.jetbrains.com/decompiler/) to do this kind of research, but the source code is online:
- https://github.com/contentstack/contentstack-dotnet/blob/master/Contentstack.Core/Models/Entry.cs: ParseObject() and ToJson()
- https://github.com/contentstack/contentstack-dotnet/blob/master/Contentstack.Core/ContentstackClient.cs (last line of constructor)
The ReadJson() method of the JsonConverter creates a JObject from its JsonReader argument, which it passes to the ParseObject() method on the Entry Model for storage after instantiating that Entry Model object.
Immediately we see the challenge: adding JsonConverters to the SerializerSettings. There are multiple possible approaches.
- Register JsonConverters manually, possibly only those needed for the type that you intend to load.
- Configure dependency injection.
- Have models expose a property or otherwise indicate any required JsonConverters (seems to imply inheritance or interface).
- JsonConverters all implement an interface; use reflection to register all implementations automatically.
You may be able to use generics to implement the converter in a single class. I expect that such functionality would depend on either reflection or Entry Model Class inheritance, in which case there is no need for a JsonConverter.
For these reasons, and because I think the logic (if not the data directly) for managing the JSON may belong in the layer that abstracts and potentially caches data from the CMS rather than individual entry models. Because I need similar functionality in other cases, and because the code linked previously provides enough example, I choose not to use JsonConverters.
I didn’t want to depend on interfaces or base classes so I used reflection. I abstract Contentstack but removed most of the repository code (including error control and TODOs!) from this recursive logic that also sets corresponding optional properties on Block Models (https://deliverystack.net/2020/07/22/how-contentstack-modular-blocks-fit-net-core), which otherwise would not know about the Entry Model that references them (might they need to?).
This uses the Contentstack default Entry Model Class, but that is mostly about getting to the JSON, so you should be able to do this with any headless CMS using its default Entry Model class or deserializing to JObject instead of directly to your Entry Model class. If the CMS does have a default Entry Model class, deserialize to JObject and then convert that to your Entry Models and store it in those Entry Models. I think you would still use an interface, inheritance, or reflection to store the JSON in the Entry Models.
In my case, the repository passes the Entry Model to this method after instantiation with isFirst of true. My implementation also associates the repository with the Entry Model so I can use the Entry Model to pull Entry Models for other Entries, such as those referenced by that Entry Model. So reflection became a general pattern for Entry Model initialization for me.
This implementation is in an extension method on the Contentstack default Entry Model class.