Table of Contents
Introduction
It’s good to have a wrapper class on top of an underlying cache implementation to abstract the core caching logic.
We will continue with in-memory caching and create a custom in memory cache c# service, if you have not read the previous post In-Memory Caching in .NET 7 ,read it first to continue with the project.
Agenda
- Add a class library project
- Creating the
AsyncLazy
class and extension methods - Creating the cache service
- Use of the cache service in our code
Adding class library project
1. Add a new class library project to the solution named CS.Services.CacheMemoryService
, and delete the class that was created by default

2. Install the following NuGet package:
Microsoft.Extensions.Caching.Memory
Creating AsyncLazy class and Extension methods
1. Add a class file called AsyncLazy
We use lazy initialization of an object, which means that its creation is deferred until it is used for the first time, and this ensures that the initialization method is called only once.
namespace CS.Services.MemoryCacheService
{
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Run(valueFactory))
{ }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Run(() => taskFactory()))
{ }
public TaskAwaiter<T> GetAwaiter()
{
return Value.GetAwaiter();
}
}
}
The AsyncLazy
type is derived from Lazy<Task<T>>
. It combines the power of Lazy<T>
and Task<T>
to get the best of both types.
AsyncLazy
behaves like a Lazy<Task<T>>
, which executes its factory delegate on a thread from the thread pool and is not executed more than once.
You can read more about AsyncLazy
here.
2. Create a new project folder named Extensions, and add a class file named MemoryCacheExtensions
namespace CS.Services.CacheMemoryService.Extensions
{
public static class MemoryCacheExtensions
{
public static Task<T> GetOrCreateAsyncLazy<T>(this IMemoryCache cache, object key,
Func<Task<T>> LazyFactory, MemoryCacheEntryOptions options)
{
if (!cache.TryGetValue(key, out AsyncLazy<T> asyncLazy))
{
var entry = cache.CreateEntry(key);
if (options != null) entry.SetOptions(options);
var newAsyncLazy = new AsyncLazy<T>(LazyFactory);
entry.Value = newAsyncLazy;
entry.Dispose(); // Dispose inserts the entry in the cache
if (!cache.TryGetValue(key, out asyncLazy)) asyncLazy = newAsyncLazy;
}
if (asyncLazy.Value.IsCompleted) return asyncLazy.Value;
return asyncLazy.Value.ContinueWith(t => t,
default, TaskContinuationOptions.RunContinuationsAsynchronously,
TaskScheduler.Default).Unwrap();
}
public static Task<T> GetOrCreateAsyncLazy<T>(this IMemoryCache cache, object key,
Func<Task<T>> LazyFactory, DateTimeOffset absoluteExpiration)
{
return cache.GetOrCreateAsyncLazy(key, LazyFactory,
new MemoryCacheEntryOptions() { AbsoluteExpiration = absoluteExpiration });
}
public static Task<T> GetOrCreateAsyncLazy<T>(this IMemoryCache cache, object key,
Func<Task<T>> LazyFactory, TimeSpan slidingExpiration)
{
return cache.GetOrCreateAsyncLazy(key, LazyFactory,
new MemoryCacheEntryOptions() { SlidingExpiration = slidingExpiration });
}
}
We have added three MemoryCache extension methods that use the AsyncLazy
type created in the third step.
The GetOrCreateAsyncLazy
method receivesLazyFacory
delegate and MemoryCacheEntryOptions
parameters , it sets the cache entry and creates the cache value when it is needed. We use the GetOrCreateAsyncLazy
method in the other two extension methods.
Creating the In Memory Cache Service
1. Create a new project folder named Interfaces, and add an interface file named ICSMemoryCacheService
namespace CS.Services.CacheMemoryService.Interfaces
{
public interface ICSMemoryCacheService
{
object GetCache(object key);
T GetCache<T>(object key);
void RemoveCache(object key);
T SetCache<T>(object key, T value, MemoryCacheEntryOptions options );
T SetCache<T>(object key, T value, DateTimeOffset absoluteExpiration);
T SetCache<T>(object key, T value, TimeSpan slidingExpiration);
Task<T> GetOrCreateAsyncLazy<T>(object key, Func<Task<T>> factory, MemoryCacheEntryOptions options);
Task<T> GetOrCreateAsyncLazy<T>(object key, Func<Task<T>> factory, DateTimeOffset absoluteExpiration);
Task<T> GetOrCreateAsyncLazy<T>(object key, Func<Task<T>> factory, TimeSpan slidingExpiration);
}
}
This code defines the ICSMemoryCacheService
interface with GetCache
, SetCache
and GetOrCreateAsyncLazy
methods for retrieving and storing data in the cache, respectively.
2. Add a class file named CSMemoryCacheService
The CSMemoryCacheService
implementation uses the IMemoryCache
instance provided by the service container to perform the cache operations.
namespace CS.Services.CacheMemoryService
{
public class CSMemoryCacheService : ICSMemoryCacheService
{
private readonly IMemoryCache memoryCache;
public CSMemoryCacheService(IMemoryCache memoryCache)
{
this.memoryCache = memoryCache;
}
public object GetCache(object key)
{
return memoryCache.Get(key);
}
public T GetCache<T>(object key)
{
return memoryCache.Get<T>(key);
}
public void RemoveCache(object key)
{
memoryCache.Remove(key);
}
public T SetCache<T>(object key, T value, MemoryCacheEntryOptions options)
{
return memoryCache.Set<T>(key, value, options);
}
public T SetCache<T>(object key, T value, DateTimeOffset absoluteExpiration)
{
return memoryCache.Set<T>(key, value, absoluteExpiration);
}
public T SetCache<T>(object key, T value, TimeSpan slidingExpiration)
{
using ICacheEntry entry = memoryCache.CreateEntry(key);
entry.SetSlidingExpiration(slidingExpiration);
entry.Value = value;
return value;
}
public Task<T> GetOrCreateAsyncLazy<T>(object key, Func<Task<T>> factory, MemoryCacheEntryOptions options)
{
return memoryCache.GetOrCreateAsyncLazy(key, factory, options);
}
public Task<T> GetOrCreateAsyncLazy<T>(object key, Func<Task<T>> factory, DateTimeOffset absoluteExpiration)
{
return memoryCache.GetOrCreateAsyncLazy(key, factory, absoluteExpiration);
}
public Task<T> GetOrCreateAsyncLazy<T>(object key, Func<Task<T>> factory, TimeSpan slidingExpiration)
{
return memoryCache.GetOrCreateAsyncLazy(key, factory, slidingExpiration);
}
}
}
3. Add a class file named CSMemoryCacheServiceExtensions
to Extensions folder
The code above adds the CSMemoryCacheService
class to the Dependency Injection container.
namespace CS.Services.CacheMemoryService.Extensions
{
public static class CSMemoryCacheServiceExtensions
{
public static IServiceCollection AddCSMemoryCacheService(this IServiceCollection services)
{
return services.AddSingleton<ICSMemoryCacheService, CSMemoryCacheService>();
}
}
}
Also read https://dotnetcoder.com/creating-a-blazor-toast-component-using-c-only-html-and-css/
Use of the cache service in our code
1. In Solution Explorer, right-click the CS.MemoryCache.WebApi project’s Dependencies node, and select Add Project Reference

2.In the Reference Manager dialog, select the CS.Services.CacheMemoryService project, and select OK

3. In your Program.cs file, add the following code to configure the cache service:
builder.Services.AddMemoryCache();
builder.Services.AddCSMemoryCacheService();
4. Update the EmployeeController
to receive an instance of the ICSMemoryCacheCache
class using constructor injection.
namespace CS.MemoryCache.WebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : ControllerBase
{
private readonly IEmployeeService employeeService;
private readonly IMemoryCache memoryCache;
private readonly ICSMemoryCacheService cSMemoryCacheService;
public EmployeeController(IMemoryCache memoryCache,
IEmployeeService employeeService,
ICSMemoryCacheService cSMemoryCacheService)
{
this.memoryCache = memoryCache;
this.employeeService = employeeService;
this.cSMemoryCacheService = cSMemoryCacheService;
}
[HttpGet("employees")]
public async Task<IEnumerable<Employee>> GelAllEmployees()
{
IEnumerable<Employee> cacheEmployees;
if (!memoryCache.TryGetValue("cacheEmployees", out cacheEmployees))
{
cacheEmployees = await employeeService.GetEmployees();
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(100))
.SetAbsoluteExpiration(TimeSpan.FromSeconds(200))
.SetPriority(CacheItemPriority.NeverRemove)
.SetSize(1024);
memoryCache.Set("cacheEmployees", cacheEmployees, cacheEntryOptions);
}
memoryCache.TryGetValue("cacheEmployees", out cacheEmployees);
return cacheEmployees;
}
[HttpGet("cache")]
public async Task<IEnumerable<Employee>> GetEmployees()
{
var items = await cSMemoryCacheService.GetOrCreateAsyncLazy<IEnumerable<Employee>>("employees", async () =>
{
var employees = await employeeService.GetEmployees();
return employees;
}, new MemoryCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(10) });
return items;
}
}
}
In the code above, we use the ICSMemoryCacheService
to retrieve data from the cache. If the data is not found in the cache, it is fetched from the data source.
Conclusion
The in-memory cache implementation provides a way to store data in memory as key-value pairs. It is designed to be fast and efficient for small to medium-sized data sets that can be stored in memory without consuming excessive amounts of memory.
The code for the demo can be found Here
Also read https://dotnetcoder.com/creating-a-reusable-blazor-search-component/
Enjoy This Blog?
Discover more from Dot Net Coder
Subscribe to get the latest posts sent to your email.