Caching API endpoint marked as cache-control "Private"



   We have an internal API, which is called after authenticate to get the user info, details etc. The problem we are facing right now is that on one page there are serveral blocks which call the same endpoint plus some other endpoints on API. If i call the API direct from browser then the browser is able to cache the response even though the reponse headers include cache-control:"Private". I know the browser is a client application and can cache the reponse. How can I cache the same reponse in EPiServer, as the samme calls to the API server is giving a lot of performance issue especially in peak times ?

We are using HttpClient object (namespace: System.Net.Http)to send HTTP requests to internal API. As these are blocks so asynchronous calls are not possible due to the child actions. 

What will be the best strategy to handle the caching as this is user data which can be changed so it shouldn't be cached for long time. 


Jul 26, 2020 12:13

You can use the object caching infrastructure to cache your user data in Episerver. That way the client (browser) can call the API again and again, without also hitting your back-office system all the time.

I actually wrote a long blog post about this scenario, where I described how to read through the cache, and how to force invalidation of cache objects. It's called Caching data that depends on Episerver content.

Hope it helps you out.

Jul 27, 2020 5:03

Hi Steffen 

Thanks for your quick reply. We have three different types of classes which are calling API endpoints, blockcontrollers,pagecontrollers,VisitorGroups Criteria for pages/blocks. Right now i have implemented methods in base controllers of both page & block. Where should the method be residing, as right now I am sending all the arguemnts from Controller as they are being inject there through DI. The other question is som API endpoints can be cached for 10 or more minutes, while others for 2-3 minutes (data can be changed quickly), should I created method overload with different CacheEvicationPolicy. 

I am gernerating cache key for each cache object by setting masterkey(unique) with object name. Should this key be dependent on Masterkey, so If i want to remove cached object i can do so by master key ?

If the user sign out should the caching be invalidated or its ok to let it be as 10 minutes will expire the caching.

Tak på forhånd

protected T APICaching<T>(Func<T> act, ISynchronizedObjectInstanceCache objectCaching, IAuthPersister authPersister) where T : class
            return objectCaching.ReadThrough(objectCaching.GenerateCacheKey<T>(authPersister.GetCacheKey()),
                x => new CacheEvictionPolicy(
                    CacheTimeoutType.Absolute), ReadStrategy.Wait);
Edited, Jul 27, 2020 11:10

Det var så lidt. 😉

Here are my answers to your questions:

  1. I would place the code in a service or repository class. So it can be reused by all the controllers that need it.
  2. I would refactor your code sample, to get the cache service and the persistor service by constructor injection, not from method parameters.
  3. Instead of creating an overload with an eviction policy, your method could just accept a timeout (in minutes) and your method can then create the policy internally.
  4. You can create a master key that includes the user id, and one that includes a "*". This way you can clear everything for a specific user and everything for all users. If a user changes something on his/her profile you can just clear his master key to force a full reload.
  5. I usually introduce a static class, CacheKeyHelper, to provide cache keys.
  6. You can clear user data on sign out. It's nice to do, but you don't have to.
Jul 27, 2020 11:47

HI Steffen 

              Thanks for your input.

Now I have created a service class which I inject in Controllers. The issue I am facing is that page controller can call API endpoints async while blocks call sync. How can the method be made async. I could not find any reference where ReadThrough() is async.

Here is the code: 

  public T ApiCachi<T>(Func<T> act, int minutes = 10) where T : class
            var masterkey= "masterkey";
            return _cacheWithHelper.ReadThrough(_cacheWithHelper.GenerateCacheKey<T>(userCacheKey),
                x =>
                    string[] dependentKeys =
                    return new CacheEvictionPolicy(
                        CacheTimeoutType.Absolute, dependentKeys);
                }, ReadStrategy.Wait);

//Page Controller Async Index method

 var data = await _Caching.ApCachi(()=> _member.Data());

//Block controller 
            var xx = _Caching.ApiCachi(()=> _yt.IndexAsync()).Result;

The other issue I am facing is that I am using ReadStrategy.Wait due to having many blocks on the page. Is this the best way, because when the method is called by the page, then there is only one call to this method ? 

Jul 28, 2020 8:03

It's true that the ReadThrough method is not seen with async reader functions.

If it can't be adjusted to async, you can use the TryGet and Insert methods instead, where your perform your async call in between.

ReadStrategy.Wait is great if the fetching method is resource-intensive. So Episerver will keep all simultaneous requests to the same cache key on hold, while one request fetches the data from the external system. Then it will serve the other requests from the cache.

Also, don't use .Result on a Task instance. It's much better and safer to copy and use this AsyncHelper class.

Jul 28, 2020 9:22

Thanks alot 

Can caching of user data (in thousands) have an impact on EPIServer Database ? And will caching obejcts get deleted from memory on timeout?

Jul 28, 2020 10:10
Stefan Holm Olsen - Jul 28, 2020 13:49
The cached data does not end up in the database. They are only stored in-memory.
When they expire or get deleted, they will be removed from memory by .Net.

If the server runs out of memory, it may start to evict cache entries before their timeout (in weighted priority).
ZZ - Jul 31, 2020 8:30
Hi Steffen
Thanks a lot for your input. Caching is implemented now, and it has significantly reduced load on the API server
Stefan Holm Olsen - Aug 01, 2020 14:48
I am happy to hear that it worked out so great for you, ZZ.

I am also trying to unit test the method by mocking it. 

 var dataClient = new Mock<IDataClient>();
            dataClient.Setup(mock => mock.DataAync()).ReturnsAsync(_userPreferences);

            var apiCache = new Mock<IApiCaching>();

            apiCache.Setup(mock => mock.ApiCachi(() => dataClient.Object.DataAsync(), It.IsAny<int>())).ReturnsAsync(_userPreferences);

// Block controller
            var data = _apiCaching.apiCache(() => _mdClient.DataAsync().Result);

Data is always null
Jul 28, 2020 13:09

I have created Mock class for testing the interface method.

 public class MockCaching:IApiCaching
        public virtual T ApiCachi<T>(Func<T> act, int minutes = xx) where T : class
            return act();
Jul 31, 2020 8:27
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.