Skip to content

Caching

Our caching provider is built on top of Microsoft.Extensions.Caching.Memory Caching is insanely easy with request handlers in Shiny Mediator.

  1. Install Shiny.Mediator.Caching.MicrosoftMemoryCache to your project.

  2. Create your request handler - you can mark Cache here with the attribute or you can use Configuration.

    [Cache]
    public class MyHandler : IRequestHandler<MyRequest, MyResult>
    {
    public async Task<MyResult> Handle(MyRequest request, RequestContext<MyRequest> context, CancellationToken ct)
    {
    return new MyResult();
    }
    }
  3. In your host startup, with the AddShinyMediator call, add the following:

    services.AddShinyMediator(x => x..AddMemoryCaching(y =>
    {
    y.ExpirationScanFrequency = TimeSpan.FromSeconds(5);
    }));

There are many additional properties you can use to interact with the cache setup

public class CacheAttribute : Attribute
{
public int AbsoluteExpirationSeconds { get; set; }
public int SlidingExpirationSeconds { get; set; }
}

Deeper Cache Control

You can add the ICacheControl to your contract to control cache directly at the point of call.

ForceRefresh allows an easy way to bypass cache and SetEntry allows you to set cache entry properties.

public class MyContract : IRequest<SomeResponse>, ICacheControl
{
public bool ForceRefresh { get; set; }
public Action<ICacheEntry>? SetEntry { get; set; } // optional: allows you to set cache entry properties
}

Configuration

We recommend configuring cache through Microsoft.Extensions.Configuration. Read Configuration for more information.

{
"Mediator": {
"Cache": {
"My.Namespace.MyHandler": {
"Priority": "High",
"AbsoluteExpirationSeconds": 60,
"SlidingExpirationSeconds": 30
}
}
}
}

Persistent Cache

Persistent cache is built into our MAUI and Uno Platform extensions. It uses the file system to store cache data. This allows cache to survive across application restarts. It works identical to the memory cache, but you can specify a different cache provider.

services.AddShinyMediator(x =>
{
x.AddMauiPersistentCache();
// OR
x.AddUnoPersistentCache();
});

It works identical to the memory cache, but stores to the filesystem instead. This is great for combining with things like Shiny Background Jobs to keep your cache fresh.

Custom Cache providers

Custom caching is now easy to accomplish inside Shiny.Mediator. Your cache provider can also make use of all the same benefits/configuration as already shown.

  1. Implement your custom provider Shiny.Mediator.Caching.ICacheProvider located in the Shiny.Mediator package.

    public interface ICacheService
    {
    Task<CacheEntry<T>?> GetOrCreate<T>(
    string key,
    Func<Task<T>> factory,
    CacheItemConfig? config = null
    );
    /// <summary>
    /// Manually insert or overwrite an item in cache
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <param name="config"></param>
    Task Set<T>(
    string key,
    T value,
    CacheItemConfig? config = null
    );
    /// <summary>
    /// Retrieves a cached value, null if not found
    /// </summary>
    /// <param name="key"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    Task<CacheEntry<T>?> Get<T>(string key);
    /// <summary>
    /// Removes a specific cache item
    /// </summary>
    /// <param name="key"></param>
    Task Remove(string key);
    /// <summary>
    /// Clears cache keys starting with prefix
    /// </summary>
    /// <param name="prefix"></param>
    Task RemoveByPrefix(string prefix);
    /// <summary>
    /// Clears all cache
    /// </summary>
    Task Clear();
    }
  2. Now register it with Shiny.Mediator

    services.AddShinyMediator(x => x.AddCaching<MyCustomCacheService>());

    You can only have 1 cache provider registered at a time for our middleware. If you need something more custom, you’ll need to create your own middleware to handle it.

Contexts & ‘Did my data come from cache’?

There is often times that you want to know IF your data came from cache, but also WHEN it was stored. This is easy to accomplish using the IMediatorContext interface. This interface is populated with tons of extra data during your mediator calls including offline data.

There is an extension method called IMediatorContext.Cache - if this value is returned as null, the data is “fresh”, otherwise, it contains info about when the data was stored.

IMediator mediator;
var response = mediator.Request(new MyRequest());
response.Result // the actual data
var cache = response.Context.Cache();
if (cache == null)
{
// ... data is direct from source
}
else
{
// data is from offline store
cache.Timestamp // when the data was stored
cache.RequestKey // the key used to store the data
}