Volo.Abp.Caching is the main package of the caching system. You can install it as a project using the add-package command of theABP CLI:
abp add-package Volo.Abp.Caching
You need to run this command on a command line terminal in a folder containing acsproj file (seeother options to install).
Usage
IDistributedCache Interface
ASP.NET Core defines theIDistributedCache interface to get/set the cache values. But it has some difficulties:
It works withbyte arrays rather than .NET objects. So, you need toserialize/deserialize the objects you need to cache.
It provides asingle key pool for all cache items, so;
You need to care about the keys to distinguishdifferent types of objects.
You need to care about the cache items ofdifferent tenants in amulti-tenant system.
IDistributedCache is defined in theMicrosoft.Extensions.Caching.Abstractions package. That means it is not only usable for ASP.NET Core applications but also available toany type of applications.
ABP defines the genericIDistributedCache<TCacheItem> interface in theVolo.Abp.Caching package.TCacheItem is the type of the object stored in the cache.
IDistributedCache<TCacheItem> solves the difficulties explained above;
It internallyserializes/deserializes the cached objects. It usesJSON serialization by default but can be overridden by replacing theIDistributedCacheSerializer service in thedependency injection system.
It automatically adds acache name prefix to the cache keys based on the object type stored in the cache. The default cache name is the full name of the cache item class (CacheItem postfix is removed if your cache item class ends with it). You can use theCacheName attribute on the cache item class to set the cache name.
It automatically adds thecurrent tenant id to the cache key to distinguish cache items for different tenants (if your application ismulti-tenant). DefineIgnoreMultiTenancy attribute on the cache item class to disable this if you want to share the cached objects among all tenants in a multi-tenant application.
Allows defining aglobal cache key prefix per application so different applications can use their isolated key pools in a shared distributed cache server.
Itcan tolerate errors wherever possible and bypasses the cache. This is useful when you have temporary problems on the cache server.
It has methods likeGetManyAsync andSetManyAsync which significantly improve the performance ofbatch operations.
Example: Store Book names and prices in the cache
namespace MyProject{ public class BookCacheItem { public string Name { get; set; } public float Price { get; set; } }}
You can inject and use theIDistributedCache<BookCacheItem> service to get/setBookCacheItem objects:
using System;using System.Threading.Tasks;using Microsoft.Extensions.Caching.Distributed;using Volo.Abp.Caching;using Volo.Abp.DependencyInjection;namespace MyProject{ public class BookService : ITransientDependency { private readonly IDistributedCache<BookCacheItem> _cache; public BookService(IDistributedCache<BookCacheItem> cache) { _cache = cache; } public async Task<BookCacheItem> GetAsync(Guid bookId) { return await _cache.GetOrAddAsync( bookId.ToString(), //Cache key async () => await GetBookFromDatabaseAsync(bookId), () => new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.Now.AddHours(1) } ); } private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId) { //TODO: get from database } }}
This sample service uses theGetOrAddAsync() method to get a book item from the cache.GetOrAddAsync is an additional method that was added by the ABP to the standard ASP.NET Core distributed cache methods.
If the book was not found in the cache, it calls the factory method (GetBookFromDatabaseAsync in this case) to retrieve the book item from the original source.
GetOrAddAsync optionally gets aDistributedCacheEntryOptions which can be used to set the lifetime of the cached item.
IDistributedCache<BookCacheItem> supports the same methods of the ASP.NET Core's standardIDistributedCache interface, so you can referit's documentation.
IDistributedCache<TCacheItem> interface assumes that the type of yourcache key isstring (so, you need to manually convert your key to string if you need to use a different kind of cache key). While this is not a big deal,IDistributedCache<TCacheItem, TCacheKey> can be used when your cache key type is notstring.
Example: Store Book names and prices in the cache
using Volo.Abp.Caching;namespace MyProject{ [CacheName("Books")] public class BookCacheItem { public string Name { get; set; } public float Price { get; set; } }}
This example uses theCacheName attribute for theBookCacheItem class to set the cache name.
You can inject and use theIDistributedCache<BookCacheItem, Guid> service to get/setBookCacheItem objects:
using System;using System.Threading.Tasks;using Microsoft.Extensions.Caching.Distributed;using Volo.Abp.Caching;using Volo.Abp.DependencyInjection;namespace MyProject{ public class BookService : ITransientDependency { private readonly IDistributedCache<BookCacheItem, Guid> _cache; public BookService(IDistributedCache<BookCacheItem, Guid> cache) { _cache = cache; } public async Task<BookCacheItem> GetAsync(Guid bookId) { return await _cache.GetOrAddAsync( bookId, //Guid type used as the cache key async () => await GetBookFromDatabaseAsync(bookId), () => new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.Now.AddHours(1) } ); } private Task<BookCacheItem> GetBookFromDatabaseAsync(Guid bookId) { //TODO: get from database } }}
This sample service uses theGetOrAddAsync() method to get a book item from the cache.
Since the cache is explicitly implemented as usingGuid as the cache key, theGuid value is passed to the_cache_GetOrAddAsync() method.
Complex Types as the Cache Key
IDistributedCache<TCacheItem, TCacheKey> internally usesToString() method of the key object to convert it to a string. If you need to use a complex object as the cache key, you need to overrideToString method of your class.
An example class that is used as a cache key:
public class UserInOrganizationCacheKey{ public Guid UserId { get; set; } public Guid OrganizationId { get; set; } //Builds the cache key public override string ToString() { return $"{UserId}_{OrganizationId}"; }}
Example usage:
public class BookService : ITransientDependency{ private readonly IDistributedCache<UserCacheItem, UserInOrganizationCacheKey> _cache; public BookService( IDistributedCache<UserCacheItem, UserInOrganizationCacheKey> cache) { _cache = cache; } ...}
Configuration
AbpDistributedCacheOptions
AbpDistributedCacheOptions is the mainoptions class to configure the caching.
Example: Set the cache key prefix for the application
Write that code inside theConfigureServices method of yourmodule class.
Available Options
HideErrors (bool, default:true): Enables/disables hiding the errors on writing/reading values from the cache server.
KeyPrefix (string, default:null): If your cache server is shared by multiple applications, you can set a prefix for the cache keys for your application. In this case, different applications can not overwrite each other's cache items.
GlobalCacheEntryOptions (DistributedCacheEntryOptions): Used to set default distributed cache options (likeAbsoluteExpiration andSlidingExpiration) used when you don't specify the options while saving cache items. The default value uses theSlidingExpiration as 20 minutes.
Error Handling
When you design a cache for your objects, you typically try to get the value from the cache first. If not found in the cache, you query the object from theoriginal source. It may be located in adatabase or may require an HTTP call to a remote server to be performed.
In most cases, you want totolerate the cache errors; If you get an error from the cache server, you don't want to cancel the operation. Instead, you silently hide (and log) the error andquery from the original source. This is what the ABP does by default.
ABP's Distributed Cachehandle, log and hide errors by default. There is an option to change this globally (see the options below).
In addition, all of theIDistributedCache<TCacheItem> (andIDistributedCache<TCacheItem, TCacheKey>) methods have an optionalhideErrors parameter, which isnull by default. The global value is used if this parameter is left asnull; otherwise, you can decide to hide or throw the exceptions for individual method calls.
Batch Operations
ABP's distributed cache interfaces provide methods to perform batch operations that improve performance when you want to batch operation multiple cache items in a single method call.
SetManyAsync andSetMany methods can be used to set multiple values to the cache.
GetManyAsync andGetMany methods can be used to retrieve multiple values from the cache.
GetOrAddManyAsync andGetOrAddMany methods can be used to retrieve multiple values and set missing values from the cache
RefreshManyAsync andRefreshMany methods can be used to reset the sliding expiration timeout of multiple values from the cache
RemoveManyAsync andRemoveMany methods can be used to remove multiple values from the cache
These are not standard methods of the ASP.NET Core caching. So, some providers may not support them. They are supported by theABP Redis Cache integration package. If the provider doesn't support it, it falls back toSetAsync andGetAsync ... methods (called once for each item).
Caching Entities
ABP provides aDistributed Entity Cache System for caching entities. It is useful if you want to use caching for quicker access to the entity rather than repeatedly querying it from the database.
It's designed as read-only and automatically invalidates a cached entity if the entity is updated or deleted.
See theEntity Cache documentation for more information.
Advanced Topics
Unit Of Work Level Cache
Distributed cache service provides an interesting feature. Assume that you've updated the price of a book in the database, then set the new price to the cache, so you can use the cached value later. What if you have an exception after setting the cache and yourollback the transaction that updates the price of the book? In this case, cache value will be incorrect.
IDistributedCache<..> methods gets an optional parameter, namedconsiderUow, which isfalse by default. If you set it totrue, then the changes you made for the cache are not actually applied to the real cache store, but associated with the currentunit of work. You get the value you set in the same unit of work, but the changes are appliedonly if the current unit of work succeeds.
IDistributedCacheSerializer
IDistributedCacheSerializer service is used to serialize and deserialize the cache items. The default implementation is theUtf8JsonDistributedCacheSerializer class that usesIJsonSerializer service to convert objects toJSON and vice verse. Then it uses UTF8 encoding to convert the JSON string to a byte array which is accepted by the distributed cache.
You canreplace this service with your own implementation if you want to implement your own serialization logic.
IDistributedCacheKeyNormalizer
IDistributedCacheKeyNormalizer is implemented by theDistributedCacheKeyNormalizer class by default. It adds the cache name, application cache prefix and current tenant ID to the cache key. If you need a more advanced key normalization, you canreplace this service with your own implementation.