The first implementation uses the memory cache system, and I thought it would be educational to show that to you. Caching data in memory is something you might need to do sooner rather than later. However, we are hiding the cache system behind our implementation, which is also educational.Here is the ApplicationMemoryCache class:
public class ApplicationMemoryCache : IApplicationState
{
private readonly IMemoryCache _memoryCache;
public ApplicationMemoryCache(IMemoryCache memoryCache)
{
_memoryCache = memoryCache ??
throw new ArgumentNullException(nameof(memoryCache));
}
public TItem Get<TItem>(string key)
{
return _memoryCache.Get<TItem>(key);
}
public bool Has<TItem>(string key)
{
return _memoryCache.TryGetValue<TItem>(key, out _);
}
public void Set<TItem>(string key, TItem value)
{
_memoryCache.Set(key, value);
}
}
Note
The ApplicationMemoryCache class is a thin wrapper over IMemoryCache, hiding the implementation details. Such a wrapper is similar to the Façade and Adapter patterns we explore in Chapter 11, Structural Patterns.
This simple class and two lines in our composition root make it an application-wide key-value store; done already! Let’s now explore the second implementation.
Second implementation
The second implementation uses ConcurrentDictionary<string, object> to store the application state data and ensure thread safety, as multiple users could use the application state simultaneously. The ApplicationDictionary class is almost as simple as ApplicationMemoryCache:
using System.Collections.Concurrent;
namespace ApplicationState;
public class ApplicationDictionary : IApplicationState
{
private readonly ConcurrentDictionary<string, object> _memoryCache = new();
public TItem?
Get<TItem>(string key)
{
return _memoryCache.TryGetValue(key, out var item)
?
(TItem)item
: default;
}
public bool Has<TItem>(string key)
{
return _memoryCache.TryGetValue(key, out var item) && item is TItem;
}
public void Set<TItem>(string key, TItem value)
where TItem : notnull
{
_memoryCache.AddOrUpdate(key, value, (k, v) => value);
}
}
The preceding code leverages the TryGetValue and AddOrUpdate methods to ensure thread safety while keeping the logic to a minimum and ensuring we avoid coding mistakes.
Can you spot the flaw that might cause some problems in this design? See the solution at the end of the project section.
Let’s explore how to use the implementations.