Custom headless UmbracoApiController which cache system would you recommend ?
Hi all,
I've created a simple API for a nodeJS site using custom controllers extended from UmbracoApiController. So a custom headless ?
I would like to add some caching (no need for dotnut cache as content is the same for all user) and the caching should be invalidated when a Umbraco user has updated some content in Umbraco.
IPublishedContent is already cached version of content, so not sure why do you need to cache Umbraco content twice.
Have you decided to return all the content to your nodejs app?
Basically, you can use Umbraco cache and subscribe to "Published" event if you want to clean it up.
Here is the article about caching:
https://our.umbraco.com/documentation/reference/cache/
https://our.umbraco.com/documentation/reference/cache/applicationcache
How to subscribe on published notification:
https://our.umbraco.com/documentation/Fundamentals/Code/Subscribing-To-Notifications/
So,
you can cache the content with specific key and if some page is published - you can clear your cache by this key.
e.g. "UmbracoContent_{pageKey}", so you will be able to clear it using Umbraco Page Key.
Unfortunately "outputcache" is not provided by the "[ResponseCache]" attribute. ResponseCache doesnt actually store anything server side. It only adds http response headers, to instruct browsers and CDNs on how to cache the response. So you have no way to "invalidate it on any publish event".
That being said, setting these headers correctly is still very powerful. I wanted something more flexible than just putting a hard coded decorator on my controllers. So I added cache related properties to my document types and wrote a custom render controller. In the custom render controller, it reads the properties from the current model and changes the response headers accordingly. works well for me so far:
public override IActionResult Index()
{
var cacheControlHeaderValue = new Microsoft.Net.Http.Headers.CacheControlHeaderValue();
var ahouldAddHeacher = false;
if (CurrentPage.HasProperty("clientCacheMaxAge") && CurrentPage.HasValue("clientCacheMaxAge") && CurrentPage.Value<int>("clientCacheMaxAge") > 0)
{
ahouldAddHeacher = true;
cacheControlHeaderValue.MaxAge = TimeSpan.FromSeconds(CurrentPage.Value<int>("clientCacheMaxAge"));
}
if (CurrentPage.HasProperty("clientCachePublic") && CurrentPage.Value<bool>("clientCachePublic"))
{
ahouldAddHeacher = true;
cacheControlHeaderValue.Public = true;
}
if (CurrentPage.HasProperty("clientCachePrivate") && CurrentPage.Value<bool>("clientCachePrivate"))
{
ahouldAddHeacher = true;
cacheControlHeaderValue.Private = true;
}
if (CurrentPage.HasProperty("clientCacheNoStore") && CurrentPage.Value<bool>("clientCacheNoStore"))
{
ahouldAddHeacher = true;
cacheControlHeaderValue.NoStore = true;
}
if (ahouldAddHeacher)
{
Response.GetTypedHeaders().CacheControl = cacheControlHeaderValue;
}
return base.Index();
}
To achieve server side caching, I used the <Cache> tag in my razor pages, but for custom APIs, I used Redis.
I needed to invalidate the cache when certain document types were published. To achieve this, I created a published handler, that adds a date to the cache for each content type alias saved:
public class ContentPublishedHandler : INotificationAsyncHandler<ContentPublishedNotification>
{
private readonly IDistributedCache cache;
public ContentPublishedHandler(IDistributedCache cache)
{
this.cache = cache;
}
public Task HandleAsync(ContentPublishedNotification notification, CancellationToken cancellationToken)
{
return Task.WhenAll(notification.PublishedEntities.Select(node => cache.SetStringAsync(node.ContentType.Alias + "LastPublished", DateTime.Now.Ticks.ToString())));
}
}
Then when generating a cache key I can do something this:
var lastPublished = await distributedCache.GetStringAsync("BlogLastPublished");
var latestBlogsCacheKey = "LatestBlogs" + lastPublished;
var latestBlogs = await distributedCache.GetStringAsync(latestBlogsCacheKey );
This is all heavily modified to illustrate the point... but the idea is that before I go generating a response for a particular content type, I query the redis cache. And part of the key I use in that query is the last published date of the content type I am querying.
Takes 2 calls to redis, but it seems to work pretty well.
Custom headless UmbracoApiController which cache system would you recommend ?
Hi all,
I've created a simple API for a nodeJS site using custom controllers extended from
UmbracoApiController
. So a custom headless ?I would like to add some caching (no need for dotnut cache as content is the same for all user) and the caching should be invalidated when a Umbraco user has updated some content in Umbraco.
My methods are only using some content queries.
What cache system would you recommend for this ?
Thanks in advance
IPublishedContent is already cached version of content, so not sure why do you need to cache Umbraco content twice. Have you decided to return all the content to your nodejs app? Basically, you can use Umbraco cache and subscribe to "Published" event if you want to clean it up. Here is the article about caching: https://our.umbraco.com/documentation/reference/cache/ https://our.umbraco.com/documentation/reference/cache/applicationcache How to subscribe on published notification: https://our.umbraco.com/documentation/Fundamentals/Code/Subscribing-To-Notifications/
So, you can cache the content with specific key and if some page is published - you can clear your cache by this key. e.g. "UmbracoContent_{pageKey}", so you will be able to clear it using Umbraco Page Key.
Hi Andrey,
Thanks for your input. I'm just doing some mapping and some tweaks before returning the results in custom objects. So not all content is returned.
I wanted to use a simple outputcache, by using a
[ResponseCache]
attribute and invalidate it on any publish event. I'll look into that.Thanks!
Hi @MrFlo
Unfortunately "outputcache" is not provided by the "[ResponseCache]" attribute. ResponseCache doesnt actually store anything server side. It only adds http response headers, to instruct browsers and CDNs on how to cache the response. So you have no way to "invalidate it on any publish event".
That being said, setting these headers correctly is still very powerful. I wanted something more flexible than just putting a hard coded decorator on my controllers. So I added cache related properties to my document types and wrote a custom render controller. In the custom render controller, it reads the properties from the current model and changes the response headers accordingly. works well for me so far:
To achieve server side caching, I used the
<Cache>
tag in my razor pages, but for custom APIs, I used Redis.I needed to invalidate the cache when certain document types were published. To achieve this, I created a published handler, that adds a date to the cache for each content type alias saved:
Then when generating a cache key I can do something this:
This is all heavily modified to illustrate the point... but the idea is that before I go generating a response for a particular content type, I query the redis cache. And part of the key I use in that query is the last published date of the content type I am querying.
Takes 2 calls to redis, but it seems to work pretty well.
Hi @Keith, thanks for your clear and informative reply! Redis seems indeed a good solution 👍 Do you know what they use in Umbraco headless?
How Do you protect the API with OAUTH?
is working on a reply...