diff --git a/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs b/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs index 4eb7182..d623212 100644 --- a/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs +++ b/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs @@ -7,9 +7,21 @@ // ReSharper disable once CheckNamespace - MS guidelines say put DI registration in this NS namespace Microsoft.Extensions.DependencyInjection { - // See https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCacheServiceCollectionExtensions.cs + // See https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCacheServiceCollectionExtensions.cs + /// + /// Set of extensions for registering LazyCache dependencies with instance. + /// public static class LazyCacheServiceCollectionExtensions { + /// + /// Register a non distributed in memory implementation of . + /// + /// + /// For implementation details see + /// + /// Instance of . + /// Modified instance of . + /// public static IServiceCollection AddLazyCache(this IServiceCollection services) { if (services == null) throw new ArgumentNullException(nameof(services)); @@ -25,6 +37,13 @@ public static IServiceCollection AddLazyCache(this IServiceCollection services) return services; } + /// + /// Register a custom implementation of to the + /// + /// Instance of . + /// A delegate that allows users to inject their own implementation. + /// Modified instance of + /// Thrown when any of: or are null. public static IServiceCollection AddLazyCache(this IServiceCollection services, Func implementationFactory) { diff --git a/LazyCache.Ninject/LazyCacheModule.cs b/LazyCache.Ninject/LazyCacheModule.cs index c1d7531..d2e92c5 100644 --- a/LazyCache.Ninject/LazyCacheModule.cs +++ b/LazyCache.Ninject/LazyCacheModule.cs @@ -6,20 +6,36 @@ namespace LazyCache { + // See https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCacheServiceCollectionExtensions.cs + /// + /// Set of extensions for registering LazyCache dependencies with instance. + /// public class LazyCacheModule : NinjectModule { private readonly Func implementationFactory; + /// + /// Initializes new instance of . + /// + /// + /// For implementation details see + /// public LazyCacheModule() { } + /// + /// Initializes new instance of . + /// + /// A delegate that allows users to inject their own implementation. public LazyCacheModule(Func implementationFactory) { this.implementationFactory = implementationFactory; } - // See also https://github.com/aspnet/Caching/blob/dev/src/Microsoft.Extensions.Caching.Memory/MemoryCacheServiceCollectionExtensions.cs + /// + /// Overrides and registers LazyeCache dependencies. + /// public override void Load() { Bind>().ToConstant(Options.Create(new MemoryCacheOptions())); diff --git a/LazyCache/CachingService.cs b/LazyCache/CachingService.cs index df091de..0823b42 100644 --- a/LazyCache/CachingService.cs +++ b/LazyCache/CachingService.cs @@ -14,10 +14,18 @@ public class CachingService : IAppCache private readonly int[] keyLocks; + /// + /// Initializes instance of . + /// public CachingService() : this(DefaultCacheProvider) { } + /// + /// Initializes instance of . + /// + /// Lazy instance of . + /// Throw when is null. public CachingService(Lazy cacheProvider) { this.cacheProvider = cacheProvider ?? throw new ArgumentNullException(nameof(cacheProvider)); @@ -25,20 +33,32 @@ public CachingService(Lazy cacheProvider) keyLocks = new int[lockCount]; } + /// + /// Initializes instance of . + /// + /// Instance of . + /// Throw when is null. + public CachingService(ICacheProvider cache) : this(() => cache) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + } + + /// + /// Initializes instance of . + /// + /// Delegate to be used to create and instance of . + /// Thrown if is null. public CachingService(Func cacheProviderFactory) { if (cacheProviderFactory == null) throw new ArgumentNullException(nameof(cacheProviderFactory)); cacheProvider = new Lazy(cacheProviderFactory); var lockCount = Math.Max(Environment.ProcessorCount * 8, 32); keyLocks = new int[lockCount]; - - } - - public CachingService(ICacheProvider cache) : this(() => cache) - { - if (cache == null) throw new ArgumentNullException(nameof(cache)); } + /// + /// Default cache provider instance: . + /// public static Lazy DefaultCacheProvider { get; set; } = new Lazy(() => new MemoryCacheProvider( @@ -46,9 +66,6 @@ public CachingService(ICacheProvider cache) : this(() => cache) new MemoryCacheOptions()) )); - /// - /// Seconds to cache objects for by default - /// [Obsolete("DefaultCacheDuration has been replaced with DefaultCacheDurationSeconds")] public virtual int DefaultCacheDuration { @@ -57,10 +74,144 @@ public virtual int DefaultCacheDuration } /// - /// Policy defining how long items should be cached for unless specified + /// Gets an instance of . + /// + /// + /// Default implementation . + /// + public virtual ICacheProvider CacheProvider => cacheProvider.Value; + + /// + /// Defines the value for . /// public virtual CacheDefaults DefaultCachePolicy { get; set; } = new CacheDefaults(); + /// + /// Unwraps the lazy object and return its value. + /// + /// Item type. + /// An item. + /// Determines whether item type has changed from being . + /// Unwrapped item value. + protected virtual T GetValueFromLazy(object item, out bool valueHasChangedType) + { + valueHasChangedType = false; + switch (item) + { + case Lazy lazy: + return lazy.Value; + case T variable: + return variable; + case AsyncLazy asyncLazy: + // this is async to sync - and should not really happen as long as GetOrAddAsync is used for an async value. + // Only happens when you cache something async and then try and grab it again later using the non async methods. + return asyncLazy.Value.ConfigureAwait(false).GetAwaiter().GetResult(); + case Task task: + return task.Result; + } + + // if they have cached something else with the same key we need to tell caller to reset the cached item + // although this is probably not the fastest this should not get called on the main use case + // where you just hit the first switch case above. + var itemsType = item?.GetType(); + if (itemsType != null && itemsType.IsGenericType && itemsType.GetGenericTypeDefinition() == typeof(Lazy<>)) + { + valueHasChangedType = true; + } + + return default(T); + } + + /// + /// Unwraps the lazy object and return a task. + /// + /// Item type. + /// Lazy item or a task. + /// Signals if the value has changed its type. + /// A task + protected virtual Task GetValueFromAsyncLazy(object item, out bool valueHasChangedType) + { + valueHasChangedType = false; + switch (item) + { + case AsyncLazy asyncLazy: + return asyncLazy.Value; + case Task task: + return task; + // this is sync to async and only happens if you cache something sync and then get it later async + case Lazy lazy: + return Task.FromResult(lazy.Value); + case T variable: + return Task.FromResult(variable); + } + + // if they have cached something else with the same key we need to tell caller to reset the cached item + // although this is probably not the fastest this should not get called on the main use case + // where you just hit the first switch case above. + var itemsType = item?.GetType(); + if (itemsType != null && itemsType.IsGenericType && itemsType.GetGenericTypeDefinition() == typeof(AsyncLazy<>)) + { + valueHasChangedType = true; + } + + return Task.FromResult(default(T)); + } + + /// + /// Ensure that the pr object value gets unwrapped. + /// + /// Cached item type. + /// Collection of . + protected virtual void EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy( + IList callbackRegistrations) + { + if (callbackRegistrations != null) + foreach (var item in callbackRegistrations) + { + var originalCallback = item.EvictionCallback; + item.EvictionCallback = (key, value, reason, state) => + { + // before the original callback we need to unwrap the Lazy that holds the cache item + if (value is AsyncLazy asyncCacheItem) + value = asyncCacheItem.IsValueCreated ? asyncCacheItem.Value : Task.FromResult(default(T)); + else if (value is Lazy cacheItem) + value = cacheItem.IsValueCreated ? cacheItem.Value : default(T); + + // pass the unwrapped cached value to the original callback + originalCallback(key, value, reason, state); + }; + } + } + + /// + /// Validate the key value. + /// + /// The cache key. + /// Thrown if key is null. + /// Thrown if key is empty or whitespace. + protected virtual void ValidateKey(string key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + if (string.IsNullOrWhiteSpace(key)) + throw new ArgumentOutOfRangeException(nameof(key), "Cache keys cannot be empty or whitespace"); + } + + + /// + /// Tries to add an item to cache. + /// + /// Item type. + /// Cache key. + /// Object that will be cached. + /// Instance of that can be used to configure behavior of the cached item. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// public virtual void Add(string key, T item, MemoryCacheEntryOptions policy) { if (item == null) @@ -70,6 +221,14 @@ public virtual void Add(string key, T item, MemoryCacheEntryOptions policy) CacheProvider.Set(key, item, policy); } + /// + /// Tries to synchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A cached object + /// Thrown when is null. + /// Thrown when is empty or whitespace. public virtual T Get(string key) { ValidateKey(key); @@ -79,6 +238,18 @@ public virtual T Get(string key) return GetValueFromLazy(item, out _); } + /// + /// Tries to asynchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A task with the cached object. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// public virtual Task GetAsync(string key) { ValidateKey(key); @@ -88,6 +259,19 @@ public virtual Task GetAsync(string key) return GetValueFromAsyncLazy(item, out _); } + /// + /// Tries to synchronously retrieve the object associated with the provided key if present. + /// + /// Item type. + /// Cache key. + /// Instance that will capture the cached object value. + /// True if the item was found. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// public virtual bool TryGetValue(string key, out T value) { ValidateKey(key); @@ -95,11 +279,44 @@ public virtual bool TryGetValue(string key, out T value) return CacheProvider.TryGetValue(key, out value); } + /// + /// Tries to synchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// A cached object or null. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + /// + /// Method will bubble up any exception that may be thrown by delegate. + /// public virtual T GetOrAdd(string key, Func addItemFactory) { return GetOrAdd(key, addItemFactory, null); } + /// + /// Tries to synchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// Instance of that can be used to configure behavior of the cached item. + /// A cached object or null. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + /// + /// Method will bubble up any exception that may be thrown by delegate. + /// public virtual T GetOrAdd(string key, Func addItemFactory, MemoryCacheEntryOptions policy) { ValidateKey(key); @@ -161,30 +378,42 @@ object CacheFactory(ICacheEntry entry) => } } - private static void SetAbsoluteExpirationFromRelative(ICacheEntry entry) - { - if (!entry.AbsoluteExpirationRelativeToNow.HasValue) return; - - var absoluteExpiration = DateTimeOffset.UtcNow + entry.AbsoluteExpirationRelativeToNow.Value; - if (!entry.AbsoluteExpiration.HasValue || absoluteExpiration < entry.AbsoluteExpiration) - entry.AbsoluteExpiration = absoluteExpiration; - } - - public virtual void Remove(string key) - { - ValidateKey(key); - CacheProvider.Remove(key); - } - - public virtual ICacheProvider CacheProvider => cacheProvider.Value; - + /// + /// Tries to asynchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// A task with the cached object. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + /// + /// Method will bubble up any exception that may be thrown by delegate. + /// public virtual Task GetOrAddAsync(string key, Func> addItemFactory) { return GetOrAddAsync(key, addItemFactory, null); } - public virtual async Task GetOrAddAsync(string key, Func> addItemFactory, - MemoryCacheEntryOptions policy) + /// + /// Tries to asynchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// Instance of that can be used to configure behavior of the cached item. + /// A task with the cached object. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + public virtual async Task GetOrAddAsync(string key, Func> addItemFactory, MemoryCacheEntryOptions policy) { ValidateKey(key); @@ -253,92 +482,29 @@ object CacheFactory(ICacheEntry entry) => } } - protected virtual T GetValueFromLazy(object item, out bool valueHasChangedType) - { - valueHasChangedType = false; - switch (item) - { - case Lazy lazy: - return lazy.Value; - case T variable: - return variable; - case AsyncLazy asyncLazy: - // this is async to sync - and should not really happen as long as GetOrAddAsync is used for an async - // value. Only happens when you cache something async and then try and grab it again later using - // the non async methods. - return asyncLazy.Value.ConfigureAwait(false).GetAwaiter().GetResult(); - case Task task: - return task.Result; - } - - // if they have cached something else with the same key we need to tell caller to reset the cached item - // although this is probably not the fastest this should not get called on the main use case - // where you just hit the first switch case above. - var itemsType = item?.GetType(); - if (itemsType != null && itemsType.IsGenericType && itemsType.GetGenericTypeDefinition() == typeof(Lazy<>)) - { - valueHasChangedType = true; - } - - return default(T); - } - - protected virtual Task GetValueFromAsyncLazy(object item, out bool valueHasChangedType) - { - valueHasChangedType = false; - switch (item) - { - case AsyncLazy asyncLazy: - return asyncLazy.Value; - case Task task: - return task; - // this is sync to async and only happens if you cache something sync and then get it later async - case Lazy lazy: - return Task.FromResult(lazy.Value); - case T variable: - return Task.FromResult(variable); - } - - // if they have cached something else with the same key we need to tell caller to reset the cached item - // although this is probably not the fastest this should not get called on the main use case - // where you just hit the first switch case above. - var itemsType = item?.GetType(); - if (itemsType != null && itemsType.IsGenericType && itemsType.GetGenericTypeDefinition() == typeof(AsyncLazy<>)) - { - valueHasChangedType = true; - } - - return Task.FromResult(default(T)); - } - - protected virtual void EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy( - IList callbackRegistrations) + // + /// Tries to remove the object associated with the provided key. + /// + /// Cache key. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + public virtual void Remove(string key) { - if (callbackRegistrations != null) - foreach (var item in callbackRegistrations) - { - var originalCallback = item.EvictionCallback; - item.EvictionCallback = (key, value, reason, state) => - { - // before the original callback we need to unwrap the Lazy that holds the cache item - if (value is AsyncLazy asyncCacheItem) - value = asyncCacheItem.IsValueCreated ? asyncCacheItem.Value : Task.FromResult(default(T)); - else if (value is Lazy cacheItem) - value = cacheItem.IsValueCreated ? cacheItem.Value : default(T); - - // pass the unwrapped cached value to the original callback - originalCallback(key, value, reason, state); - }; - } + ValidateKey(key); + CacheProvider.Remove(key); } - protected virtual void ValidateKey(string key) + private static void SetAbsoluteExpirationFromRelative(ICacheEntry entry) { - if (key == null) - throw new ArgumentNullException(nameof(key)); + if (!entry.AbsoluteExpirationRelativeToNow.HasValue) return; - if (string.IsNullOrWhiteSpace(key)) - throw new ArgumentOutOfRangeException(nameof(key), "Cache keys cannot be empty or whitespace"); + var absoluteExpiration = DateTimeOffset.UtcNow + entry.AbsoluteExpirationRelativeToNow.Value; + if (!entry.AbsoluteExpiration.HasValue || absoluteExpiration < entry.AbsoluteExpiration) + entry.AbsoluteExpiration = absoluteExpiration; } } } \ No newline at end of file diff --git a/LazyCache/ExpirationMode.cs b/LazyCache/ExpirationMode.cs index c85c383..7a53a66 100644 --- a/LazyCache/ExpirationMode.cs +++ b/LazyCache/ExpirationMode.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; namespace LazyCache { diff --git a/LazyCache/IAppCache.cs b/LazyCache/IAppCache.cs index 1bf9737..1b3cecc 100644 --- a/LazyCache/IAppCache.cs +++ b/LazyCache/IAppCache.cs @@ -1,26 +1,160 @@ using System; using System.Threading.Tasks; +using LazyCache.Providers; using Microsoft.Extensions.Caching.Memory; namespace LazyCache { public interface IAppCache { + /// + /// Gets an instance of . + /// + /// + /// Default implementation . + /// ICacheProvider CacheProvider { get; } /// - /// Define the number of seconds to cache objects for by default + /// Defines the value for . /// CacheDefaults DefaultCachePolicy { get; } + + /// + /// Tries to add an item to the cache. + /// + /// Item type. + /// Cache key. + /// Object that will be cached. + /// Instance of that can be used to configure behavior of the cached item. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// void Add(string key, T item, MemoryCacheEntryOptions policy); + + /// + /// Tries to synchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A cached object + /// Thrown when is null. + /// Thrown when is empty or whitespace. T Get(string key); + + /// + /// Tries to asynchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key + /// A task with the cached object. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// Task GetAsync(string key); + + /// + /// Tries to synchronously retrieve the object associated with the provided key if present. + /// + /// Item type. + /// Cache key. + /// Instance that will capture the cached object value. + /// True if the item was found. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// bool TryGetValue(string key, out T value); + /// + /// Tries to synchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// A cached object or null. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + /// + /// Method will bubble up any exception that may be thrown by delegate. + /// T GetOrAdd(string key, Func addItemFactory); + + /// + /// Tries to synchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// Instance of that can be used to configure behavior of the cached item. + /// A cached object or null. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + /// + /// Method will bubble up any exception that may be thrown by delegate. + /// T GetOrAdd(string key, Func addItemFactory, MemoryCacheEntryOptions policy); + + /// + /// Tries to asynchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// A task with the cached object. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// + /// + /// Method will bubble up any exception that may be thrown by delegate. + /// Task GetOrAddAsync(string key, Func> addItemFactory); + + /// + /// Tries to asynchronously retrieve an object associated with the provided key. + /// + /// Item type. + /// Cache key. + /// A method that will be executed to add an item to the cache. + /// Instance of that can be used to configure behavior of the cached item. + /// A task with the cached object. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// Task GetOrAddAsync(string key, Func> addItemFactory, MemoryCacheEntryOptions policy); + + /// + /// Tries to remove the object associated with the provided key. + /// + /// Cache key. + /// + /// Thrown when is null. + /// + /// + /// Thrown when is empty or whitespace. + /// void Remove(string key); } } \ No newline at end of file