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

in memory cache c#, in-memory cache, in memory cache

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, SetCacheand 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 CSMemoryCacheServiceExtensionsto 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

add reference

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

add reference

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?

Buy Me a Coffee Donate via PayPal

Discover more from Dot Net Coder

Subscribe to get the latest posts sent to your email.

Author

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
100% Free SEO Tools - Tool Kits PRO