What follows is a draft of a simple controller action that could support any headless CMS using the repository pattern described in this blog. Most of my ASP.NET project is just template from Visual Studio.
Update: This is NOT about generic classes; I should not have used the word generic in the original title of this post.
Basically, the Index() action maps the requested URL to an Entry to the default Entry Model for that Entry and then passes that Entry as the model for that View.
I shouldn’t post this without showing the dependency injection and routing configuration (register other routes first and use this as a catchall route?), but that exposes other things I’m working on (it’s chilly in here), specifically getting an endpoint URL (_baseUrl) for the ASP.NET Core application server so that the solution can preload HTML output caches for the site. This is not part of the IRepository interface for now, hence the cast back to ContentstackRepository. This logic to get _baseUrl doesn’t seem to work on Azure and some local webserver configurations. One issue with wrapping all of the vendor APIs here is that I think I need at least one Startup for web projects in one project and another Startup for Console apps in another project that has no web-specific configuration and dependencies. I’ve had trouble sharing Startup classes, so I may need multiple Startup classes that call some or all of the same methods.
Unfortunately, the repository implementation is too big and defective and in need of architectural review to post in public now. You can see that it should not be much work to implement, with different challenges for different vendors. I would be glad to share the code that includes a working implementation for Contentstack individually.
Or just access the CMS vendor SDK directly, in which case, please see https://deliverystack.net/2020/07/24/working-with-headless-cms-vendor-net-core-sdks/.
If you cannot use a repository abstraction, then you can use the vendor API directly. The challenge is in getting the Entry associated with the requested URL path if I want friendly URLs that CMS users can control.
I am used to web CMS with hierarchical representation of Entries mapping to the information architecture of the solution and driving navigation and URLs automatically. I would like for the vendors to correct me so that I can update this content, but from what I have seen, headless solutions do not let CMS users manage Entries in hierarchies or expose features that let users define hierarchies of otherwise siloed Entries. Most seem to expect that a URL Field in the Content Type defines the URL of the Entry, with no requirement that the parent(s) implied by that URL exist and potentially no prevention of URL path conflicts between Entries. I think the lack of management of hierarchies of Entries and URL management in general is a major architectural failing in these products.
This leads is related to another challenge, which is how ASP.NET is supposed to determine the Entry associated with a URL, including its Content Type and hence Entry Model. From what I have seen, only Kentico Kontent provides the required single API to retrieve an Entry of any Content Type based on URL, but I think still requires that I specify the Entry Model type, which I actually don’t know until after retrieving the Entry (unless all Entry Models for Entries with URLs have a common base class and I can use that as the model for the view).
Contentful appears to require some form of Content Type inheritance for this, which seems straightforward (all Content Types inherit can inherit from a generic page Content Type that has the URL field?).
Contentstack (currently) requires querying each Content Type for Entries with that URL. This adds complication, especially as Contentstack allows multiple Entries to have a common URL and requires paging if a call returns more than 100 Entries. Contentstack intends to provide a solution to query across Content Types in its GraphQL solution, but that’s another set of APIs and JSON formats that, not seeing much value from .NET relative to other APIs, I would prefer to avoid. Personally, I think that at least getting an Entry by URL is a fundamental API for web CMS, which is the majority of CMS, so it should be a core API.
I would enforce that two Entries of any type in a single Stack with a common URL is an error case, which I will enforce at the repository level, and find a way to enforce this in the CMS UI as well. Or I would use an alternate technique to define item URLs, such as a custom field to select Entries to define an ordered tree structure. My solution is to abstract queries against the CMS to do the iterations and paging, infrastructure that I can use whether I get one or more Entries by ID, URL, or otherwise. And I use attributes to determine the default Entry Model classes when they are not specified explicitly by the code calling the repository.