├── FluentCache.png ├── FluentCache_24.png ├── FluentCache ├── AssemblyInfo.cs ├── ICacheStrategyAsync.cs ├── Execution │ ├── ICacheExecutionPlan.cs │ └── CacheExecutionPlan.cs ├── Strategies │ ├── SingleValueCacheStrategy.cs │ ├── BulkMethodCacheStrategy.cs │ ├── CacheStrategy.cs │ ├── MethodCacheStrategy.cs │ ├── BulkCacheStrategyIncomplete.cs │ ├── BulkCacheStrategy.cs │ ├── BulkCacheStrategyAsync.cs │ ├── CacheStrategyIncomplete.cs │ ├── CacheStrategy_Generic.cs │ ├── CacheStrategyAsync.cs │ └── CacheStrategyParameterized.cs ├── CacheOperation.cs ├── CacheValidationResult.cs ├── Parameter.cs ├── Expressions │ ├── InvalidCachingExpressionException.cs │ └── ArgumentReplacer.cs ├── CachedValue.cs ├── CacheExpiration.cs ├── ICacheStrategy.cs ├── FluentCache.csproj ├── FluentCacheException.cs ├── ParameterCacheKeys.cs ├── RetrievalErrorHandlerResult.cs ├── ICache.cs ├── Cache_Generic.cs └── Simple │ └── FluentDictionaryCache.cs ├── FluentCache.Test.Nuget ├── SimpleCacheNugetTests.cs ├── MemoryCacheNugetTests.cs ├── Properties │ └── AssemblyInfo.cs ├── RuntimeCacheNugetTests.cs ├── RedisCacheNugetTests.cs ├── packages.config ├── CacheTester.cs └── FluentCache.Test.Nuget.csproj ├── FluentCache.Test ├── Implementations │ ├── SimpleCacheTests.cs │ ├── MemoryCacheTests.cs │ ├── RuntimeCacheTests.cs │ ├── RedisCacheTests.cs │ └── CacheTester.cs ├── packages.config ├── Strategies │ ├── ParameterReplacerTests.cs │ ├── ClosureTest.cs │ ├── GenericTest.cs │ ├── ExampleTests.cs │ └── BulkCacheTests.cs ├── Properties │ └── AssemblyInfo.cs ├── CacheFailureTests.cs └── FluentCache.Test.csproj ├── FluentCache.Microsoft.Extensions.Caching.Abstractions ├── Distributed │ ├── ISerializer.cs │ ├── DistributedStorage.cs │ └── FluentIDistributedCache.cs ├── Memory │ ├── MemoryStorage.cs │ └── FluentIMemoryCache.cs └── FluentCache.Microsoft.Extensions.Caching.Abstractions.csproj ├── FluentCache.Microsoft.Extensions.Caching.Redis ├── JsonSerializer.cs ├── FluentRedisCache.cs └── FluentCache.Microsoft.Extensions.Caching.Redis.csproj ├── LICENSE ├── .gitattributes ├── FluentCache.Microsoft.Extensions.Caching.Memory ├── FluentMemoryCache.cs └── FluentCache.Microsoft.Extensions.Caching.Memory.csproj ├── FluentCache.RuntimeCaching ├── FluentCache.RuntimeCaching.csproj └── FluentMemoryCache.cs ├── .gitignore ├── FluentCache.sln └── README.md /FluentCache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evan-rash/FluentCache/HEAD/FluentCache.png -------------------------------------------------------------------------------- /FluentCache_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evan-rash/FluentCache/HEAD/FluentCache_24.png -------------------------------------------------------------------------------- /FluentCache/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly: InternalsVisibleTo("FluentCache.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9b369df060a6bbba45a3df0d793b067f4c85ef8b599bee19413d890f54600dbf9d71ae85146d9101da1da4cbcc29fd9a44eb7f63dbfdad09f0715dbc6fc345627363bcfa8d82d5f6539d6baa0462bff24461ca077673bc7ecbf36f4530dccb27890e35bad28909a957370a8c86c677904f6b498b9897dc45b61a1c1a96ec3d0")] -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/SimpleCacheNugetTests.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Simple; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace FluentCache.Test.Nuget 10 | { 11 | [TestClass] 12 | public class SimleCacheTests 13 | { 14 | [TestMethod] 15 | public async Task Nuget_SimpleCache() 16 | { 17 | Func factory = () => new FluentDictionaryCache(); 18 | await CacheTester.TestCacheAsync(factory); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FluentCache.Test/Implementations/SimpleCacheTests.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Simple; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace FluentCache.Test.Implementations 10 | { 11 | [TestClass] 12 | public class SimleCacheTests 13 | { 14 | [TestMethod] 15 | public async Task Implementation_SimpleCache() 16 | { 17 | Func factory = () => new FluentDictionaryCache(); 18 | await CacheTester.TestCacheAsync(factory); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/MemoryCacheNugetTests.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Microsoft.Extensions.Caching.Memory; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace FluentCache.Test.Nuget 10 | { 11 | [TestClass] 12 | public class MemoryCacheNugetTests 13 | { 14 | [TestMethod] 15 | public async Task Nuget_MemoryCache() 16 | { 17 | await CacheTester.TestCacheAsync(CacheFactory); 18 | } 19 | 20 | private ICache CacheFactory() 21 | { 22 | return new FluentMemoryCache(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Abstractions/Distributed/ISerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FluentCache.Microsoft.Extensions.Caching.Distributed 6 | { 7 | /// 8 | /// Defines an interface for serializing and deserializing values to the distributed cache 9 | /// 10 | public interface ISerializer 11 | { 12 | /// 13 | /// Serialize the specified object to a string 14 | /// 15 | string Serialize(object value); 16 | 17 | /// 18 | /// Deserialize the specified object from a string 19 | /// 20 | T Deserialize(string value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("FluentCache.Test.Nuget")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("FluentCache.Test.Nuget")] 10 | [assembly: AssemblyCopyright("Copyright © 2017")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("fd2bada7-df67-4c0d-8bef-01397191e9ec")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /FluentCache.Test/Implementations/MemoryCacheTests.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Microsoft.Extensions.Caching.Memory; 2 | using FluentCache.Simple; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace FluentCache.Test.Implementations 11 | { 12 | [TestClass] 13 | public class MemoryCacheTests 14 | { 15 | [TestMethod] 16 | public async Task Implementation_MemoryCache() 17 | { 18 | await CacheTester.TestCacheAsync(CacheFactory); 19 | } 20 | 21 | private ICache CacheFactory() 22 | { 23 | return new FluentMemoryCache(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FluentCache/ICacheStrategyAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// Specifies a caching strategy to retrieve cached values asynchronously 11 | /// 12 | public interface ICacheStrategyAsync : ICacheStrategy 13 | { 14 | /// 15 | /// Retrieves the cached value asynchronously 16 | /// 17 | Task RetrieveAsync(CachedValue existingCachedValue); 18 | 19 | /// 20 | /// Validates the cached value asynchronously 21 | /// 22 | Task ValidateAsync(CachedValue existingCachedValue); 23 | } 24 | } -------------------------------------------------------------------------------- /FluentCache/Execution/ICacheExecutionPlan.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Execution 8 | { 9 | /// 10 | /// Defines an execution plan for getting a value from a cache, validating, and retrieving a new value 11 | /// 12 | /// The type of value that is cached 13 | public interface ICacheExecutionPlan 14 | { 15 | /// 16 | /// Executes the plan 17 | /// 18 | CachedValue Execute(); 19 | 20 | /// 21 | /// Asynchronously executes the plan 22 | /// 23 | Task> ExecuteAsync(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FluentCache/Strategies/SingleValueCacheStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | /// 10 | /// A strategy to access or update a single value in a cache 11 | /// 12 | public class SingleValueCacheStrategy : CacheStrategy 13 | { 14 | internal SingleValueCacheStrategy(ICache cache, string baseKey) 15 | : base(cache, baseKey) 16 | { 17 | } 18 | 19 | /// 20 | /// Clears the value associated with this caching strategy from the cache 21 | /// 22 | public void ClearValue() 23 | { 24 | Cache.Remove(Key, Region); 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/RuntimeCacheNugetTests.cs: -------------------------------------------------------------------------------- 1 | 2 | using FluentCache.RuntimeCaching; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace FluentCache.Test.Nuget 11 | { 12 | [TestClass] 13 | public class RuntimeCacheNugetTests 14 | { 15 | [TestMethod] 16 | public async Task Nuget_RuntimeMemoryCache() 17 | { 18 | await CacheTester.TestCacheAsync(CreateCache); 19 | } 20 | 21 | private ICache CreateCache() 22 | { 23 | var memoryCache = System.Runtime.Caching.MemoryCache.Default; 24 | var cache = new FluentMemoryCache(memoryCache); 25 | return cache; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FluentCache/CacheOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// Defines a list of caching operations 11 | /// 12 | public enum CacheOperation 13 | { 14 | /// 15 | /// Getting a value from the cache 16 | /// 17 | Get = 0, 18 | /// 19 | /// Setting a value in the cache 20 | /// 21 | Set = 1, 22 | /// 23 | /// Removing a value from the cache 24 | /// 25 | Remove = 2, 26 | /// 27 | /// Marking a value in the cache as validated 28 | /// 29 | MarkValidated = 3, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FluentCache.Test/Implementations/RuntimeCacheTests.cs: -------------------------------------------------------------------------------- 1 | 2 | using FluentCache.RuntimeCaching; 3 | using FluentCache.Simple; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace FluentCache.Test.Implementations 12 | { 13 | [TestClass] 14 | public class RuntimeCacheTests 15 | { 16 | [TestMethod] 17 | public async Task Implementation_RuntimeMemoryCache() 18 | { 19 | await CacheTester.TestCacheAsync(CreateCache); 20 | } 21 | 22 | private ICache CreateCache() 23 | { 24 | var memoryCache = System.Runtime.Caching.MemoryCache.Default; 25 | var cache = new FluentMemoryCache(memoryCache); 26 | return cache; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FluentCache/CacheValidationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// Defines possible outcomes when determining whether or not a cached value is still valid 11 | /// 12 | public enum CacheValidationResult 13 | { 14 | /// 15 | /// The cached value was determined to still be valid 16 | /// 17 | Valid = 0, 18 | 19 | /// 20 | /// The cached value was determined to be invalid 21 | /// 22 | Invalid = 1, 23 | 24 | /// 25 | /// The cached value was not validated and it is unknown whether or not the value is valid or invalid 26 | /// 27 | Unknown = 2 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Abstractions/Memory/MemoryStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FluentCache.Microsoft.Extensions.Caching.Memory 6 | { 7 | internal class MemoryStorage 8 | { 9 | public DateTime CacheDate { get; set; } 10 | public DateTime LastValidatedDate { get; set; } 11 | public long Version { get; set; } 12 | public object Value { get; set; } 13 | 14 | public CachedValue ToCachedValue() 15 | { 16 | if (!(Value is T)) 17 | return null; 18 | 19 | return new CachedValue 20 | { 21 | CachedDate = CacheDate, 22 | LastValidatedDate = LastValidatedDate, 23 | Value = (T)Value, 24 | Version = Version 25 | }; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FluentCache.Test/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Redis/JsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Microsoft.Extensions.Caching.Distributed; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace FluentCache.Microsoft.Extensions.Caching.Redis 7 | { 8 | /// 9 | /// ISerializer implementation using JSON 10 | /// 11 | public class JsonSerializer : ISerializer 12 | { 13 | /// 14 | /// Deserializes a value from JSON 15 | /// 16 | public T Deserialize(string value) 17 | { 18 | return Newtonsoft.Json.JsonConvert.DeserializeObject(value); 19 | } 20 | 21 | /// 22 | /// Serializes a value to JSON 23 | /// 24 | public string Serialize(object value) 25 | { 26 | return Newtonsoft.Json.JsonConvert.SerializeObject(value); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FluentCache/Parameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// Methods to configure how the fluent caching API handles parameters 11 | /// 12 | public static class Parameter 13 | { 14 | /// 15 | /// Informs the fluent caching API that the specified parameter should not be included when determining the cache key 16 | /// 17 | public static T DoNotCache() 18 | { 19 | return default(T); 20 | } 21 | 22 | /// 23 | /// Informs the fluent caching API that the specified parameter should not be included when determining the cache key. Use the value parameter if a value is needed for data retrieval 24 | /// 25 | public static T DoNotCache(T value) 26 | { 27 | return value; 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /FluentCache.Test/Implementations/RedisCacheTests.cs: -------------------------------------------------------------------------------- 1 | 2 | using FluentCache.Microsoft.Extensions.Caching.Redis; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace FluentCache.Test.Implementations 12 | { 13 | [TestClass] 14 | public class RedisCacheTests 15 | { 16 | [TestMethod] 17 | public async Task Implementation_RedisCache() 18 | { 19 | await CacheTester.TestCacheAsync(CacheFactory); 20 | } 21 | 22 | private ICache CacheFactory() 23 | { 24 | var keys = JToken.Parse(System.IO.File.ReadAllText(@"c:\code\keys\fluentcachetest.json")); 25 | string instance = keys.Value("instance"); 26 | string configuration = keys.Value("configuration"); 27 | 28 | return new FluentRedisCache(instance, configuration); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/RedisCacheNugetTests.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Microsoft.Extensions.Caching.Redis; 2 | using Microsoft.Extensions.Caching.Redis; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Newtonsoft.Json.Linq; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace FluentCache.Test.Nuget 12 | { 13 | [TestClass] 14 | public class RedisCacheNugetTests 15 | { 16 | [TestMethod] 17 | public async Task Nuget_RedisCache() 18 | { 19 | await CacheTester.TestCacheAsync(CacheFactory); 20 | } 21 | 22 | private ICache CacheFactory() 23 | { 24 | var keys = JToken.Parse(System.IO.File.ReadAllText(@"c:\code\keys\fluentcachetest.json")); 25 | string instance = keys.Value("instance"); 26 | string configuration = keys.Value("configuration"); 27 | 28 | return new FluentRedisCache(instance, configuration); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FluentCache/Expressions/InvalidCachingExpressionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Expressions 8 | { 9 | /// 10 | /// An exception that is thrown when the fluent caching API is unable to parse the provided expression 11 | /// 12 | public class InvalidCachingExpressionException : FluentCacheException 13 | { 14 | /// 15 | /// Constructs a new instance 16 | /// 17 | public InvalidCachingExpressionException() { } 18 | 19 | /// 20 | /// Constructs a new instance with the specified message 21 | /// 22 | public InvalidCachingExpressionException(string message) : base(message) { } 23 | 24 | /// 25 | /// Constructs a new instance with the specified message and inner exception 26 | /// 27 | public InvalidCachingExpressionException(string message, Exception inner) : base(message, inner) { } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FluentCache/CachedValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache 9 | { 10 | /// 11 | /// Defines a cached value 12 | /// 13 | /// The type of the value 14 | public class CachedValue 15 | { 16 | /// 17 | /// Gets the date the value was first cached 18 | /// 19 | public DateTime CachedDate { get; set; } 20 | 21 | /// 22 | /// Gets the date the cached value was last validated, or null if it has not been validated 23 | /// 24 | public DateTime LastValidatedDate { get; set; } 25 | 26 | /// 27 | /// Gets the version of this cached value 28 | /// 29 | public long Version { get; set; } 30 | 31 | /// 32 | /// Gets the cached value 33 | /// 34 | public T Value { get; set; } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Evan Rash (evanrash@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /FluentCache/CacheExpiration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache 9 | { 10 | /// 11 | /// Defines a policy for how values should be expired 12 | /// 13 | public class CacheExpiration 14 | { 15 | 16 | /// 17 | /// Creates a new default cache policy with no expiration 18 | /// 19 | public CacheExpiration() 20 | : this(null) 21 | { 22 | 23 | } 24 | 25 | /// 26 | /// Creates a cache policy with the specified sliding expiration 27 | /// 28 | public CacheExpiration(TimeSpan? slidingExpiration) 29 | { 30 | _slidingExpiration = slidingExpiration; 31 | } 32 | 33 | private readonly TimeSpan? _slidingExpiration; 34 | 35 | /// 36 | /// Resolves the sliding expiration duration for a cached item 37 | /// 38 | public TimeSpan? SlidingExpiration => _slidingExpiration; 39 | 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /FluentCache.Test/Strategies/ParameterReplacerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using FluentCache.Expressions; 9 | 10 | namespace FluentCache.Test 11 | { 12 | [TestClass] 13 | public class ParameterReplacerTests 14 | { 15 | [TestMethod] 16 | public void TestReplaceArgumentWithParameter() 17 | { 18 | List values = new List { 4, 5, 6 }; 19 | 20 | Expression> expression = t => t.Sum(values); 21 | 22 | Expression, int>> newExpression = ArgumentReplacer.ReplaceFirstArgumentInLambdaWithParameter, int>(expression, "nums"); 23 | 24 | Func, int> newFunc = newExpression.Compile(); 25 | 26 | Assert.AreEqual(Sum(values), newFunc(this, values)); 27 | } 28 | 29 | private int Sum(IList inputs) 30 | { 31 | return inputs.Sum(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FluentCache/ICacheStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// Specifies a caching strategy that is used by a Cache 11 | /// 12 | public interface ICacheStrategy 13 | { 14 | /// 15 | /// Gets the key of this cached value. Should be unique in combination with Region 16 | /// 17 | string Key { get; } 18 | 19 | /// 20 | /// Gets the cache region. Should be unique in combination with Key 21 | /// 22 | string Region { get; } 23 | 24 | /// 25 | /// Resolves the expiration policy for the value that will be cached 26 | /// 27 | CacheExpiration ResolveExpiration(T value); 28 | 29 | /// 30 | /// Validates an existing cached value 31 | /// 32 | CacheValidationResult Validate(CachedValue existingCachedValue); 33 | 34 | /// 35 | /// Retrieves a cached value 36 | /// 37 | T Retrieve(CachedValue existingCachedValue); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | # Declare files that will always have CRLF line endings on checkout. 7 | *.appxmanifest text eol=crlf 8 | *.c text eol=crlf 9 | *.cpp text eol=crlf 10 | *.cs text eol=crlf 11 | *.csproj text eol=crlf 12 | *.css text eol=crlf 13 | *.def text eol=crlf 14 | *.filters text eol=crlf 15 | *.h text eol=crlf 16 | *.htm text eol=crlf 17 | *.html text eol=crlf 18 | *.idl text eol=crlf 19 | *.inf text eol=crlf 20 | *.inx text eol=crlf 21 | *.js text eol=crlf 22 | *.jsproj text eol=crlf 23 | *.rc text eol=crlf 24 | *.rgs text eol=crlf 25 | *.sln text eol=crlf 26 | *.vcxproj text eol=crlf 27 | *.xaml text eol=crlf 28 | 29 | ############################################################################### 30 | # Set default behavior for command prompt diff. 31 | # 32 | # This is need for earlier builds of msysgit that does not have it on by 33 | # default for csharp files. 34 | # Note: This is only used by command line 35 | ############################################################################### 36 | *.cs diff=csharp -------------------------------------------------------------------------------- /FluentCache.Test/Strategies/ClosureTest.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Expressions; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace FluentCache.Test 12 | { 13 | [TestClass] 14 | public class ClosureTest 15 | { 16 | [TestMethod] 17 | public void Test() 18 | { 19 | Func myAction = i => i + 1; 20 | int localVariable = 10; 21 | 22 | int analyzed = (int)GetFirstParameterValue(this, () => myAction(localVariable)); 23 | 24 | Assert.AreEqual(localVariable, analyzed); 25 | } 26 | 27 | public object GetFirstParameterValue(T source, Expression expression) 28 | { 29 | var invocationCall = expression.Body as InvocationExpression; 30 | 31 | ExpressionAnalyzer analyzer = new ExpressionAnalyzer(); 32 | 33 | bool dontIgnore = analyzer.TryProcessParameter(source, invocationCall.Arguments.First(), checkForParameterDoNotCache: true, parameterValue: out object parameterValue); 34 | if (!dontIgnore) 35 | Assert.Fail("Should not have been ignore"); 36 | 37 | return parameterValue; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Abstractions/Distributed/DistributedStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace FluentCache.Microsoft.Extensions.Caching.Distributed 6 | { 7 | internal class DistributedStorage 8 | { 9 | public DateTime CacheDate { get; set; } 10 | public DateTime LastValidatedDate { get; set; } 11 | public long Version { get; set; } 12 | public string Value { get; set; } 13 | 14 | public CachedValue ToCachedValue(ISerializer serializer) 15 | { 16 | return new CachedValue 17 | { 18 | CachedDate = CacheDate, 19 | LastValidatedDate = LastValidatedDate, 20 | Value = serializer.Deserialize(Value), 21 | Version = Version 22 | }; 23 | } 24 | 25 | public byte[] ToBytes(ISerializer serializer) 26 | { 27 | string serializedValue = serializer.Serialize(this); 28 | return Encoding.UTF8.GetBytes(serializedValue); 29 | } 30 | 31 | public static DistributedStorage FromBytes(ISerializer serializer, byte[] bytes) 32 | { 33 | string serializedValue = Encoding.UTF8.GetString(bytes); 34 | return serializer.Deserialize(serializedValue); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Memory/FluentMemoryCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.Extensions.Caching.Memory; 5 | 6 | namespace FluentCache.Microsoft.Extensions.Caching.Memory 7 | { 8 | /// 9 | /// Provides a FluentCache.ICache wrapper around Microsoft.Extensions.Caching.Memory.MemoryCache 10 | /// 11 | public class FluentMemoryCache : FluentIMemoryCache 12 | { 13 | private static MemoryCache CreateCache(MemoryCacheOptions options = null) 14 | { 15 | return new MemoryCache(options ?? new MemoryCacheOptions()); 16 | } 17 | 18 | /// 19 | /// Construct a new FluentMemoryCache using the default MemoryCache options 20 | /// 21 | public FluentMemoryCache() 22 | : this(CreateCache()) 23 | { 24 | 25 | } 26 | 27 | /// 28 | /// Construct a new FluentMemoryCache using the specified memory cache options 29 | /// 30 | public FluentMemoryCache(MemoryCacheOptions options) 31 | : this(CreateCache(options)) 32 | { 33 | 34 | } 35 | 36 | 37 | /// 38 | /// Construct a new FluentMemoryCache from the specified MemoryCache 39 | /// 40 | public FluentMemoryCache(MemoryCache memoryCache) : base(memoryCache) 41 | { 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FluentCache/FluentCache.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.1 5 | 4.0.0.2 6 | Evan Rash 7 | 8 | https://github.com/cordialgerm/FluentCache/blob/master/LICENSE 9 | https://github.com/cordialgerm/FluentCache 10 | https://github.com/cordialgerm/FluentCache 11 | GitHub 12 | Cache Fluent 13 | true 14 | FluentCache is a simple, fluent library to help you write clean, legible caching code by reducing boilerplate. 15 | https://raw.githubusercontent.com/cordialgerm/FluentCache/master/FluentCache.png 16 | true 17 | signature.pfx 18 | 4.0.0.2 19 | 4.0.0.2 20 | 21 | 22 | 23 | 4 24 | true 25 | 26 | 27 | bin\Debug\netstandard1.2\FluentCache.xml 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /FluentCache.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FluentCache.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("FluentCache.Test")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3e18bd61-abe5-43a5-8703-bcb83c097ecb")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /FluentCache/Strategies/BulkMethodCacheStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | 10 | 11 | internal class BulkMethodCacheStrategy : BulkCacheStrategyIncomplete 12 | { 13 | internal BulkMethodCacheStrategy(ICache cache, Func, ICollection>> bulkGetMethod, string baseKey, ICollection keys) 14 | : base(cache, baseKey, keys) 15 | { 16 | Method = bulkGetMethod; 17 | } 18 | 19 | private readonly Func, ICollection>> Method; 20 | 21 | public BulkCacheStrategy RetrieveUsingMethod() 22 | { 23 | return this.RetrieveUsing(Method); 24 | } 25 | } 26 | 27 | internal class AsyncBulkMethodCacheStrategy : BulkCacheStrategyIncomplete 28 | { 29 | internal AsyncBulkMethodCacheStrategy(ICache cache, Func, Task>>> bulkGetMethod, string baseKey, ICollection keys) 30 | : base(cache, baseKey, keys) 31 | { 32 | Method = bulkGetMethod; 33 | } 34 | 35 | private readonly Func, Task>>> Method; 36 | 37 | public BulkCacheStrategyAsync RetrieveUsingMethod() 38 | { 39 | return this.RetrieveUsingAsync(Method); 40 | } 41 | } 42 | 43 | 44 | 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /FluentCache/FluentCacheException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// The base for exceptions thrown by the FluentCache APIs 11 | /// 12 | public class FluentCacheException : Exception 13 | { 14 | /// 15 | /// Constructs a new instance with the specified message, operation, and inner exception 16 | /// 17 | public FluentCacheException(string message, CacheOperation operation, Exception innerException) : base(message, innerException) 18 | { 19 | Operation = operation; 20 | } 21 | 22 | /// 23 | /// Constructs a new instance 24 | /// 25 | public FluentCacheException() 26 | : base() 27 | { 28 | } 29 | 30 | /// 31 | /// Constructs a new instance with the specified message 32 | /// 33 | public FluentCacheException(string message) 34 | : base(message) 35 | { 36 | } 37 | 38 | /// 39 | /// Constructs a new instance with the specified message and inner exception 40 | /// 41 | public FluentCacheException(string message, Exception innerException) 42 | : base(message, innerException) 43 | { 44 | } 45 | 46 | 47 | /// 48 | /// Specifies which caching operation failed 49 | /// 50 | public CacheOperation? Operation { get; set; } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Redis/FluentRedisCache.cs: -------------------------------------------------------------------------------- 1 | using FluentCache.Microsoft.Extensions.Caching.Distributed; 2 | using Microsoft.Extensions.Caching.Distributed; 3 | using Microsoft.Extensions.Caching.Redis; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace FluentCache.Microsoft.Extensions.Caching.Redis 9 | { 10 | /// 11 | /// A FluentCache.ICache wrapper using Redis 12 | /// 13 | public class FluentRedisCache : FluentIDistributedCache 14 | { 15 | private static IDistributedCache CreateCache(string instance, string configuration) 16 | { 17 | var options = new RedisCacheOptions 18 | { 19 | Configuration = configuration, 20 | InstanceName = instance 21 | }; 22 | return new RedisCache(options); 23 | } 24 | 25 | private static ISerializer CreateSerializer() 26 | { 27 | return new JsonSerializer(); 28 | } 29 | 30 | /// 31 | /// Constructs a FluentRedisCache for the specified instance 32 | /// 33 | public FluentRedisCache(string instance, string configuration) 34 | : this(instance, configuration, CreateSerializer()) 35 | { 36 | 37 | } 38 | 39 | /// 40 | /// Constructs a FluentRedisCache for the specified instance using the specified serializer 41 | /// 42 | public FluentRedisCache(string instance, string configuration, ISerializer serializer) 43 | : base(CreateCache(instance, configuration), serializer) 44 | { 45 | 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FluentCache/ParameterCacheKeys.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache 9 | { 10 | /// 11 | /// Default methods for generating cache keys for parameters 12 | /// 13 | public static class ParameterCacheKeys 14 | { 15 | /// 16 | /// Generates a cache key for a parameter value. The default implementation uses parameter.ToString() 17 | /// 18 | public static string GenerateCacheKey(object parameter) 19 | { 20 | if (parameter == null) 21 | return String.Empty; 22 | 23 | else if (parameter is string) 24 | return (string)parameter; 25 | else if (parameter is IEnumerable) 26 | return GenerateCacheKey(parameter as IEnumerable); 27 | else 28 | return parameter.ToString(); 29 | } 30 | 31 | /// 32 | /// Generates a cache key for an enumerable parameter value 33 | /// 34 | public static string GenerateCacheKey(IEnumerable parameter) 35 | { 36 | if (parameter == null) 37 | return String.Empty; 38 | else 39 | return GenerateCacheKey(parameter.Cast()); 40 | } 41 | 42 | /// 43 | /// Generates a cache key for an enumerable parameter value 44 | /// 45 | public static string GenerateCacheKey(IEnumerable parameter) 46 | { 47 | if (parameter == null) 48 | return String.Empty; 49 | else 50 | return "[" + String.Join(",", parameter) + "]"; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /FluentCache/Expressions/ArgumentReplacer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Reflection; 8 | 9 | namespace FluentCache.Expressions 10 | { 11 | internal static class ArgumentReplacer 12 | { 13 | public static Expression> ReplaceFirstArgumentInLambdaWithParameter(Expression> lambda, string paramName) 14 | { 15 | MethodCallExpression method = lambda.Body as MethodCallExpression; 16 | 17 | if (method.Arguments.Count < 1) 18 | throw new InvalidCachingExpressionException(String.Format("Method '{0}' must have at least one argument", lambda.ToString())); 19 | 20 | TypeInfo parameterType = method.Method.GetParameters().First().ParameterType.GetTypeInfo(); 21 | if (!parameterType.IsAssignableFrom(typeof(TArgument).GetTypeInfo())) 22 | throw new InvalidCachingExpressionException(String.Format("Method '{0}' must have first argument of type '{1}'", lambda.ToString(), typeof(TArgument).FullName)); 23 | 24 | ParameterExpression instance = lambda.Parameters.First(); 25 | 26 | ParameterExpression newParameter = Expression.Parameter(typeof(TArgument), paramName); 27 | 28 | List parameters = method.Arguments.Skip(1).ToList(); 29 | parameters.Insert(0, newParameter); 30 | 31 | MethodCallExpression newMethod = Expression.Call(instance, method.Method, parameters); 32 | 33 | Expression> newExpression = Expression.Lambda>(newMethod, instance, newParameter); 34 | 35 | return newExpression; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FluentCache/RetrievalErrorHandlerResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// Defines the result of a retrieval error handler 11 | /// 12 | /// 13 | public class RetrievalErrorHandlerResult 14 | { 15 | /// 16 | /// Indicates whether the retrieval error was handled 17 | /// 18 | public bool IsErrorHandled { get; set; } 19 | 20 | /// 21 | /// Specifies the fallback value that should be used 22 | /// 23 | public T FallbackResult { get; set; } 24 | 25 | /// 26 | /// A simple retrieval error handler that uses the previous cached value, if it exists 27 | /// 28 | public static RetrievalErrorHandlerResult UsePreviousCachedValue(Exception retrievalException, CachedValue previousValue) 29 | { 30 | if (previousValue != null) 31 | return new RetrievalErrorHandlerResult { FallbackResult = previousValue.Value, IsErrorHandled = true }; 32 | else 33 | return new RetrievalErrorHandlerResult { IsErrorHandled = false }; 34 | } 35 | 36 | /// 37 | /// A simple retrieval error handler that uses the previous cached value if it exists, otherwise uses a default value 38 | /// 39 | public static RetrievalErrorHandlerResult UsePreviousCachedValueOrDefault(Exception retrievalException, CachedValue previousValue, T defaultValue) 40 | { 41 | return new RetrievalErrorHandlerResult { IsErrorHandled = true, FallbackResult = previousValue == null? defaultValue : previousValue.Value }; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FluentCache.RuntimeCaching/FluentCache.RuntimeCaching.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 4.0.0.2 6 | Evan Rash 7 | Evan Rash 8 | FluentCache implementations based on System.Runtime.Caching.MemoryCache 9 | https://github.com/cordialgerm/FluentCache/blob/master/LICENSE 10 | https://github.com/cordialgerm/FluentCache 11 | https://github.com/cordialgerm/FluentCache 12 | GitHub 13 | Cache Fluent System.Runtime.Caching.MemoryCache 14 | true 15 | https://raw.githubusercontent.com/cordialgerm/FluentCache/master/FluentCache.png 16 | true 17 | signature.pfx 18 | 4.0.0.2 19 | 4.0.0.2 20 | 21 | 22 | 23 | 4 24 | 25 | true 26 | 27 | bin\Debug\netstandard2.0\FluentCache.RuntimeCaching.xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Runtime.Caching.dll 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Abstractions/FluentCache.Microsoft.Extensions.Caching.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 4.0.0.2 6 | Evan Rash 7 | Evan Rash 8 | FluentCache implementations based on Microsoft.Extensions.Caching.Abstractions interfaces (IMemoryCache and IDistributedCache) 9 | https://github.com/cordialgerm/FluentCache/blob/master/LICENSE 10 | https://github.com/cordialgerm/FluentCache 11 | https://github.com/cordialgerm/FluentCache 12 | GitHub 13 | Cache Fluent Microsoft.Extensions.Caching IMemoryCache IDistributedCache 14 | true 15 | FluentCache 16 | https://raw.githubusercontent.com/cordialgerm/FluentCache/master/FluentCache.png 17 | true 18 | signature.pfx 19 | 4.0.0.2 20 | 4.0.0.2 21 | 22 | 23 | 24 | 4 25 | 26 | true 27 | 28 | bin\Debug\netstandard2.0\FluentCache.Microsoft.Extensions.Caching.xml 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Memory/FluentCache.Microsoft.Extensions.Caching.Memory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 4.0.0.2 6 | true 7 | Evan Rash 8 | Evan Rash 9 | FluentCache 10 | FluentCache implementations based on Microsoft.Extensions.Caching.Memory 11 | https://github.com/cordialgerm/FluentCache/blob/master/LICENSE 12 | https://github.com/cordialgerm/FluentCache 13 | https://github.com/cordialgerm/FluentCache 14 | GitHub 15 | Cache Fluent Microsoft.Extensions.Caching.Memory MemoryCache 16 | https://raw.githubusercontent.com/cordialgerm/FluentCache/master/FluentCache.png 17 | true 18 | signature.pfx 19 | 4.0.0.2 20 | 4.0.0.2 21 | 22 | 23 | 24 | true 25 | 26 | 27 | bin\Debug\netstandard2.0\FluentCache.Microsoft.Extensions.Caching.Memory.xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /FluentCache.Test/Strategies/GenericTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Test 9 | { 10 | [TestClass] 11 | public class GenericTest 12 | { 13 | public class DataItem 14 | { 15 | public string ID { get; set; } 16 | } 17 | 18 | public interface IRepository 19 | { 20 | T Get(string id); 21 | 22 | T Default { get; } 23 | } 24 | 25 | private class DataItemRepository : IRepository 26 | { 27 | public DataItem Default => new DataItem { ID = "Default" }; 28 | 29 | public DataItem Get(string id) 30 | { 31 | return new DataItem { ID = id }; 32 | } 33 | } 34 | 35 | [TestMethod] 36 | public void TestCacheKeyIsNotGeneric_Method() 37 | { 38 | IRepository repository = new DataItemRepository(); 39 | var cache = new Simple.FluentDictionaryCache().WithSource(repository); 40 | 41 | string id = "test"; 42 | var strat = cache.Method(c => c.Get(id)); 43 | 44 | 45 | 46 | Assert.IsFalse(strat.Region.Contains("IRepository`1"), "The region should be based on the concrete type DataItemRepository and not the calling type in the expression IRepository"); 47 | } 48 | 49 | [TestMethod] 50 | public void TestCacheKeyIsNotGeneric_Member_Property() 51 | { 52 | IRepository repository = new DataItemRepository(); 53 | var cache = new Simple.FluentDictionaryCache().WithSource(repository); 54 | var strat = cache.Method(c => c.Default); 55 | Assert.IsFalse(strat.Region.Contains("IRepository`1"), "The region should be based on the concrete type DataItemRepository and not the calling type in the expression IRepository"); 56 | } 57 | 58 | 59 | 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Redis/FluentCache.Microsoft.Extensions.Caching.Redis.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 4.0.0.2 6 | true 7 | Evan Rash 8 | Evan Rash 9 | FluentCache 10 | FluentCache implementation for Microsoft.Extensions.Caching.Redis 11 | https://github.com/cordialgerm/FluentCache/blob/master/LICENSE 12 | https://github.com/cordialgerm/FluentCache 13 | https://github.com/cordialgerm/FluentCache 14 | GitHub 15 | Cache Fluent Redis FluentCache.Microsoft.Extensions.Caching.Redis 16 | https://raw.githubusercontent.com/cordialgerm/FluentCache/master/FluentCache.png 17 | true 18 | signature.pfx 19 | 4.0.0.2 20 | 4.0.0.2 21 | 22 | 23 | 24 | 25 | true 26 | 27 | bin\Debug\netstandard2.0\FluentCache.Microsoft.Extensions.Caching.Redis.xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /FluentCache/ICache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache 9 | { 10 | /// 11 | /// Represents a cache that can be used to store and retrieve values 12 | /// 13 | public interface ICache 14 | { 15 | /// 16 | /// Gets a value from the cache 17 | /// 18 | /// The type of the value 19 | /// The key of the value to retrieve 20 | /// The region in the cache where the key is stored 21 | /// The cached value 22 | CachedValue Get(string key, string region); 23 | 24 | /// 25 | /// Sets a value in the cache 26 | /// 27 | /// The type of the value 28 | /// The key of the value to retrieve 29 | /// The region in the cache where the key is stored 30 | /// The cached value 31 | /// The expiration policy for this value 32 | /// The cached value 33 | CachedValue Set(string key, string region, T value, CacheExpiration cacheExpiration); 34 | 35 | /// 36 | /// Removes a value from the cache 37 | /// 38 | void Remove(string key, string region); 39 | 40 | /// 41 | /// Marks a value in the cache as validated 42 | /// 43 | void MarkAsValidated(string key, string region); 44 | 45 | /// 46 | /// Gets a string representation of a parameter value that is used to build up a unique cache key for a parametized caching expression 47 | /// 48 | string GetParameterCacheKeyValue(object parameterValue); 49 | 50 | /// 51 | /// Creates an execution plan for the specified caching strategy 52 | /// 53 | Execution.ICacheExecutionPlan CreateExecutionPlan(ICacheStrategy cacheStrategy); 54 | } 55 | 56 | 57 | } -------------------------------------------------------------------------------- /FluentCache/Strategies/CacheStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Strategies 9 | { 10 | /// 11 | /// A strategy to access or update data in a cache 12 | /// 13 | public class CacheStrategy 14 | { 15 | internal CacheStrategy(ICache cache, string baseKey) 16 | { 17 | _Cache = cache; 18 | _BaseKey = baseKey; 19 | Parameters = new List(); 20 | } 21 | 22 | private readonly ICache _Cache; 23 | private readonly string _BaseKey; 24 | 25 | internal ICache Cache { get { return _Cache; } } 26 | internal string BaseKey { get { return _BaseKey; } } 27 | 28 | internal IReadOnlyList Parameters { get; set; } 29 | 30 | internal P GetParameter

(int index) 31 | { 32 | return (P)Parameters[index]; 33 | } 34 | 35 | internal virtual void CopyFrom(CacheStrategy other) 36 | { 37 | this.DefaultExpiration = other.DefaultExpiration; 38 | this.Region = other.Region; 39 | this.Parameters = other.Parameters?.ToList(); 40 | } 41 | 42 | ///

43 | /// Gets the Region that this caching policy will use when caching items 44 | /// 45 | public string Region { get; internal set; } 46 | 47 | /// 48 | /// Gets the Expiration that this caching policy will use when caching items 49 | /// 50 | public CacheExpiration DefaultExpiration { get; internal set; } 51 | 52 | /// 53 | /// Gets the ExpirationCallback that this caching policy will use when caching items 54 | /// 55 | public Func ExpirationCallback { get; internal set; } 56 | 57 | internal CacheExpiration ResolveExpiration(object result) 58 | { 59 | return ExpirationCallback?.Invoke(result) ?? DefaultExpiration; 60 | } 61 | 62 | /// 63 | /// Gets the Key that this caching policy will using when caching items 64 | /// 65 | public string Key 66 | { 67 | get 68 | { 69 | if (Parameters == null || !Parameters.Any()) 70 | { 71 | return String.Format("{0}", BaseKey); 72 | } 73 | else 74 | { 75 | return String.Format("{0}.{1}", BaseKey, String.Join(".", Parameters.Select(p => Cache.GetParameterCacheKeyValue(p)))); 76 | } 77 | } 78 | } 79 | 80 | 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /FluentCache/Strategies/MethodCacheStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | /// 10 | /// Defines a caching strategy for a particular method 11 | /// 12 | internal class MethodCacheStrategy : CacheStrategyIncomplete 13 | { 14 | internal MethodCacheStrategy(ICache cache, Func method, string baseKey) 15 | : base(cache, baseKey) 16 | { 17 | Method = method; 18 | } 19 | 20 | private readonly Func Method; 21 | 22 | /// 23 | /// Indicates that the method used to create the caching strategy should also be used to retrieve the value if it is missing or invalid 24 | /// 25 | /// A caching strategy that can be used to access the value 26 | public CacheStrategy RetrieveUsingMethod() 27 | { 28 | return this.RetrieveUsing(Method); 29 | } 30 | 31 | /// 32 | /// Sets the cached value 33 | /// 34 | /// The value to be cached 35 | public void SetValue(TResult value) 36 | { 37 | Cache.Set(Key, Region, value, ResolveExpiration(value)); 38 | } 39 | 40 | /// 41 | /// Gets the cached value 42 | /// 43 | /// The cached value 44 | public TResult GetValue() 45 | { 46 | return base.GetValue(); 47 | } 48 | } 49 | 50 | /// 51 | /// Defines an asynchronous caching strategy for a particular method 52 | /// 53 | public class AsyncMethodCacheStrategy : CacheStrategyIncomplete 54 | { 55 | internal AsyncMethodCacheStrategy(ICache cache, Func> method, string baseKey) 56 | : base(cache, baseKey) 57 | { 58 | Method = method; 59 | } 60 | 61 | private readonly Func> Method; 62 | 63 | /// 64 | /// Indicates that the method used to create the caching strategy should also be used to retrieve the value if it is missing or invalid 65 | /// 66 | /// A caching strategy that can be used to access the value 67 | public CacheStrategyAsync RetrieveUsingMethod() 68 | { 69 | return this.RetrieveUsingAsync(Method); 70 | } 71 | 72 | /// 73 | /// Sets the cached value 74 | /// 75 | /// The value to be cached 76 | public void SetValue(TResult value) 77 | { 78 | Cache.Set(Key, Region, value, ResolveExpiration(value)); 79 | } 80 | 81 | /// 82 | /// Gets the cached value 83 | /// 84 | /// The cached value 85 | public TResult GetValue() 86 | { 87 | return base.GetValue(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /FluentCache/Strategies/BulkCacheStrategyIncomplete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | /// 10 | /// An incomplete strategy to access or modify a batch of strongly-typed data in a Cache 11 | /// 12 | public class BulkCacheStrategyIncomplete : CacheStrategy 13 | { 14 | internal BulkCacheStrategyIncomplete(ICache cache, string baseKey, ICollection keys) 15 | : base(cache, baseKey) 16 | { 17 | Keys = keys.ToList(); 18 | } 19 | 20 | internal readonly ICollection Keys; 21 | 22 | internal string GetItemKey(TKey key) 23 | { 24 | return String.Format("{0}&itemkey={1}", Key, key); 25 | } 26 | 27 | /// 28 | /// Updates the cache strategy to include parameters 29 | /// 30 | public BulkCacheStrategyIncomplete WithParameters(params object[] parameters) 31 | { 32 | this.Parameters = (parameters == null || !parameters.Any()) ? null : parameters.ToList(); 33 | return this; 34 | } 35 | 36 | /// 37 | /// Updates the cache strategy to use the specified method to retrieve missing or invalid items in the batch 38 | /// 39 | public BulkCacheStrategy RetrieveUsing(Func, ICollection>> retrieve) 40 | { 41 | var strat = new BulkCacheStrategy(Cache, BaseKey, Keys); 42 | strat.CopyFrom(this); 43 | 44 | strat.RetrieveCallback = retrieve; 45 | return strat; 46 | } 47 | 48 | /// 49 | /// Updates the cache strategy to use the specified async method to retrieve missing or invalid items in the batch 50 | /// 51 | public BulkCacheStrategyAsync RetrieveUsingAsync(Func, Task>>> retrieve) 52 | { 53 | var strat = new BulkCacheStrategyAsync(Cache, BaseKey, Keys); 54 | strat.CopyFrom(this); 55 | 56 | strat.RetrieveCallback = retrieve; 57 | return strat; 58 | } 59 | 60 | /// 61 | /// Clears all values in the batch 62 | /// 63 | public void ClearValues() 64 | { 65 | foreach (TKey key in Keys) 66 | { 67 | string itemKey = GetItemKey(key); 68 | Cache.Remove(itemKey, Region); 69 | } 70 | } 71 | 72 | /// 73 | /// Sets the specified value in the cache 74 | /// 75 | public void SetValue(TKey key, TResult value) 76 | { 77 | string itemKey = GetItemKey(key); 78 | Cache.Set(itemKey, Region, value, DefaultExpiration); 79 | } 80 | 81 | /// 82 | /// Sets the specified values in the cache 83 | /// 84 | public void SetValues(ICollection> values) 85 | { 86 | foreach (var kvp in values) 87 | SetValue(kvp.Key, kvp.Value); 88 | } 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /FluentCache/Cache_Generic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache 8 | { 9 | /// 10 | /// A cache for a particular source whose methods you want to cache. Various Fluent extension methods simplify the process 11 | /// 12 | /// 13 | public sealed class Cache : ICache 14 | { 15 | /// 16 | /// Constructs a new instance 17 | /// 18 | public Cache(TSource source, ICache cache) 19 | { 20 | _source = source; 21 | _cache = cache; 22 | } 23 | private readonly ICache _cache; 24 | private readonly TSource _source; 25 | 26 | /// 27 | /// Gets the source whose methods you want to cache 28 | /// 29 | public TSource Source { get { return _source; } } 30 | 31 | /// 32 | /// Gets a value from the cache 33 | /// 34 | /// The type of the value 35 | /// The key of the value to retrieve 36 | /// The region in the cache where the key is stored 37 | /// The cached value 38 | public CachedValue Get(string key, string region) 39 | { 40 | return _cache.Get(key, region); 41 | } 42 | 43 | /// 44 | /// Sets a value in the cache 45 | /// 46 | /// The type of the value 47 | /// The key of the value to retrieve 48 | /// The region in the cache where the key is stored 49 | /// The cached value 50 | /// The expiration policy for this value 51 | /// The cached value 52 | public CachedValue Set(string key, string region, T value, CacheExpiration cacheExpiration) 53 | { 54 | return _cache.Set(key, region, value, cacheExpiration); 55 | } 56 | 57 | /// 58 | /// Removes a value from the cache 59 | /// 60 | public void Remove(string key, string region) 61 | { 62 | _cache.Remove(key, region); 63 | } 64 | 65 | /// 66 | /// Gets a string representation of a parameter value that is used to build up a unique cache key for a parametized caching expression. The default implementation is parameterValue.ToString() 67 | /// 68 | public string GetParameterCacheKeyValue(object parameterValue) 69 | { 70 | return _cache.GetParameterCacheKeyValue(parameterValue); 71 | } 72 | 73 | /// 74 | /// Marks a value in the cache as validated 75 | /// 76 | public void MarkAsValidated(string key, string region) 77 | { 78 | _cache.MarkAsValidated(key, region); 79 | } 80 | 81 | /// 82 | /// Creates an execution plan for retrieving the cached value 83 | /// 84 | public Execution.ICacheExecutionPlan CreateExecutionPlan(ICacheStrategy cacheStrategy) 85 | { 86 | return _cache.CreateExecutionPlan(cacheStrategy); 87 | } 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /FluentCache/Strategies/BulkCacheStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | /// 10 | /// A strategy to asynchronously access or modify strongly-typed data in a Cache 11 | /// 12 | public class BulkCacheStrategy : BulkCacheStrategyIncomplete 13 | { 14 | internal BulkCacheStrategy(ICache cache, string baseKey, ICollection keys) 15 | : base(cache, baseKey, keys) 16 | { 17 | } 18 | 19 | internal Func, CacheValidationResult> ValidateCallback { get; set; } 20 | internal Func, ICollection>> RetrieveCallback { get; set; } 21 | 22 | /// 23 | /// Invalidates the cached value if the specified validation delegate returns CacheValidationResult.Invalid 24 | /// 25 | /// A delegate that validates the cached value 26 | /// An updated cache strategy that includes the invalidation strategy 27 | public BulkCacheStrategy Validate(Func, CacheValidationResult> validate) 28 | { 29 | this.ValidateCallback = validate; 30 | return this; 31 | } 32 | 33 | /// 34 | /// Invalidates the cached value if the specified validation delegate returns false 35 | /// 36 | /// A delegate that validates the cached value 37 | /// An updated cache strategy that includes the invalidation strategy 38 | public BulkCacheStrategy InvalidateIf(Func, bool> validate) 39 | { 40 | Func, CacheValidationResult> val = existing => validate(existing) ? CacheValidationResult.Valid : CacheValidationResult.Invalid; 41 | this.ValidateCallback = val; 42 | return this; 43 | } 44 | 45 | /// 46 | /// Gets all cached items 47 | /// 48 | public IList> GetAll() 49 | { 50 | var keysToLoad = Keys.ToList(); 51 | var results = new List>(Keys.Count); 52 | 53 | foreach (TKey key in Keys) 54 | { 55 | string itemKey = GetItemKey(key); 56 | CacheStrategy itemStrategy = new CacheStrategy(Cache, itemKey).WithRegion(Region); 57 | 58 | if (ValidateCallback != null) 59 | itemStrategy = itemStrategy.Validate(ValidateCallback); 60 | 61 | CachedValue cachedValue = itemStrategy.Get(); 62 | if (cachedValue != null) 63 | { 64 | keysToLoad.Remove(key); 65 | results.Add(cachedValue); 66 | } 67 | } 68 | 69 | if (RetrieveCallback != null) 70 | { 71 | ICollection> newResults = RetrieveCallback(keysToLoad); 72 | 73 | foreach (KeyValuePair result in newResults) 74 | { 75 | string itemKey = GetItemKey(result.Key); 76 | TResult value = result.Value; 77 | 78 | CachedValue cachedValue = Cache.Set(itemKey, Region, value, DefaultExpiration); 79 | 80 | results.Add(cachedValue); 81 | } 82 | } 83 | 84 | return results; 85 | } 86 | 87 | /// 88 | /// Gets all cached values 89 | /// 90 | public IList GetAllValues() 91 | { 92 | IList> results = GetAll(); 93 | return results.Select(s => s.Value) 94 | .ToList(); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Abstractions/Memory/FluentIMemoryCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using FluentCache.Execution; 5 | using Microsoft.Extensions.Caching.Memory; 6 | 7 | namespace FluentCache.Microsoft.Extensions.Caching.Memory 8 | { 9 | /// 10 | /// Provides a FluentCache.ICache wrapper around Microsoft.Extensions.Caching.Memory.IMemoryCache 11 | /// 12 | public class FluentIMemoryCache : ICache 13 | { 14 | /// 15 | /// Constructs a FluentMemoryCache wrapper around IMemoryCache 16 | /// 17 | public FluentIMemoryCache(IMemoryCache memoryCache) 18 | { 19 | MemoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache)); 20 | } 21 | 22 | private readonly IMemoryCache MemoryCache; 23 | 24 | 25 | /// 26 | /// Gets the specified cached value 27 | /// 28 | public CachedValue Get(string key, string region) 29 | { 30 | string k = GetCacheKey(key, region); 31 | MemoryStorage storage = MemoryCache.Get(k) as MemoryStorage; 32 | return storage?.ToCachedValue(); 33 | } 34 | 35 | /// 36 | /// Sets the specified cached value 37 | /// 38 | public CachedValue Set(string key, string region, T value, CacheExpiration cacheExpiration) 39 | { 40 | DateTime now = DateTime.UtcNow; 41 | string k = GetCacheKey(key, region); 42 | if (MemoryCache.Get(k) is MemoryStorage storage) 43 | { 44 | storage.Version++; 45 | storage.LastValidatedDate = now; 46 | storage.CacheDate = now; 47 | storage.Value = value; 48 | } 49 | else 50 | { 51 | storage = new MemoryStorage 52 | { 53 | CacheDate = now, 54 | LastValidatedDate = now, 55 | Version = 0L, 56 | Value = value 57 | }; 58 | 59 | var options = new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheExpiration?.SlidingExpiration }; 60 | MemoryCache.Set(k, storage, options); 61 | } 62 | 63 | return storage.ToCachedValue(); 64 | } 65 | 66 | /// 67 | /// Removes the specified cached value 68 | /// 69 | public void Remove(string key, string region) 70 | { 71 | string k = GetCacheKey(key, region); 72 | MemoryCache.Remove(k); 73 | } 74 | 75 | /// 76 | /// Marks the specified cached value as validated 77 | /// 78 | public void MarkAsValidated(string key, string region) 79 | { 80 | var now = DateTime.UtcNow; 81 | string k = GetCacheKey(key, region); 82 | if (MemoryCache.Get(k) is MemoryStorage storage) 83 | storage.LastValidatedDate = now; 84 | } 85 | 86 | /// 87 | /// Generates a unique key for the parameter value 88 | /// 89 | public virtual string GetParameterCacheKeyValue(object parameterValue) 90 | { 91 | return ParameterCacheKeys.GenerateCacheKey(parameterValue); 92 | } 93 | 94 | /// 95 | /// Creates an execution plan for retrieving a cached value 96 | /// 97 | public virtual ICacheExecutionPlan CreateExecutionPlan(ICacheStrategy cacheStrategy) 98 | { 99 | return new CacheExecutionPlan(this, cacheStrategy); 100 | } 101 | 102 | private string GetCacheKey(string key, string region) 103 | { 104 | return region + ":" + key; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | # TODO: Comment the next line if you want to checkin your web deploy settings 137 | # but database connection strings (with potential passwords) will be unencrypted 138 | *.pubxml 139 | *.publishproj 140 | 141 | # NuGet Packages 142 | *.nupkg 143 | # The packages folder can be ignored because of Package Restore 144 | **/packages/* 145 | # except build/, which is used as an MSBuild target. 146 | !**/packages/build/ 147 | # Uncomment if necessary however generally it will be regenerated when needed 148 | #!**/packages/repositories.config 149 | 150 | # Windows Azure Build Output 151 | csx/ 152 | *.build.csdef 153 | 154 | # Windows Store app package directory 155 | AppPackages/ 156 | 157 | # Visual Studio cache files 158 | # files ending in .cache can be ignored 159 | *.[Cc]ache 160 | # but keep track of directories ending in .cache 161 | !*.[Cc]ache/ 162 | 163 | # Others 164 | ClientBin/ 165 | [Ss]tyle[Cc]op.* 166 | ~$* 167 | *~ 168 | *.dbmdl 169 | *.dbproj.schemaview 170 | *.pfx 171 | *.publishsettings 172 | node_modules/ 173 | bower_components/ 174 | orleans.codegen.cs 175 | 176 | # RIA/Silverlight projects 177 | Generated_Code/ 178 | 179 | # Backup & report files from converting an old project file 180 | # to a newer Visual Studio version. Backup files are not needed, 181 | # because we have git ;-) 182 | _UpgradeReport_Files/ 183 | Backup*/ 184 | UpgradeLog*.XML 185 | UpgradeLog*.htm 186 | 187 | # SQL Server files 188 | *.mdf 189 | *.ldf 190 | 191 | # Business Intelligence projects 192 | *.rdl.data 193 | *.bim.layout 194 | *.bim_*.settings 195 | 196 | # Microsoft Fakes 197 | FakesAssemblies/ 198 | 199 | # Node.js Tools for Visual Studio 200 | .ntvs_analysis.dat 201 | 202 | # Visual Studio 6 build log 203 | *.plg 204 | 205 | # Visual Studio 6 workspace options file 206 | *.opt 207 | 208 | #ignore local nuget files 209 | FluentCache-nuget/lib 210 | FluentCache.Redis-nuget/lib 211 | -------------------------------------------------------------------------------- /FluentCache.Microsoft.Extensions.Caching.Abstractions/Distributed/FluentIDistributedCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using FluentCache.Execution; 5 | using Microsoft.Extensions.Caching.Distributed; 6 | 7 | namespace FluentCache.Microsoft.Extensions.Caching.Distributed 8 | { 9 | /// 10 | /// Provides a FluentCache.ICache wrapper around the Microsoft.Extensions.Caching.Distributed.IDistributedCache 11 | /// 12 | public class FluentIDistributedCache : ICache 13 | { 14 | /// 15 | /// Constructs a new FluentDistributedCache wrapper around IDistributedCache 16 | /// 17 | public FluentIDistributedCache(IDistributedCache distributedCache, ISerializer serializer) 18 | { 19 | DistributedCache = distributedCache ?? throw new ArgumentNullException(nameof(distributedCache)); 20 | Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); 21 | } 22 | 23 | private readonly IDistributedCache DistributedCache; 24 | private readonly ISerializer Serializer; 25 | 26 | /// 27 | /// Gets the specified cached value 28 | /// 29 | public CachedValue Get(string key, string region) 30 | { 31 | string k = GetCacheKey(key, region); 32 | byte[] bytes = DistributedCache.Get(k); 33 | if (bytes == null) 34 | return null; 35 | 36 | var storage = DistributedStorage.FromBytes(Serializer, bytes); 37 | return storage.ToCachedValue(Serializer); 38 | } 39 | 40 | /// 41 | /// Sets the specified cached value 42 | /// 43 | 44 | public CachedValue Set(string key, string region, T value, CacheExpiration cacheExpiration) 45 | { 46 | var now = DateTime.UtcNow; 47 | string k = GetCacheKey(key, region); 48 | 49 | DistributedStorage storage = null; 50 | string serializedValue = Serializer.Serialize(value); 51 | byte[] bytes = DistributedCache.Get(k); 52 | 53 | if (bytes == null) 54 | { 55 | storage = new DistributedStorage 56 | { 57 | CacheDate = now, 58 | Version = 0L, 59 | Value = serializedValue, 60 | LastValidatedDate = now 61 | }; 62 | } 63 | else 64 | { 65 | storage = DistributedStorage.FromBytes(Serializer, bytes); 66 | storage.Version++; 67 | storage.LastValidatedDate = now; 68 | storage.Value = serializedValue; 69 | } 70 | 71 | var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheExpiration?.SlidingExpiration }; 72 | DistributedCache.Set(k, storage.ToBytes(Serializer), options); 73 | 74 | return storage.ToCachedValue(Serializer); 75 | } 76 | 77 | /// 78 | /// Removes the specified cached value 79 | /// 80 | public void Remove(string key, string region) 81 | { 82 | string k = GetCacheKey(key, region); 83 | DistributedCache.Remove(k); 84 | } 85 | 86 | /// 87 | /// Marks the specified cached value as modified 88 | /// 89 | public void MarkAsValidated(string key, string region) 90 | { 91 | var now = DateTime.UtcNow; 92 | string k = GetCacheKey(key, region); 93 | byte[] bytes = DistributedCache.Get(k); 94 | if (bytes == null) 95 | return; 96 | 97 | var storage = DistributedStorage.FromBytes(Serializer, bytes); 98 | storage.LastValidatedDate = now; 99 | DistributedCache.Set(k, bytes); 100 | } 101 | 102 | 103 | private string GetCacheKey(string key, string region) 104 | { 105 | return region + ":" + key; 106 | } 107 | 108 | /// 109 | /// Generates a unique key for the parameter value 110 | /// 111 | public virtual string GetParameterCacheKeyValue(object parameterValue) 112 | { 113 | return ParameterCacheKeys.GenerateCacheKey(parameterValue); 114 | } 115 | 116 | /// 117 | /// Creates an execution plan for retrieving a cached value 118 | /// 119 | public virtual ICacheExecutionPlan CreateExecutionPlan(ICacheStrategy cacheStrategy) 120 | { 121 | return new CacheExecutionPlan(this, cacheStrategy); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /FluentCache.Test/CacheFailureTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Test 9 | { 10 | [TestClass] 11 | public class CacheFailureTests 12 | { 13 | private class CacheMe 14 | { 15 | public CacheMe() 16 | { 17 | Random = new Random(0); 18 | } 19 | 20 | private readonly Random Random; 21 | 22 | public double DoWork() 23 | { 24 | return Random.NextDouble(); 25 | } 26 | 27 | public async Task DoWorkAsync() 28 | { 29 | await Task.Delay(TimeSpan.FromSeconds(.125)); 30 | return DoWork(); 31 | } 32 | } 33 | 34 | private Cache CreateHandledCache(params CacheOperation[] failingOperations) 35 | { 36 | return new SimpleFailingCache(true, failingOperations).WithSource(new CacheMe()); 37 | } 38 | 39 | private ICache CreateUnhandledCache(params CacheOperation[] failingOperations) 40 | { 41 | return new SimpleFailingCache(false, failingOperations).WithSource(new CacheMe()); 42 | } 43 | 44 | [TestMethod] 45 | public void CacheFailureOnGet_Handled() 46 | { 47 | ICache cache = CreateHandledCache(CacheOperation.Get); 48 | 49 | double result = cache.Method(m => m.DoWork()) 50 | .GetValue(); 51 | 52 | Assert.AreNotEqual(default(double), result, "Even though there was an error, we should have handled it"); 53 | } 54 | 55 | [TestMethod, ExpectedException(typeof(FluentCacheException))] 56 | public void CacheFailureOnGet_Unhandled() 57 | { 58 | ICache cache = CreateUnhandledCache(CacheOperation.Get); 59 | 60 | double result = cache.Method(m => m.DoWork()) 61 | .GetValue(); 62 | 63 | } 64 | 65 | [TestMethod] 66 | public async Task CacheFailureOnGet_Async_Handled() 67 | { 68 | ICache cache = CreateHandledCache(CacheOperation.Get); 69 | 70 | double result = await cache.Method(m => m.DoWorkAsync()) 71 | .GetValueAsync(); 72 | 73 | Assert.AreNotEqual(default(double), result, "Even though there was an error, we should have handled it"); 74 | } 75 | 76 | [TestMethod, ExpectedException(typeof(FluentCacheException))] 77 | public async Task CacheFailureOnGet_Async_Unhandled() 78 | { 79 | ICache cache = CreateUnhandledCache(CacheOperation.Get); 80 | 81 | double result = await cache.Method(m => m.DoWorkAsync()) 82 | .GetValueAsync(); 83 | 84 | } 85 | 86 | [TestMethod] 87 | public void CacheFailureOnSet_Handled() 88 | { 89 | ICache cache = CreateHandledCache(CacheOperation.Set); 90 | 91 | CacheStrategy cacheStrategy = cache.Method(m => m.DoWork()); 92 | 93 | ICachedValue result1 = cacheStrategy.Get(); 94 | ICachedValue result2 = cacheStrategy.Get(); 95 | 96 | Assert.AreNotEqual(result1.Value, result2.Value); 97 | } 98 | 99 | [TestMethod, ExpectedException(typeof(FluentCacheException))] 100 | public void CacheFailureOnSet_Unhandled() 101 | { 102 | ICache cache = CreateUnhandledCache(CacheOperation.Set); 103 | 104 | CacheStrategy cacheStrategy = cache.Method(m => m.DoWork()); 105 | 106 | ICachedValue result = cacheStrategy.Get(); 107 | } 108 | 109 | [TestMethod] 110 | public async Task CacheFailureOnSet_Async_Handled() 111 | { 112 | ICache cache = CreateHandledCache(CacheOperation.Set); 113 | 114 | CacheStrategyAsync cacheStrategy = cache.Method(m => m.DoWorkAsync()); 115 | 116 | ICachedValue result1 = await cacheStrategy.GetAsync(); 117 | ICachedValue result2 = await cacheStrategy.GetAsync(); 118 | 119 | Assert.AreNotEqual(result1.Value, result2.Value); 120 | } 121 | 122 | [TestMethod, ExpectedException(typeof(FluentCacheException))] 123 | public async Task CacheFailureOnSet_Async_Unhandled() 124 | { 125 | ICache cache = CreateUnhandledCache(CacheOperation.Set); 126 | 127 | CacheStrategyAsync cacheStrategy = cache.Method(m => m.DoWorkAsync()); 128 | 129 | ICachedValue result = await cacheStrategy.GetAsync(); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /FluentCache.RuntimeCaching/FluentMemoryCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Caching; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.RuntimeCaching 9 | { 10 | /// 11 | /// Provides a FluentCache.ICache wrapper around the System.Runtime.Caching.MemoryCache 12 | /// 13 | public class FluentMemoryCache : ICache 14 | { 15 | /// 16 | /// Creates a new FluentMemoryCache wrapper around MemoryCache.Default 17 | /// 18 | /// 19 | public static ICache Default() 20 | { 21 | return new FluentMemoryCache(MemoryCache.Default); 22 | } 23 | 24 | /// 25 | /// Creates a new FluentMemoryCache wrapper around the specified MemoryCache 26 | /// 27 | public FluentMemoryCache(MemoryCache memoryCache) 28 | { 29 | MemoryCache = memoryCache ?? throw new ArgumentNullException("memoryCache"); 30 | } 31 | 32 | private readonly MemoryCache MemoryCache; 33 | 34 | private class Storage 35 | { 36 | public DateTime CacheDate { get; set; } 37 | public DateTime LastValidatedDate { get; set; } 38 | public long Version { get; set; } 39 | public object Value { get; set; } 40 | 41 | public CachedValue ToCachedValue() 42 | { 43 | if (!(Value is T)) 44 | return null; 45 | 46 | return new CachedValue 47 | { 48 | CachedDate = CacheDate, 49 | LastValidatedDate = LastValidatedDate, 50 | Value = (T)Value, 51 | Version = Version 52 | }; 53 | } 54 | 55 | } 56 | 57 | /// 58 | /// Gets the specified cached value 59 | /// 60 | public CachedValue Get(string key, string region) 61 | { 62 | string k = GetCacheKey(key, region); 63 | Storage storage = MemoryCache.Get(k) as Storage; 64 | if (storage == null) 65 | return null; 66 | 67 | return storage.ToCachedValue(); 68 | } 69 | 70 | /// 71 | /// Sets the specified cached value 72 | /// 73 | public CachedValue Set(string key, string region, T value, CacheExpiration cacheExpiration) 74 | { 75 | DateTime now = DateTime.UtcNow; 76 | string k = GetCacheKey(key, region); 77 | if (MemoryCache.Get(k) is Storage storage) 78 | { 79 | storage.Version++; 80 | storage.LastValidatedDate = now; 81 | storage.CacheDate = now; 82 | storage.Value = value; 83 | } 84 | else 85 | { 86 | storage = new Storage 87 | { 88 | CacheDate = now, 89 | LastValidatedDate = now, 90 | Value = value, 91 | Version = 0L 92 | }; 93 | 94 | var cachePolicy = new CacheItemPolicy(); 95 | if (cacheExpiration?.SlidingExpiration != null) 96 | cachePolicy.SlidingExpiration = cacheExpiration.SlidingExpiration.GetValueOrDefault(); 97 | 98 | MemoryCache.Add(k, storage, cachePolicy); 99 | } 100 | 101 | return storage.ToCachedValue(); 102 | } 103 | 104 | /// 105 | /// Removes the specified cached value 106 | /// 107 | public void Remove(string key, string region) 108 | { 109 | string k = GetCacheKey(key, region); 110 | MemoryCache.Remove(k); 111 | } 112 | 113 | /// 114 | /// Marks the specified cached value as modified 115 | /// 116 | public void MarkAsValidated(string key, string region) 117 | { 118 | DateTime now = DateTime.UtcNow; 119 | string k = GetCacheKey(key, region); 120 | if (MemoryCache.Get(k) is Storage storage) 121 | storage.LastValidatedDate = now; 122 | } 123 | 124 | /// 125 | /// Generates a unique key for the parameter value 126 | /// 127 | public virtual string GetParameterCacheKeyValue(object parameterValue) 128 | { 129 | return ParameterCacheKeys.GenerateCacheKey(parameterValue); 130 | } 131 | 132 | /// 133 | /// Creates an execution plan for retrieving a cached value 134 | /// 135 | public virtual Execution.ICacheExecutionPlan CreateExecutionPlan(ICacheStrategy cacheStrategy) 136 | { 137 | return new Execution.CacheExecutionPlan(this, cacheStrategy); 138 | } 139 | 140 | private string GetCacheKey(string key, string region) 141 | { 142 | return region + ":" + key; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /FluentCache.Test/Strategies/ExampleTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Test 9 | { 10 | [TestClass] 11 | public class ExampleTests 12 | { 13 | private Cache CreateCache() 14 | { 15 | return new FluentCache.Simple.FluentDictionaryCache().WithSource(new ExampleTestsRepository()); 16 | } 17 | 18 | [TestMethod] 19 | public async Task BasicExample() 20 | { 21 | Cache cache = CreateCache(); 22 | 23 | //Here's an example of some typical caching code 24 | //I want to retrieve a value from my cache, and if it's not there load it from the repository 25 | var repository = new ExampleTestsRepository(); 26 | int parameter = 5; 27 | string region = "FluentCacheExamples"; 28 | string cacheKey = "Samples.DoSomeHardParameterizedWork." + parameter; 29 | 30 | CachedValue cachedValue = cache.Get(cacheKey, region); 31 | if (cachedValue == null) 32 | { 33 | double val = repository.DoSomeHardParameterizedWork(parameter); 34 | cachedValue = cache.Set(cacheKey, region, val, new CacheExpiration()); 35 | } 36 | double result = cachedValue.Value; 37 | 38 | //This code is full of boilerplate, magic strings, and is hard to read! 39 | //The *intent* of the code is overwhelmed by the mechanics of how to cache the value 40 | //I hope the method names and parameters don't change, otherwise I have to remember to update the cache key! 41 | 42 | //Here's the equivalent code using FluentCache 43 | //FluentCache automatically analyzes the expression tree an generates a unique cache key from the type, method, and any parameters 44 | double ezResult = cache.Method(r => r.DoSomeHardParameterizedWork(parameter)) 45 | .GetValue(); 46 | 47 | //Here's some more FluentCache examples 48 | 49 | //You can specify cache expiration policies 50 | double ttlValue = cache.Method(r => r.DoSomeHardWork()) 51 | .ExpireAfter(TimeSpan.FromMinutes(5)) 52 | .GetValue(); 53 | 54 | //You can specify dynamic cache expiration policies 55 | double ttlValue2 = cache.Method(r => r.DoSomeHardWork()) 56 | .ExpireAfter(d => d <= 2.0 ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(2)) 57 | .GetValue(); 58 | 59 | //It supports asyn/await natively 60 | double asyncValue = await cache.Method(r => r.DoSomeHardWorkAsync()) 61 | .GetValueAsync(); 62 | 63 | //You can specify validation strategies to customize when caches should be updated 64 | double onlyCachePositiveValues = cache.Method(r => r.DoSomeHardWork()) 65 | .InvalidateIf(cachedVal => cachedVal.Value <= 0d) 66 | .GetValue(); 67 | 68 | //You can clear existing cached values 69 | cache.Method(r => r.DoSomeHardParameterizedWork(parameter)) 70 | .ClearValue(); 71 | 72 | /* 73 | Getting Started 74 | 75 | To get started, you need to choose a FluentCache implementation 76 | FluentCache supports System.Runtime.Caching.MemoryCache out of the box 77 | Other cache types can implement the FluentCache.ICache interface 78 | 79 | In this example, we will use the Dictionarycache, which is a simple wrapper around a ConcurrentDictionary 80 | 81 | */ 82 | ICache myCache = new FluentCache.Simple.FluentDictionaryCache(); 83 | 84 | //Now that we have our cache, we're going to create a wrapper around our Repository 85 | //The wrapper will allow us to cache the results of various Repository methods 86 | var repo = new ExampleTestsRepository(); 87 | Cache myRepositoryCache = myCache.WithSource(repo); 88 | 89 | //Now that we have a wrapper, we can create and execute a CacheStrategy 90 | string resource = myRepositoryCache.Method(r => r.RetrieveResource()) 91 | .ExpireAfter(TimeSpan.FromMinutes(30)) 92 | .GetValue(); 93 | } 94 | } 95 | 96 | public class ExampleTestsRepository 97 | { 98 | public double DoSomeHardWork() 99 | { 100 | return Math.Exp(10); 101 | } 102 | 103 | public async Task DoSomeHardWorkAsync() 104 | { 105 | await Task.Delay(TimeSpan.FromSeconds(1)); 106 | return DoSomeHardWork(); 107 | } 108 | 109 | public double DoSomeHardParameterizedWork(int parameter) 110 | { 111 | return Math.Exp(parameter); 112 | } 113 | 114 | public string RetrieveResource() 115 | { 116 | return "This is just a test"; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /FluentCache/Simple/FluentDictionaryCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Simple 9 | { 10 | /// 11 | /// A simple Cache implementation that uses Dictionary to cache values 12 | /// 13 | public class FluentDictionaryCache : ICache 14 | { 15 | /// 16 | /// Constructs a new instance 17 | /// 18 | public FluentDictionaryCache() 19 | { 20 | Dictionary = new ConcurrentDictionary(); 21 | } 22 | 23 | private readonly ConcurrentDictionary Dictionary; 24 | 25 | /// 26 | /// Gets the specified cached value 27 | /// 28 | public virtual CachedValue Get(string key, string region) 29 | { 30 | string k = GetCacheKey(key, region); 31 | Storage storage; 32 | if (!Dictionary.TryGetValue(k, out storage)) 33 | return null; 34 | 35 | DateTime now = DateTime.UtcNow; 36 | if (now - storage.LastAccessedDate > storage.Expiration?.SlidingExpiration) 37 | { 38 | Dictionary.TryRemove(k, out storage); 39 | return null; 40 | } 41 | 42 | storage.LastAccessedDate = now; 43 | 44 | return storage.ToCachedValue(); 45 | } 46 | 47 | /// 48 | /// Sets the specified cached value 49 | /// 50 | public virtual CachedValue Set(string key, string region, T value, CacheExpiration cacheExpiration) 51 | { 52 | DateTime now = DateTime.UtcNow; 53 | string k = GetCacheKey(key, region); 54 | 55 | var newStorage = new Storage 56 | { 57 | CacheDate = now, 58 | LastAccessedDate = now, 59 | LastValidatedDate = now, 60 | Expiration = cacheExpiration, 61 | Value = value, 62 | Version = 0L, 63 | }; 64 | 65 | Func updateIfExists = (newKey, existing) => 66 | { 67 | existing.Version = existing.Version + 1L; 68 | existing.LastValidatedDate = now; 69 | existing.LastAccessedDate = now; 70 | existing.Value = value; 71 | return existing; 72 | }; 73 | 74 | Storage storage = Dictionary.AddOrUpdate(k, newStorage, updateIfExists); 75 | return storage.ToCachedValue(); 76 | } 77 | 78 | /// 79 | /// Removes the specified cached value 80 | /// 81 | public virtual void Remove(string key, string region) 82 | { 83 | string k = GetCacheKey(key, region); 84 | Storage storage; 85 | Dictionary.TryRemove(k, out storage); 86 | } 87 | 88 | /// 89 | /// Marks the specified cached value as validated 90 | /// 91 | public virtual void MarkAsValidated(string key, string region) 92 | { 93 | DateTime now = DateTime.UtcNow; 94 | Func updateLastModifiedDate = (newKey, existing) => 95 | { 96 | if (existing == null) 97 | return null; 98 | 99 | existing.LastValidatedDate = now; 100 | return existing; 101 | }; 102 | 103 | //Note: if the caller tries to mark validated for a non-existing item then we will just insert a null Storage object 104 | Dictionary.AddOrUpdate(key, default(Storage), updateLastModifiedDate); 105 | } 106 | 107 | /// 108 | /// Gets the cache key for a parameter value 109 | /// 110 | public virtual string GetParameterCacheKeyValue(object parameterValue) 111 | { 112 | return ParameterCacheKeys.GenerateCacheKey(parameterValue); 113 | } 114 | 115 | private string GetCacheKey(string key, string region) 116 | { 117 | return region + ":" + key; 118 | } 119 | 120 | /// 121 | /// Creates an execution plan for the cache strategy 122 | /// 123 | public virtual Execution.ICacheExecutionPlan CreateExecutionPlan(ICacheStrategy cacheStrategy) 124 | { 125 | return new Execution.CacheExecutionPlan(this, cacheStrategy); 126 | } 127 | 128 | private class Storage 129 | { 130 | public DateTime CacheDate { get; set; } 131 | public long Version { get; set; } 132 | public DateTime LastValidatedDate { get; set; } 133 | public DateTime LastAccessedDate { get; set; } 134 | public object Value { get; set; } 135 | public CacheExpiration Expiration { get; set; } 136 | 137 | public CachedValue ToCachedValue() 138 | { 139 | return new CachedValue 140 | { 141 | CachedDate = CacheDate, 142 | LastValidatedDate = LastValidatedDate, 143 | Value = (T)Value, 144 | Version = Version 145 | }; 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /FluentCache/Execution/CacheExecutionPlan.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Execution 8 | { 9 | /// 10 | /// Defines an execution plan for getting a value from a cache, validating, and retrieving a new value 11 | /// 12 | /// The type of value that is cached 13 | public class CacheExecutionPlan : ICacheExecutionPlan 14 | { 15 | /// 16 | /// Constructs a new instance of the execution plan 17 | /// 18 | public CacheExecutionPlan(ICache cache, ICacheStrategy cacheStrategy) 19 | { 20 | _cache = cache; 21 | _cacheStrategy = cacheStrategy; 22 | _cacheStrategyAsync = cacheStrategy as ICacheStrategyAsync; 23 | } 24 | 25 | private readonly ICache _cache; 26 | private readonly ICacheStrategy _cacheStrategy; 27 | private readonly ICacheStrategyAsync _cacheStrategyAsync; 28 | 29 | /// 30 | /// Gets the Key that will be used in combination with the Region to retrieve the value from the cache 31 | /// 32 | public string Key { get { return _cacheStrategy.Key; } } 33 | 34 | /// 35 | /// Gets the Region that will be used in combination with the Key to retrieve the value from the cache 36 | /// 37 | public string Region { get { return _cacheStrategy.Region; } } 38 | 39 | /// 40 | /// Gets the cache that will be used to retrieve the value 41 | /// 42 | public ICache Cache { get { return _cache; } } 43 | 44 | /// 45 | /// Retrieves the value if it is not in the cache 46 | /// 47 | protected virtual CachedValue RetrieveCachedValue(CachedValue previousCachedValue) 48 | { 49 | T value = _cacheStrategy.Retrieve(previousCachedValue); 50 | return Cache.Set(Key, Region, value, _cacheStrategy.ResolveExpiration(value)); 51 | } 52 | 53 | /// 54 | /// Asynchronously retrieves the value if it is not in the cache 55 | /// 56 | protected virtual async Task> RetrieveCachedValueAsync(CachedValue previousCachedValue) 57 | { 58 | if (_cacheStrategyAsync == null) 59 | throw new InvalidOperationException("Specified cache strategy must be an instance of ICacheStrategyAsync"); 60 | 61 | T value = await _cacheStrategyAsync.RetrieveAsync(previousCachedValue); 62 | return Cache.Set(Key, Region, value, _cacheStrategyAsync.ResolveExpiration(value)); 63 | } 64 | 65 | /// 66 | /// Validates the cached value 67 | /// 68 | protected virtual CacheValidationResult ValidateCachedValue(CachedValue existingCachedValue) 69 | { 70 | if (existingCachedValue == null) 71 | return CacheValidationResult.Unknown; 72 | 73 | CacheValidationResult result = _cacheStrategy.Validate(existingCachedValue); 74 | if (result == CacheValidationResult.Valid) 75 | Cache.MarkAsValidated(Key, Region); 76 | 77 | return result; 78 | } 79 | 80 | /// 81 | /// Asynchronously validates the cached value 82 | /// 83 | protected virtual async Task ValidateCachedValueAsync(CachedValue existingCachedValue) 84 | { 85 | if (_cacheStrategyAsync == null) 86 | throw new InvalidOperationException("Specified cache strategy must be an instance of ICacheStrategyAsync"); 87 | 88 | if (existingCachedValue == null) 89 | return CacheValidationResult.Unknown; 90 | 91 | CacheValidationResult result = await _cacheStrategyAsync.ValidateAsync(existingCachedValue); 92 | if (result == CacheValidationResult.Valid) 93 | Cache.MarkAsValidated(Key, Region); 94 | 95 | return result; 96 | } 97 | 98 | /// 99 | /// Executes the plan 100 | /// 101 | public virtual CachedValue Execute() 102 | { 103 | CachedValue cachedValue = Cache.Get(Key, Region); 104 | CacheValidationResult validationResult = ValidateCachedValue(cachedValue); 105 | 106 | if (validationResult != CacheValidationResult.Valid && (validationResult == CacheValidationResult.Invalid || cachedValue == null)) 107 | cachedValue = RetrieveCachedValue(previousCachedValue: cachedValue); 108 | 109 | return cachedValue; 110 | } 111 | 112 | /// 113 | /// Asynchronously executes the plan 114 | /// 115 | public virtual async Task> ExecuteAsync() 116 | { 117 | CachedValue cachedValue = cachedValue = Cache.Get(Key, Region); 118 | CacheValidationResult validationResult = await ValidateCachedValueAsync(cachedValue); 119 | 120 | if (validationResult != CacheValidationResult.Valid && (validationResult == CacheValidationResult.Invalid || cachedValue == null)) 121 | cachedValue = await RetrieveCachedValueAsync(previousCachedValue: cachedValue); 122 | 123 | return cachedValue; 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /FluentCache/Strategies/BulkCacheStrategyAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | /// 10 | /// A strategy to asynchronously access or modify strongly-typed data in a Cache 11 | /// 12 | public class BulkCacheStrategyAsync : BulkCacheStrategyIncomplete 13 | { 14 | internal BulkCacheStrategyAsync(ICache cache, string baseKey, ICollection keys) 15 | : base(cache, baseKey, keys) 16 | { 17 | } 18 | 19 | internal Func, Task> ValidateCallback { get; set; } 20 | internal Func, Task>>> RetrieveCallback { get; set; } 21 | 22 | /// 23 | /// Invalidates the cached value if the specified asynchronous validation delegate returns CacheValidationResult.Invalid 24 | /// 25 | /// An asynchronous delegate that validates the cached value 26 | /// An updated cache strategy that includes the invalidation strategy 27 | public BulkCacheStrategyAsync ValidateAsync(Func, Task> validate) 28 | { 29 | this.ValidateCallback = cachedvalue => validate(cachedvalue); 30 | return this; 31 | } 32 | 33 | /// 34 | /// Invalidates the cached value if the specified asynchronous validation delegate returns false 35 | /// 36 | /// An asynchronous delegate that validates the cached value 37 | /// An updated cache strategy that includes the invalidation strategy 38 | public BulkCacheStrategyAsync InvalidateIfAsync(Func, Task> validate) 39 | { 40 | Func, Task> val = async existing => (await validate(existing)) ? CacheValidationResult.Valid : CacheValidationResult.Invalid; 41 | this.ValidateCallback = val; 42 | return this; 43 | } 44 | 45 | /// 46 | /// Invalidates the cached value if the specified validation delegate returns CacheValidationResult.Invalid 47 | /// 48 | /// A delegate that validates the cached value 49 | /// An updated cache strategy that includes the invalidation strategy 50 | public BulkCacheStrategyAsync Validate(Func, CacheValidationResult> validate) 51 | { 52 | Func, Task> val = existing => Task.FromResult(validate(existing)); 53 | return this.ValidateAsync(val); 54 | } 55 | 56 | /// 57 | /// Invalidates the cached value if the specified validation delegate returns false 58 | /// 59 | /// A delegate that validates the cached value 60 | /// An updated cache strategy that includes the invalidation strategy 61 | public BulkCacheStrategyAsync InvalidateIf(Func, bool> validate) 62 | { 63 | Func, Task> val = existing => Task.FromResult(validate(existing)); 64 | return this.InvalidateIfAsync(val); ; 65 | } 66 | 67 | /// 68 | /// Asynchronously gets all cached results 69 | /// 70 | public async Task>> GetAllAsync() 71 | { 72 | var keysToLoad = Keys.ToList(); 73 | var results = new List>(Keys.Count); 74 | 75 | foreach (TKey key in Keys) 76 | { 77 | string itemKey = GetItemKey(key); 78 | CacheStrategyAsync itemStrategy = new CacheStrategyAsync(Cache, itemKey).WithRegion(Region); 79 | 80 | if (ValidateCallback != null) 81 | itemStrategy = itemStrategy.ValidateAsync(ValidateCallback); 82 | 83 | CachedValue cachedValue = await itemStrategy.GetAsync(); 84 | if (cachedValue != null) 85 | { 86 | keysToLoad.Remove(key); 87 | results.Add(cachedValue); 88 | } 89 | } 90 | 91 | if (RetrieveCallback != null) 92 | { 93 | ICollection> newResults = await RetrieveCallback(keysToLoad); 94 | 95 | foreach (KeyValuePair result in newResults) 96 | { 97 | string itemKey = GetItemKey(result.Key); 98 | TResult value = result.Value; 99 | 100 | CachedValue cachedValue = Cache.Set(itemKey, Region, value, DefaultExpiration); 101 | 102 | results.Add(cachedValue); 103 | } 104 | } 105 | 106 | return results; 107 | } 108 | 109 | /// 110 | /// Asynchronously gets all cached values 111 | /// 112 | public async Task> GetAllValuesAsync() 113 | { 114 | IList> results = await GetAllAsync(); 115 | return results.Select(s => s.Value) 116 | .ToList(); 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /FluentCache/Strategies/CacheStrategyIncomplete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Strategies 9 | { 10 | /// 11 | /// An incomplete strategy to access or modify strongly-typed data in a Cache 12 | /// 13 | public class CacheStrategyIncomplete : SingleValueCacheStrategy 14 | { 15 | internal CacheStrategyIncomplete(ICache cache, string baseKey) 16 | : base(cache, baseKey) 17 | { 18 | } 19 | 20 | /// 21 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 22 | /// 23 | /// A delegate that specifies how the value is to be retrieved 24 | /// An updated cache strategy that includes the retrieval strategy 25 | [Obsolete("Deprecated. Use RetrieveUsing() instead")] 26 | public CacheStrategy Retrieve(Func retrieve) 27 | { 28 | return Complete().RetrieveUsing(retrieve); 29 | } 30 | 31 | /// 32 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 33 | /// 34 | /// A delegate that specifies how the value is to be retrieved 35 | /// An updated cache strategy that includes the retrieval strategy 36 | public CacheStrategy RetrieveUsing(Func retrieve) 37 | { 38 | return Complete().RetrieveUsing(retrieve); 39 | } 40 | 41 | /// 42 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 43 | /// 44 | /// An asynchronous delegate that specifies how the value is to be retrieved 45 | /// An updated cache strategy that includes the retrieval strategy 46 | public CacheStrategyAsync RetrieveUsingAsync(Func> retrieve) 47 | { 48 | return CompleteAsync().RetrieveUsingAsync(retrieve); 49 | } 50 | 51 | /// 52 | /// Gets the cached value wrapper from the cache 53 | /// 54 | /// The cached value wrapper 55 | public CachedValue Get() 56 | { 57 | return Complete().Get(); 58 | } 59 | 60 | /// 61 | /// Gets the cached value 62 | /// 63 | /// The cached value 64 | public T GetValue() 65 | { 66 | CacheStrategy copy = Complete(); 67 | return copy.GetValue(); 68 | } 69 | 70 | internal CacheStrategy Complete() 71 | { 72 | var copy = new CacheStrategy(Cache, BaseKey); 73 | copy.CopyFrom(this); 74 | return copy; 75 | } 76 | 77 | internal CacheStrategyAsync CompleteAsync() 78 | { 79 | var copy = new CacheStrategyAsync(Cache, BaseKey); 80 | copy.CopyFrom(this); 81 | return copy; 82 | } 83 | 84 | /// 85 | /// Updates the cache strategy to include strongly typed parameters 86 | /// 87 | public CacheStrategyParameterized WithParameters(P1 param1) 88 | { 89 | var copy = new CacheStrategyParameterized(Cache, BaseKey); 90 | copy.CopyFrom(this); 91 | copy.Parameters = new List { param1 }; 92 | return copy; 93 | } 94 | 95 | /// 96 | /// Updates the cache strategy to include strongly typed parameters 97 | /// 98 | public CacheStrategyParameterized WithParameters(P1 param1, P2 param2) 99 | { 100 | var copy = new CacheStrategyParameterized(Cache, BaseKey); 101 | copy.CopyFrom(this); 102 | copy.Parameters = new List { param1, param2 }; 103 | return copy; 104 | } 105 | 106 | /// 107 | /// Updates the cache strategy to include strongly typed parameters 108 | /// 109 | public CacheStrategyParameterized WithParameters(P1 param1, P2 param2, P3 param3) 110 | { 111 | var copy = new CacheStrategyParameterized(Cache, BaseKey); 112 | copy.CopyFrom(this); 113 | copy.Parameters = new List { param1, param2, param3 }; 114 | return copy; 115 | } 116 | 117 | /// 118 | /// Updates the cache strategy to include strongly typed parameters 119 | /// 120 | public CacheStrategyParameterized WithParameters(P1 param1, P2 param2, P3 param3, P4 param4) 121 | { 122 | var copy = new CacheStrategyParameterized(Cache, BaseKey); 123 | copy.CopyFrom(this); 124 | copy.Parameters = new List { param1, param2, param3, param4 }; 125 | return copy; 126 | } 127 | 128 | /// 129 | /// Updates the cache strategy to include parameters 130 | /// 131 | public CacheStrategyIncomplete WithParameters(params object[] parameters) 132 | { 133 | this.Parameters = parameters.ToList(); 134 | return this; 135 | } 136 | 137 | 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /FluentCache.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCache.Test", "FluentCache.Test\FluentCache.Test.csproj", "{5FA1B9E5-45DD-4B93-A25B-F7079FC1901F}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCache", "FluentCache\FluentCache.csproj", "{5DBFD0C2-A7E5-4F16-BBE0-633E38B6EB6D}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCache.RuntimeCaching", "FluentCache.RuntimeCaching\FluentCache.RuntimeCaching.csproj", "{1D658A84-86F9-4128-A898-9D3B40A94407}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCache.Microsoft.Extensions.Caching.Redis", "FluentCache.Microsoft.Extensions.Caching.Redis\FluentCache.Microsoft.Extensions.Caching.Redis.csproj", "{F0EC29E0-740F-4F74-9453-5B6CF69075C3}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCache.Test.Nuget", "FluentCache.Test.Nuget\FluentCache.Test.Nuget.csproj", "{FD2BADA7-DF67-4C0D-8BEF-01397191E9EC}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git", "git", "{435EDA4E-4469-41E6-AE19-61B2A13761E2}" 17 | ProjectSection(SolutionItems) = preProject 18 | README.md = README.md 19 | EndProjectSection 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentCache.Microsoft.Extensions.Caching.Memory", "FluentCache.Microsoft.Extensions.Caching.Memory\FluentCache.Microsoft.Extensions.Caching.Memory.csproj", "{20DEC89C-7F8D-4F2A-87BC-2B5107491818}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCache.Microsoft.Extensions.Caching.Abstractions", "FluentCache.Microsoft.Extensions.Caching.Abstractions\FluentCache.Microsoft.Extensions.Caching.Abstractions.csproj", "{A86DE79D-0BF9-44B8-AA06-EDD8A5D32195}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Development|Any CPU = Development|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {5FA1B9E5-45DD-4B93-A25B-F7079FC1901F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {5FA1B9E5-45DD-4B93-A25B-F7079FC1901F}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {5FA1B9E5-45DD-4B93-A25B-F7079FC1901F}.Development|Any CPU.ActiveCfg = Debug|Any CPU 35 | {5FA1B9E5-45DD-4B93-A25B-F7079FC1901F}.Development|Any CPU.Build.0 = Debug|Any CPU 36 | {5FA1B9E5-45DD-4B93-A25B-F7079FC1901F}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {5FA1B9E5-45DD-4B93-A25B-F7079FC1901F}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {5DBFD0C2-A7E5-4F16-BBE0-633E38B6EB6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {5DBFD0C2-A7E5-4F16-BBE0-633E38B6EB6D}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {5DBFD0C2-A7E5-4F16-BBE0-633E38B6EB6D}.Development|Any CPU.ActiveCfg = Debug|Any CPU 41 | {5DBFD0C2-A7E5-4F16-BBE0-633E38B6EB6D}.Development|Any CPU.Build.0 = Debug|Any CPU 42 | {5DBFD0C2-A7E5-4F16-BBE0-633E38B6EB6D}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {5DBFD0C2-A7E5-4F16-BBE0-633E38B6EB6D}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {1D658A84-86F9-4128-A898-9D3B40A94407}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {1D658A84-86F9-4128-A898-9D3B40A94407}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {1D658A84-86F9-4128-A898-9D3B40A94407}.Development|Any CPU.ActiveCfg = Debug|Any CPU 47 | {1D658A84-86F9-4128-A898-9D3B40A94407}.Development|Any CPU.Build.0 = Debug|Any CPU 48 | {1D658A84-86F9-4128-A898-9D3B40A94407}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {1D658A84-86F9-4128-A898-9D3B40A94407}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {F0EC29E0-740F-4F74-9453-5B6CF69075C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {F0EC29E0-740F-4F74-9453-5B6CF69075C3}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {F0EC29E0-740F-4F74-9453-5B6CF69075C3}.Development|Any CPU.ActiveCfg = Debug|Any CPU 53 | {F0EC29E0-740F-4F74-9453-5B6CF69075C3}.Development|Any CPU.Build.0 = Debug|Any CPU 54 | {F0EC29E0-740F-4F74-9453-5B6CF69075C3}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {F0EC29E0-740F-4F74-9453-5B6CF69075C3}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {FD2BADA7-DF67-4C0D-8BEF-01397191E9EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {FD2BADA7-DF67-4C0D-8BEF-01397191E9EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {FD2BADA7-DF67-4C0D-8BEF-01397191E9EC}.Development|Any CPU.ActiveCfg = Debug|Any CPU 59 | {FD2BADA7-DF67-4C0D-8BEF-01397191E9EC}.Development|Any CPU.Build.0 = Debug|Any CPU 60 | {FD2BADA7-DF67-4C0D-8BEF-01397191E9EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {FD2BADA7-DF67-4C0D-8BEF-01397191E9EC}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {20DEC89C-7F8D-4F2A-87BC-2B5107491818}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {20DEC89C-7F8D-4F2A-87BC-2B5107491818}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {20DEC89C-7F8D-4F2A-87BC-2B5107491818}.Development|Any CPU.ActiveCfg = Debug|Any CPU 65 | {20DEC89C-7F8D-4F2A-87BC-2B5107491818}.Development|Any CPU.Build.0 = Debug|Any CPU 66 | {20DEC89C-7F8D-4F2A-87BC-2B5107491818}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {20DEC89C-7F8D-4F2A-87BC-2B5107491818}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {A86DE79D-0BF9-44B8-AA06-EDD8A5D32195}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {A86DE79D-0BF9-44B8-AA06-EDD8A5D32195}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {A86DE79D-0BF9-44B8-AA06-EDD8A5D32195}.Development|Any CPU.ActiveCfg = Debug|Any CPU 71 | {A86DE79D-0BF9-44B8-AA06-EDD8A5D32195}.Development|Any CPU.Build.0 = Debug|Any CPU 72 | {A86DE79D-0BF9-44B8-AA06-EDD8A5D32195}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {A86DE79D-0BF9-44B8-AA06-EDD8A5D32195}.Release|Any CPU.Build.0 = Release|Any CPU 74 | EndGlobalSection 75 | GlobalSection(SolutionProperties) = preSolution 76 | HideSolutionNode = FALSE 77 | EndGlobalSection 78 | GlobalSection(ExtensibilityGlobals) = postSolution 79 | SolutionGuid = {77717A2D-52FC-4494-9601-114FBDE84A13} 80 | EndGlobalSection 81 | EndGlobal 82 | -------------------------------------------------------------------------------- /FluentCache/Strategies/CacheStrategy_Generic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Strategies 9 | { 10 | /// 11 | /// A strategy to access or modify strongly-typed data in a Cache 12 | /// 13 | /// The type of the data in the Cache 14 | public class CacheStrategy : SingleValueCacheStrategy, ICacheStrategy 15 | { 16 | internal CacheStrategy(ICache cache, string baseKey) 17 | : base(cache, baseKey) 18 | { 19 | } 20 | 21 | internal Func, CacheValidationResult> ValidateCallback { get; set; } 22 | internal Func RetrieveCallback { get; set; } 23 | internal Func, RetrievalErrorHandlerResult> RetrieveErrorHandler { get; set; } 24 | 25 | /// 26 | /// Gets the cached value wrapper from the cache 27 | /// 28 | /// The cached value wrapper 29 | public CachedValue Get() 30 | { 31 | Execution.ICacheExecutionPlan plan = Cache.CreateExecutionPlan(this); 32 | return plan.Execute(); 33 | } 34 | 35 | /// 36 | /// Invalidates the cached value if the specified validation delegate returns false 37 | /// 38 | /// A delegate that validates the cached value 39 | /// An updated cache strategy that includes the invalidation strategy 40 | public CacheStrategy InvalidateIf(Func, bool> validate) 41 | { 42 | Func, CacheValidationResult> val = existing => validate(existing) ? CacheValidationResult.Valid : CacheValidationResult.Invalid; 43 | return Validate(val); 44 | } 45 | 46 | /// 47 | /// Invalidates the cached value if the specified validation delegate returns CacheValidationResult.Invalid 48 | /// 49 | /// A delegate that validates the cached value 50 | /// An updated cache strategy that includes the invalidation strategy 51 | 52 | public CacheStrategy Validate(Func, CacheValidationResult> validate) 53 | { 54 | this.ValidateCallback = validate; 55 | return this; 56 | } 57 | 58 | /// 59 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 60 | /// 61 | /// A delegate that specifies how the value is to be retrieved 62 | /// An updated cache strategy that includes the retrieval strategy 63 | public CacheStrategy RetrieveUsing(Func retrieve) 64 | { 65 | this.RetrieveCallback = retrieve; 66 | return this; 67 | } 68 | 69 | /// 70 | /// Specifies an error handling strategy if retrieval fails 71 | /// 72 | /// An error handler that takes the exception and previous cached value (if it exists) and returns the fallback value 73 | /// An updated cache strategy that includes the retrieval error handler strategy 74 | public CacheStrategy IfRetrievalFails(Func, RetrievalErrorHandlerResult> errorHandler) 75 | { 76 | this.RetrieveErrorHandler = errorHandler; 77 | return this; 78 | } 79 | 80 | /// 81 | /// Gets the cached value 82 | /// 83 | /// The cached value 84 | public T GetValue() 85 | { 86 | CachedValue cachedValue = this.Get(); 87 | return cachedValue == null ? default(T) : cachedValue.Value; 88 | } 89 | 90 | /// 91 | /// Sets the cached value 92 | /// 93 | /// The value to be cached 94 | public void SetValue(T value) 95 | { 96 | Cache.Set(Key, Region, value, ResolveExpiration(value)); 97 | } 98 | 99 | /// 100 | /// Resolves the Expiration that this caching policy will use when caching items 101 | /// 102 | public CacheExpiration ResolveExpiration(T value) 103 | { 104 | return base.ResolveExpiration(value); 105 | 106 | } 107 | 108 | CacheValidationResult ICacheStrategy.Validate(CachedValue existingValue) 109 | { 110 | if (ValidateCallback == null) 111 | return CacheValidationResult.Unknown; 112 | 113 | return ValidateCallback(existingValue); 114 | } 115 | 116 | T ICacheStrategy.Retrieve(CachedValue existingCachedValue) 117 | { 118 | if (RetrieveCallback == null) 119 | return default(T); 120 | 121 | if (RetrieveErrorHandler == null) 122 | { 123 | return RetrieveCallback(); 124 | } 125 | else 126 | { 127 | try 128 | { 129 | return RetrieveCallback(); 130 | } 131 | catch (Exception x) 132 | { 133 | RetrievalErrorHandlerResult result = RetrieveErrorHandler(x, existingCachedValue); 134 | if (result.IsErrorHandled) 135 | return result.FallbackResult; 136 | else 137 | throw; 138 | } 139 | } 140 | } 141 | } 142 | 143 | 144 | 145 | 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FluentCache ![](https://raw.githubusercontent.com/cordialgerm/FluentCache/master/FluentCache_24.png) 2 | 3 | ## A Library for Fluent Caching in C# 4 | 5 | FluentCache is a simple, fluent library to help you write clean, legible caching code by reducing boilerplate. 6 | 7 | ```csharp 8 | double ezResult = cache.Method(r => r.DoSomeHardParameterizedWork(parameter)) 9 | .GetValue(); 10 | ``` 11 | 12 | |Package|Status| 13 | |---|---| 14 | |[FluentCache](https://www.nuget.org/packages/FluentCache) |![FluentCache](https://buildstats.info/nuget/FluentCache)| 15 | |[FluentCache.RuntimeCaching](https://www.nuget.org/packages/FluentCache.RuntimeCaching) |![](https://buildstats.info/nuget/FluentCache.RuntimeCaching)| 16 | |[FluentCache.Microsoft.Extensions.Caching.Abstractions](https://www.nuget.org/packages/FluentCache.Microsoft.Extensions.Caching.Abstractions) |![](https://buildstats.info/nuget/FluentCache.Microsoft.Extensions.Caching.Abstractions)| 17 | |[FluentCache.Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/FluentCache.Microsoft.Extensions.Caching.Memory) |![](https://buildstats.info/nuget/FluentCache.Microsoft.Extensions.Caching.Memory)| 18 | |[FluentCache.Microsoft.Extensions.Caching.Redis](https://www.nuget.org/packages/FluentCache.Microsoft.Extensions.Caching.Redis) |![](https://buildstats.info/nuget/FluentCache.Microsoft.Extensions.Caching.Redis)| 19 | 20 | **Features**: 21 | 22 | * **Fluent API**: clean, simple API to encapsulate caching logic 23 | * **Automatic Cache Key Generation**: refactor at will and never worry about magic strings. FluentCache automatically analyzes the expression tree and generates caching keys based on the type, method, and parameters 24 | * **Caching policies**: specify caching policies like expiration, validation, and error handling 25 | * **Cache Implementations**: FluentCache supports common caching implementations and has a simple ICache interface to support other providers 26 | 27 | Here's an example of some typical caching code that can be replaced by FluentCache: 28 | 29 | ```csharp 30 | //retrieve a value from the cache. If it's not there, load it from the repository 31 | var repository = new Repository(); 32 | int parameter = 5; 33 | string region = "FluentCacheExamples"; 34 | string cacheKey = "Samples.DoSomeHardParameterizedWork." + parameter; 35 | 36 | CachedValue cachedValue = cache.Get(cacheKey, region); 37 | if (cachedValue == null) 38 | { 39 | double val = repository.DoSomeHardParameterizedWork(parameter); 40 | cachedValue = cache.Set(cacheKey, region, val, new CacheExpiration()); 41 | } 42 | double result = cachedValue.Value; 43 | ``` 44 | There are several issues with this code: 45 | 46 | * **boilerplate**: duplicating this code is tedious 47 | * **magic strings**: the cache key is based on magic strings that won't automatically refactor as methods and parameters change 48 | * **hard to read**: the intent is overwhelmed by the mechanics 49 | 50 | ## Examples: 51 | 52 | 53 | ### Cache Expiration Policies 54 | ```csharp 55 | double ttlValue = cache.Method(r => r.DoSomeHardWork()) 56 | .ExpireAfter(TimeSpan.FromMinutes(5)) 57 | .GetValue(); 58 | ``` 59 | 60 | ### Async/Await Retrieval 61 | ```csharp 62 | double asyncValue = await cache.Method(r => r.DoSomeHardWorkAsync()) 63 | .GetValueAsync(); 64 | ``` 65 | 66 | ### Validation Strategies 67 | ```csharp 68 | double onlyCachePositiveValues = cache.Method(r => r.DoSomeHardWork()) 69 | .InvalidateIf(cachedVal => cachedVal.Value <= 0d) 70 | .GetValue(); 71 | ``` 72 | 73 | ### Clearing Values 74 | ```csharp 75 | cache.Method(r => r.DoSomeHardParameterizedWork(parameter)) 76 | .ClearValue(); 77 | ``` 78 | 79 | 80 | ## Getting Started 81 | 82 | ### Hello World 83 | 84 | To get started, we will use the `FluentDictionaryCache` to illustrate the various Fluent extension methods provided by the API 85 | 86 | ```csharp 87 | //use the simplest cache, which wraps a dictionary 88 | //other cache implementations are provided in additional nuget packages 89 | ICache myCache = new FluentCache.Simple.FluentDictionaryCache(); 90 | 91 | //create a wrapper around our Repository 92 | //wrapper will allow us to cache the results of various Repository methods 93 | Repository repo = new Repository(); 94 | Cache myRepositoryCache = myCache.WithSource(repo); 95 | 96 | //create and execute a CacheStrategy using Fluent Extension methods 97 | string resource = myRepositoryCache.Method(r => r.RetrieveResource()) 98 | .ExpireAfter(TimeSpan.FromMinutes(30)) 99 | .GetValue(); 100 | ``` 101 | 102 | ### Implementations 103 | 104 | | Cache Implementation | FluentCache Type | NuGet Package | 105 | |---|---|---| 106 | |`ConcurrentDictionary` | `FluentDictionaryCache` | `FluentCache` | 107 | |`System.Runtime.Caching.MemoryCache`|`FluentMemoryCache`|`FluentCache.RuntimeCaching`| 108 | |`Microsoft.Extensions.Caching.Memory.MemoryCache`| `FluentMemoryCache` | `FluentCache.Microsoft.Extensions.Caching.Memory`| 109 | |`Microsoft.Extensions.Caching.Redis.RedisCache` | `FluentRedisCache` | `FluentCache.Microsoft.Extensions.Caching.Redis`| 110 | |`Microsoft.Extensions.Caching.Memory.IMemoryCache`| `FluentIMemoryCache` | `FluentCache.Microsoft.Extensions.Caching.Abstractions`| 111 | |`Microsoft.Extensions.Caching.Distributed.IDistributedCache` | `FluentIDistributedCache` | `FluentCache.Microsoft.Extensions.Caching.Abstractions`| 112 | 113 | Other caches can implement the `FluentCache.ICache` interface 114 | 115 | ## New in v4.0.2 116 | 117 | 1. `.ExpireAfter()` now supports a callback for determining sliding expiration based on cached value 118 | 1. `FluentCache` now only requires .NET Standard 1.1 119 | 1. Assemblies are signed 120 | 121 | ## New in v4 122 | 123 | 1. Support for .NET Standard 2.0 124 | 1. New implementations for `Microsoft.Extensions.Caching` 125 | 1. `Microsoft.Extensions.Caching.Memory.IMemoryCache` added to `FluentCache.Microsoft.Extensions.Caching.Abstractions` nuget package 126 | 1. `Microsoft.Extensions.Caching.Memory.MemoryCache` added to `FluentCache.Microsoft.Extensions.Caching.Memory` nuget package 127 | 1. `Microsoft.Extensions.Caching.Distributed.IDistributedCache` added to `FluentCache.Microsoft.Extensions.Caching.Abstractions` nuget package 128 | 1. `Microsoft.Extensions.Caching.Redis.RedisCache` added to `FluentCache.Microsoft.Extensions.Caching.Redis` nuget package 129 | 1. Support for caching methods on generic repositories 130 | 131 | The previous `FluentCache.Redis` package is deprecated in favor of `FluentCache.Microsoft.Extensions.Caching.Redis` 132 | 133 | 134 | -------------------------------------------------------------------------------- /FluentCache.Test/Strategies/BulkCacheTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FluentCache.Test 9 | { 10 | [TestClass] 11 | public class BulkCacheTests 12 | { 13 | private Cache CreateCache() 14 | { 15 | return new Simple.FluentDictionaryCache().WithSource(new BulkCacheOperations()); 16 | } 17 | 18 | [TestMethod] 19 | public async Task Bulk_CacheOperation() 20 | { 21 | var cache = CreateCache(); 22 | 23 | var nums = new List { 1 ,2, 3 }; 24 | 25 | cache.Method(t => t.GetExponents(nums)) 26 | .GetAll(); 27 | 28 | var nums2 = new List { 2, 3 }; 29 | 30 | await Task.Delay(TimeSpan.FromSeconds(.25)); 31 | 32 | DateTime now = DateTime.UtcNow; 33 | 34 | 35 | IList> results = cache.Method(t => t.GetExponents(nums2)) 36 | .GetAll(); 37 | 38 | foreach (CachedValue cachedItem in results) 39 | { 40 | Assert.IsTrue(cachedItem.CachedDate < now, "All items should have been previously cached"); 41 | } 42 | } 43 | 44 | [TestMethod] 45 | public async Task Bulk_CacheOperationAsync() 46 | { 47 | var cache = CreateCache(); 48 | 49 | var nums = new List { 1, 2, 3 }; 50 | 51 | await cache.Method(t => t.GetExponentsAsync(nums)) 52 | .GetAllAsync(); 53 | 54 | var nums2 = new List { 2, 3 }; 55 | 56 | await Task.Delay(TimeSpan.FromSeconds(.25)); 57 | 58 | DateTime now = DateTime.UtcNow; 59 | 60 | 61 | IList> results = await cache.Method(t => t.GetExponentsAsync(nums2)) 62 | .GetAllAsync(); 63 | 64 | foreach (CachedValue cachedItem in results) 65 | { 66 | Assert.IsTrue(cachedItem.CachedDate < now, "All items should have been previously cached"); 67 | } 68 | } 69 | 70 | [TestMethod, ExpectedException(typeof(Expressions.InvalidCachingExpressionException))] 71 | public void Bulk_FirstParameterMustBeKeyCollection() 72 | { 73 | var cache = CreateCache(); 74 | 75 | cache.Method(t => t.GetExponentsBadArgument("evil")) 76 | .GetAll(); 77 | } 78 | 79 | [TestMethod] 80 | public async Task Bulk_CacheOperation_MultipleParameters() 81 | { 82 | var cache = CreateCache(); 83 | 84 | var nums = new List { 1, 2, 3 }; 85 | 86 | var firstStrat = cache.Method(t => t.GetExponentsWithParameter(nums, 10)); 87 | firstStrat.GetAll(); 88 | 89 | await Task.Delay(TimeSpan.FromSeconds(.5)); 90 | DateTime notCachedBefore = DateTime.UtcNow; 91 | await Task.Delay(TimeSpan.FromSeconds(.5)); 92 | 93 | var secondStrat = cache.Method(t => t.GetExponentsWithParameter(nums, 11)); 94 | IList> results = secondStrat.GetAll(); 95 | 96 | foreach (CachedValue cachedItem in results) 97 | { 98 | Assert.IsTrue(cachedItem.CachedDate > notCachedBefore, "No items should have been cached. Item with value {0} was cached on {1} and should not have been cached before {2}", cachedItem.Value, cachedItem.CachedDate, notCachedBefore); 99 | } 100 | } 101 | 102 | [TestMethod] 103 | public async Task Bulk_CacheOperationAsync_MultipleParameters() 104 | { 105 | var cache = CreateCache(); 106 | 107 | var nums = new List { 1, 2, 3 }; 108 | 109 | cache.Method(t => t.GetExponentsWithParameter(nums, 10)) 110 | .GetAll(); 111 | 112 | await Task.Delay(TimeSpan.FromSeconds(.5)); 113 | DateTime notCachedBefore = DateTime.UtcNow; 114 | await Task.Delay(TimeSpan.FromSeconds(.5)); 115 | 116 | IList> results = cache.Method(t => t.GetExponentsWithParameter(nums, 11)) 117 | .GetAll(); 118 | 119 | foreach (CachedValue cachedItem in results) 120 | { 121 | Assert.IsTrue(cachedItem.CachedDate > notCachedBefore, "No items should have been cached. Item with value {0} was cached on {1} and should not have been cached before {2}", cachedItem.Value, cachedItem.CachedDate, notCachedBefore); 122 | } 123 | } 124 | 125 | [TestMethod] 126 | public async Task Bulk_CacheOperation_Clear() 127 | { 128 | var cache = CreateCache(); 129 | 130 | var nums = new List { 1, 2, 3 }; 131 | 132 | var strat = cache.Method(t => t.GetExponentsWithParameter(nums, 10)); 133 | 134 | strat.GetAll(); 135 | 136 | await Task.Delay(TimeSpan.FromSeconds(.5)); 137 | DateTime notCachedBefore = DateTime.UtcNow; 138 | await Task.Delay(TimeSpan.FromSeconds(.5)); 139 | 140 | strat.ClearValues(); 141 | 142 | IList> results = strat.GetAll(); 143 | 144 | foreach (CachedValue cachedItem in results) 145 | Assert.IsTrue(cachedItem.CachedDate > notCachedBefore, "No items should have been cached. Item with value {0} was cached on {1} and should not have been cached before {2}", cachedItem.Value, cachedItem.CachedDate, notCachedBefore); 146 | } 147 | 148 | 149 | } 150 | 151 | public class BulkCacheOperations 152 | { 153 | public ICollection> GetExponentsBadArgument(string input) 154 | { 155 | throw new NotImplementedException(); 156 | } 157 | 158 | public ICollection> GetExponentsWithParameter(ICollection indexes, double baseValue) 159 | { 160 | return indexes.Select(indx => new { Index = indx, Value = Math.Pow(baseValue, indx) }) 161 | .ToDictionary(x => x.Index, x => x.Value); 162 | } 163 | 164 | public async Task>> GetExponentsWithParameterAsync(ICollection indexes, double baseValue) 165 | { 166 | await Task.Delay(TimeSpan.FromSeconds(.125)); 167 | return GetExponentsWithParameter(indexes, baseValue); 168 | } 169 | 170 | public ICollection> GetExponents(ICollection indexes) 171 | { 172 | return indexes.Select(indx => new { Index = indx, Value = Math.Exp(indx) }) 173 | .ToDictionary(x => x.Index, x => x.Value); 174 | 175 | } 176 | 177 | public async Task>> GetExponentsAsync(ICollection indexes) 178 | { 179 | await Task.Delay(TimeSpan.FromSeconds(.75)); 180 | 181 | return GetExponents(indexes); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /FluentCache/Strategies/CacheStrategyAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | /// 10 | /// A strategy to asynchronously access or modify strongly-typed data in a Cache 11 | /// 12 | /// 13 | public class CacheStrategyAsync : SingleValueCacheStrategy, ICacheStrategyAsync 14 | { 15 | internal CacheStrategyAsync(ICache cache, string baseKey) 16 | : base(cache, baseKey) 17 | { 18 | } 19 | 20 | internal Func, Task> ValidateCallback { get; set; } 21 | internal Func> RetrieveCallback { get; set; } 22 | internal Func, RetrievalErrorHandlerResult> RetrieveErrorHandler { get; set; } 23 | 24 | /// 25 | /// Asynchronously gets the cached value wrapper from the cache 26 | /// 27 | /// A task containing the cached value wrapper 28 | public async Task> GetAsync() 29 | { 30 | Execution.ICacheExecutionPlan plan = Cache.CreateExecutionPlan(this); 31 | return await plan.ExecuteAsync(); 32 | } 33 | 34 | /// 35 | /// Invalidates the cached value if the specified validation delegate returns false 36 | /// 37 | /// A delegate that validates the cached value 38 | /// An updated cache strategy that includes the invalidation strategy 39 | public CacheStrategyAsync InvalidateIf(Func, bool> validate) 40 | { 41 | Func, Task> val = existing => Task.FromResult(validate(existing)); 42 | return InvalidateIfAsync(val); 43 | } 44 | 45 | /// 46 | /// Invalidates the cached value if the specified asynchronous validation delegate returns false 47 | /// 48 | /// An asynchronous delegate that validates the cached value 49 | /// An updated cache strategy that includes the invalidation strategy 50 | public CacheStrategyAsync InvalidateIfAsync(Func, Task> validate) 51 | { 52 | Func, Task> val = async existing => (await validate(existing))? CacheValidationResult.Valid : CacheValidationResult.Invalid; 53 | return ValidateAsync(val); 54 | } 55 | 56 | /// 57 | /// Invalidates the cached value if the specified asynchronous validation delegate returns CacheValidationResult.Invalid 58 | /// 59 | /// An asynchronous delegate that validates the cached value 60 | /// An updated cache strategy that includes the invalidation strategy 61 | public CacheStrategyAsync ValidateAsync(Func, Task> validate) 62 | { 63 | this.ValidateCallback = validate; 64 | return this; 65 | } 66 | 67 | /// 68 | /// Invalidates the cached value if the specified validation delegate returns CacheValidationResult.Invalid 69 | /// 70 | /// A delegate that validates the cached value 71 | /// An updated cache strategy that includes the invalidation strategy 72 | public CacheStrategyAsync Validate(Func, CacheValidationResult> validate) 73 | { 74 | Func, Task> val = existing => Task.FromResult(validate(existing)); 75 | return ValidateAsync(val); 76 | } 77 | 78 | /// 79 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 80 | /// 81 | /// An asynchronous delegate that specifies how the value is to be retrieved 82 | /// An updated cache strategy that includes the retrieval strategy 83 | public CacheStrategyAsync RetrieveUsingAsync(Func> retrieve) 84 | { 85 | this.RetrieveCallback = retrieve; 86 | return this; 87 | } 88 | 89 | /// 90 | /// Specifies an error handling strategy if retrieval fails 91 | /// 92 | /// An error handler that takes the exception and previous cached value (if it exists) and returns the fallback value 93 | /// An updated cache strategy that includes the retrieval error handler strategy 94 | public CacheStrategyAsync IfRetrievalFails(Func, RetrievalErrorHandlerResult> errorHandler) 95 | { 96 | this.RetrieveErrorHandler = errorHandler; 97 | return this; 98 | } 99 | 100 | /// 101 | /// Asynchronously gets the cached value 102 | /// 103 | /// A task containing the cached value 104 | public async Task GetValueAsync() 105 | { 106 | CachedValue cachedValue = await this.GetAsync(); 107 | return cachedValue == null ? default(T) : cachedValue.Value; 108 | } 109 | 110 | /// 111 | /// Sets the cached value 112 | /// 113 | /// The value to be cached 114 | public void SetValue(T value) 115 | { 116 | Cache.Set(Key, Region, value, ResolveExpiration(value)); 117 | } 118 | 119 | /// 120 | /// Resolves the Expiration that this caching policy will use when caching items 121 | /// 122 | public CacheExpiration ResolveExpiration(T value) 123 | { 124 | return base.ResolveExpiration(value); 125 | } 126 | 127 | string ICacheStrategy.Key 128 | { 129 | get { return Key; } 130 | } 131 | 132 | string ICacheStrategy.Region 133 | { 134 | get { return Region; } 135 | } 136 | 137 | 138 | CacheValidationResult ICacheStrategy.Validate(CachedValue existingValue) 139 | { 140 | throw new NotImplementedException(); 141 | } 142 | 143 | T ICacheStrategy.Retrieve(CachedValue existingCachedValue) 144 | { 145 | throw new NotImplementedException(); 146 | } 147 | 148 | async Task ICacheStrategyAsync.RetrieveAsync(CachedValue existingCachedValue) 149 | { 150 | if (RetrieveCallback == null) 151 | return default(T); 152 | 153 | if (RetrieveErrorHandler == null) 154 | { 155 | return await RetrieveCallback(); 156 | } 157 | else 158 | { 159 | try 160 | { 161 | return await RetrieveCallback(); 162 | } 163 | catch (Exception x) 164 | { 165 | RetrievalErrorHandlerResult result = RetrieveErrorHandler(x, existingCachedValue); 166 | if (result.IsErrorHandled) 167 | return result.FallbackResult; 168 | else 169 | throw; 170 | } 171 | } 172 | } 173 | 174 | async Task ICacheStrategyAsync.ValidateAsync(CachedValue existingCachedValue) 175 | { 176 | if (ValidateCallback == null) 177 | return CacheValidationResult.Unknown; 178 | 179 | return await ValidateCallback(existingCachedValue); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/CacheTester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentCache; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using FluentCache.Strategies; 9 | using System.Reflection; 10 | 11 | namespace FluentCache.Test.Nuget 12 | { 13 | public class CacheTester 14 | { 15 | public class Example 16 | { 17 | public double CalculateSomeWork() 18 | { 19 | return Math.Sqrt(1234); 20 | } 21 | 22 | public double CalculateSomeWork(int power) 23 | { 24 | return Math.Pow(Math.E, power); 25 | } 26 | 27 | public async Task CalculateSomeWorkAsync() 28 | { 29 | await Task.Delay(TimeSpan.FromSeconds(.125)); 30 | return CalculateSomeWork(); 31 | } 32 | 33 | public async Task CalculateSomeWorkAsync(int power) 34 | { 35 | await Task.Delay(TimeSpan.FromSeconds(.125)); 36 | return CalculateSomeWork(power); 37 | 38 | } 39 | 40 | } 41 | 42 | internal CacheTester() 43 | { 44 | } 45 | 46 | public void ThisMethod_GetValue_Default(Cache cache) 47 | { 48 | double result = cache.ThisMethod() 49 | .GetValue(); 50 | 51 | Assert.AreEqual(default(double), result, "Should return default since we didn't specify a retrieval mechanism"); 52 | 53 | } 54 | 55 | public void Method_Get(Cache cache) 56 | { 57 | CachedValue result = cache.Method(c => c.CalculateSomeWork()) 58 | .Get(); 59 | 60 | Assert.IsNotNull(result, "Should not return null since we specified a retrieval mechanism"); 61 | } 62 | 63 | public void Method_GetValue(Cache cache) 64 | { 65 | double result = cache.Method(c => c.CalculateSomeWork()) 66 | .GetValue(); 67 | 68 | Assert.AreNotEqual(default(double), "Should not return 0 since we specified a retrieval mechanism"); 69 | } 70 | 71 | public void Method_Get_InitialVersion(Cache cache) 72 | { 73 | var strat = cache.Method(c => c.CalculateSomeWork()); 74 | strat.ClearValue(); 75 | 76 | CachedValue result = strat.Get(); 77 | 78 | Assert.AreEqual(0L, result.Version, "Initial Get should have 0 as the version"); 79 | 80 | } 81 | 82 | public void Method_Get_MultipleSameVersion(Cache cache) 83 | { 84 | CacheStrategy cacheStrategy = cache.Method(c => c.CalculateSomeWork()); 85 | for (int i = 0; i < 10; i++) 86 | { 87 | CachedValue result = cacheStrategy.Get(); 88 | Assert.AreEqual(0L, result.Version, "All subsequent calls should retrieve the existing version"); 89 | } 90 | } 91 | 92 | public void Method_Get_Invalidation(Cache cache) 93 | { 94 | bool isInvalid = false; 95 | Func, CacheValidationResult> validate = existing => 96 | { 97 | if (isInvalid) 98 | return CacheValidationResult.Invalid; 99 | else 100 | return CacheValidationResult.Unknown; 101 | }; 102 | 103 | CacheStrategy strategy = cache.Method(c => c.CalculateSomeWork()) 104 | .Validate(validate); 105 | 106 | CachedValue result0 = strategy.Get(); 107 | 108 | 109 | Assert.AreEqual(0L, result0.Version); 110 | 111 | isInvalid = true; 112 | CachedValue result1 = strategy.Get(); 113 | CachedValue result2 = strategy.Get(); 114 | 115 | Assert.AreEqual(1L, result1.Version); 116 | Assert.AreEqual(2L, result2.Version); 117 | 118 | } 119 | 120 | public async Task Method_ExpireAfter_VersionReset(Cache cache) 121 | { 122 | DateTime now = DateTime.UtcNow; 123 | TimeSpan wait = TimeSpan.FromSeconds(.5); 124 | 125 | CacheStrategy strategy = cache.Method(c => c.CalculateSomeWork()) 126 | .ExpireAfter(wait); 127 | 128 | strategy.ClearValue(); 129 | 130 | CachedValue result0 = strategy.Get(); 131 | 132 | await Task.Delay(wait + wait); 133 | 134 | CachedValue result1 = strategy.Get(); 135 | 136 | Assert.AreEqual(0, result0.Version, "The first call should be for a new value with version 0"); 137 | Assert.AreNotEqual(result0.CachedDate, result1.CachedDate, "The 2nd call should have expired and caused a new item to be inserted"); 138 | Assert.AreEqual(0, result1.Version, "The 2nd call should have expired. Expired items are removed, so we don't expect the version to have incremented"); 139 | } 140 | 141 | public async Task Method_ExpireAfter(Cache cache) 142 | { 143 | TimeSpan wait = TimeSpan.FromSeconds(1); 144 | 145 | CacheStrategy strategy = cache.Method(c => c.CalculateSomeWork()) 146 | .ExpireAfter(wait); 147 | 148 | DateTime firstCacheDate = default(DateTime); 149 | strategy.Get(); 150 | for (int i = 1; i < 10; i++) 151 | { 152 | CachedValue result = strategy.Get(); 153 | Assert.AreEqual(0L, result.Version, "These calls should be cached"); 154 | 155 | await Task.Delay(TimeSpan.FromSeconds(wait.TotalSeconds / 5.0)); 156 | 157 | firstCacheDate = result.CachedDate; 158 | } 159 | 160 | await Task.Delay(wait); 161 | 162 | CachedValue expiredResult = strategy.Get(); 163 | 164 | Assert.AreNotEqual(firstCacheDate, expiredResult.CachedDate, "This call should have expired after waiting for {0}", wait); 165 | } 166 | 167 | public void Method_Parameterized(Cache cache) 168 | { 169 | double result = cache.Method(c => c.CalculateSomeWork(3)) 170 | .GetValue(); 171 | 172 | Assert.AreNotEqual(0, result, "This should be a calculated value"); 173 | } 174 | 175 | public async Task Method_Async_GetValue_NotDefault(Cache cache) 176 | { 177 | 178 | CacheStrategyAsync strategy = cache.Method(c => c.CalculateSomeWorkAsync(3)); 179 | 180 | double value = await strategy.GetValueAsync(); 181 | 182 | Assert.AreNotEqual(0d, value); 183 | } 184 | 185 | public async Task Method_Async_Parameters(Cache cache) 186 | { 187 | CacheStrategyAsync strategy = cache.Method(c => c.CalculateSomeWorkAsync(3)); 188 | 189 | CachedValue result1 = await strategy.GetAsync(); 190 | CachedValue result2 = await strategy.GetAsync(); 191 | 192 | Assert.AreEqual(result1.Version, result2.Version); 193 | } 194 | 195 | 196 | 197 | public static async Task TestCacheAsync(Func cacheFactory) 198 | { 199 | CacheTester tester = new CacheTester(); 200 | 201 | var methods = tester.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 202 | foreach (var testMethod in methods) 203 | { 204 | var cache = cacheFactory().WithSource(new Example()); 205 | 206 | var testResult = testMethod.Invoke(tester, new object[] { cache }); 207 | if (testResult is Task) 208 | { 209 | await (testResult as Task); 210 | } 211 | } 212 | } 213 | 214 | } 215 | 216 | 217 | } 218 | -------------------------------------------------------------------------------- /FluentCache/Strategies/CacheStrategyParameterized.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FluentCache.Strategies 8 | { 9 | /// 10 | /// A strategy to access or modify strongly-typed data in a Cache that depends on one or more parameters 11 | /// 12 | public sealed class CacheStrategyParameterized : CacheStrategyIncomplete 13 | { 14 | internal CacheStrategyParameterized(ICache cache, string baseKey) 15 | : base(cache, baseKey) 16 | { 17 | } 18 | 19 | /// 20 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 21 | /// 22 | /// A delegate that specifies how the value is to be retrieved 23 | /// An updated cache strategy that includes the retrieval strategy 24 | public CacheStrategy RetrieveUsing(Func retrieve) 25 | { 26 | CacheStrategy strategy = base.Complete(); 27 | var p1 = strategy.GetParameter(0); 28 | 29 | strategy.RetrieveCallback = () => retrieve(p1); 30 | 31 | return strategy; 32 | } 33 | 34 | /// 35 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 36 | /// 37 | /// An asynchronous delegate that specifies how the value is to be retrieved 38 | /// An updated cache strategy that includes the retrieval strategy 39 | public CacheStrategyAsync RetrieveUsingAsync(Func> retrieve) 40 | { 41 | CacheStrategyAsync strategy = base.CompleteAsync(); 42 | var p1 = strategy.GetParameter(0); 43 | 44 | strategy.RetrieveCallback = () => retrieve(p1); 45 | 46 | return strategy; 47 | } 48 | } 49 | 50 | /// 51 | /// A strategy to access or modify strongly-typed data in a Cache that depends on one or more parameters 52 | /// 53 | public sealed class CacheStrategyParameterized : CacheStrategyIncomplete 54 | { 55 | internal CacheStrategyParameterized(ICache cache, string baseKey) 56 | : base(cache, baseKey) 57 | { 58 | } 59 | 60 | /// 61 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 62 | /// 63 | /// A delegate that specifies how the value is to be retrieved 64 | /// An updated cache strategy that includes the retrieval strategy 65 | public CacheStrategy Retrieve(Func retrieve) 66 | { 67 | CacheStrategy strategy = base.Complete(); 68 | var p1 = strategy.GetParameter(0); 69 | var p2 = strategy.GetParameter(1); 70 | 71 | 72 | strategy.RetrieveCallback = () => retrieve(p1, p2); 73 | 74 | return strategy; 75 | } 76 | 77 | /// 78 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 79 | /// 80 | /// An asynchronous delegate that specifies how the value is to be retrieved 81 | /// An updated cache strategy that includes the retrieval strategy 82 | public CacheStrategyAsync RetrieveAsync(Func> retrieve) 83 | { 84 | CacheStrategyAsync strategy = base.CompleteAsync(); 85 | var p1 = strategy.GetParameter(0); 86 | var p2 = strategy.GetParameter(1); 87 | 88 | strategy.RetrieveCallback = () => retrieve(p1, p2); 89 | 90 | return strategy; 91 | } 92 | } 93 | 94 | /// 95 | /// A strategy to access or modify strongly-typed data in a Cache that depends on one or more parameters 96 | /// 97 | public sealed class CacheStrategyParameterized : CacheStrategyIncomplete 98 | { 99 | internal CacheStrategyParameterized(ICache cache, string baseKey) 100 | : base(cache, baseKey) 101 | { 102 | } 103 | 104 | /// 105 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 106 | /// 107 | /// A delegate that specifies how the value is to be retrieved 108 | /// An updated cache strategy that includes the retrieval strategy 109 | public CacheStrategy RetrieveUsing(Func retrieve) 110 | { 111 | CacheStrategy strategy = base.Complete(); 112 | var p1 = strategy.GetParameter(0); 113 | var p2 = strategy.GetParameter(1); 114 | var p3 = strategy.GetParameter(2); 115 | 116 | 117 | strategy.RetrieveCallback = () => retrieve(p1, p2, p3); 118 | 119 | return strategy; 120 | } 121 | 122 | /// 123 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 124 | /// 125 | /// An asynchronous delegate that specifies how the value is to be retrieved 126 | /// An updated cache strategy that includes the retrieval strategy 127 | public CacheStrategyAsync RetrieveUsingAsync(Func> retrieve) 128 | { 129 | CacheStrategyAsync strategy = base.CompleteAsync(); 130 | var p1 = strategy.GetParameter(0); 131 | var p2 = strategy.GetParameter(1); 132 | var p3 = strategy.GetParameter(2); 133 | 134 | strategy.RetrieveCallback = () => retrieve(p1, p2, p3); 135 | 136 | return strategy; 137 | } 138 | } 139 | 140 | /// 141 | /// A strategy to access or modify strongly-typed data in a Cache that depends on one or more parameters 142 | /// 143 | public sealed class CacheStrategyParameterized : CacheStrategyIncomplete 144 | { 145 | internal CacheStrategyParameterized(ICache cache, string baseKey) 146 | : base(cache, baseKey) 147 | { 148 | } 149 | 150 | 151 | /// 152 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 153 | /// 154 | /// A delegate that specifies how the value is to be retrieved 155 | /// An updated cache strategy that includes the retrieval strategy 156 | public CacheStrategy RetrieveUsing(Func retrieve) 157 | { 158 | CacheStrategy strategy = base.Complete(); 159 | var p1 = strategy.GetParameter(0); 160 | var p2 = strategy.GetParameter(1); 161 | var p3 = strategy.GetParameter(2); 162 | var p4 = strategy.GetParameter(3); 163 | 164 | strategy.RetrieveCallback = () => retrieve(p1, p2, p3, p4); 165 | 166 | return strategy; 167 | } 168 | 169 | /// 170 | /// Specifies a data retrieval strategy if the desired value does not exist in the cache or is invalid 171 | /// 172 | /// An asynchronous delegate that specifies how the value is to be retrieved 173 | /// An updated cache strategy that includes the retrieval strategy 174 | public CacheStrategyAsync RetrieveUsingAsync(Func> retrieve) 175 | { 176 | CacheStrategyAsync strategy = base.CompleteAsync(); 177 | var p1 = strategy.GetParameter(0); 178 | var p2 = strategy.GetParameter(1); 179 | var p3 = strategy.GetParameter(2); 180 | var p4 = strategy.GetParameter(3); 181 | 182 | strategy.RetrieveCallback = () => retrieve(p1, p2, p3, p4); 183 | 184 | return strategy; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /FluentCache.Test/Implementations/CacheTester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentCache; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using FluentCache.Strategies; 9 | using System.Reflection; 10 | 11 | namespace FluentCache.Test.Implementations 12 | { 13 | public class CacheTester 14 | { 15 | public class Example 16 | { 17 | public double CalculateSomeWork() 18 | { 19 | return Math.Sqrt(1234); 20 | } 21 | 22 | public double CalculateSomeWork(int power) 23 | { 24 | return Math.Pow(Math.E, power); 25 | } 26 | 27 | public async Task CalculateSomeWorkAsync() 28 | { 29 | await Task.Delay(TimeSpan.FromSeconds(.125)); 30 | return CalculateSomeWork(); 31 | } 32 | 33 | public async Task CalculateSomeWorkAsync(int power) 34 | { 35 | await Task.Delay(TimeSpan.FromSeconds(.125)); 36 | return CalculateSomeWork(power); 37 | 38 | } 39 | 40 | } 41 | 42 | internal CacheTester() 43 | { 44 | } 45 | 46 | public void ThisMethod_GetValue_Default(Cache cache) 47 | { 48 | double result = cache.ThisMethod() 49 | .GetValue(); 50 | 51 | Assert.AreEqual(default(double), result, "Should return default since we didn't specify a retrieval mechanism"); 52 | 53 | } 54 | 55 | public void Method_Get(Cache cache) 56 | { 57 | CachedValue result = cache.Method(c => c.CalculateSomeWork()) 58 | .Get(); 59 | 60 | Assert.IsNotNull(result, "Should not return null since we specified a retrieval mechanism"); 61 | } 62 | 63 | public void Method_GetValue(Cache cache) 64 | { 65 | double result = cache.Method(c => c.CalculateSomeWork()) 66 | .GetValue(); 67 | 68 | Assert.AreNotEqual(default(double), "Should not return 0 since we specified a retrieval mechanism"); 69 | } 70 | 71 | public void Method_Get_InitialVersion(Cache cache) 72 | { 73 | var strat = cache.Method(c => c.CalculateSomeWork()); 74 | strat.ClearValue(); 75 | 76 | CachedValue result = strat.Get(); 77 | 78 | Assert.AreEqual(0L, result.Version, "Initial Get should have 0 as the version"); 79 | 80 | } 81 | 82 | public void Method_Get_MultipleSameVersion(Cache cache) 83 | { 84 | CacheStrategy cacheStrategy = cache.Method(c => c.CalculateSomeWork()); 85 | for (int i = 0; i < 10; i++) 86 | { 87 | CachedValue result = cacheStrategy.Get(); 88 | Assert.AreEqual(0L, result.Version, "All subsequent calls should retrieve the existing version"); 89 | } 90 | } 91 | 92 | public void Method_Get_Invalidation(Cache cache) 93 | { 94 | bool isInvalid = false; 95 | Func, CacheValidationResult> validate = existing => 96 | { 97 | if (isInvalid) 98 | return CacheValidationResult.Invalid; 99 | else 100 | return CacheValidationResult.Unknown; 101 | }; 102 | 103 | CacheStrategy strategy = cache.Method(c => c.CalculateSomeWork()) 104 | .Validate(validate); 105 | 106 | CachedValue result0 = strategy.Get(); 107 | 108 | 109 | Assert.AreEqual(0L, result0.Version); 110 | 111 | isInvalid = true; 112 | CachedValue result1 = strategy.Get(); 113 | CachedValue result2 = strategy.Get(); 114 | 115 | Assert.AreEqual(1L, result1.Version); 116 | Assert.AreEqual(2L, result2.Version); 117 | 118 | } 119 | 120 | public async Task Method_ExpireAfter_VersionReset(Cache cache) 121 | { 122 | DateTime now = DateTime.UtcNow; 123 | TimeSpan wait = TimeSpan.FromSeconds(.5); 124 | 125 | CacheStrategy strategy = cache.Method(c => c.CalculateSomeWork()) 126 | .ExpireAfter(wait); 127 | 128 | strategy.ClearValue(); 129 | 130 | CachedValue result0 = strategy.Get(); 131 | 132 | await Task.Delay(wait + wait); 133 | 134 | CachedValue result1 = strategy.Get(); 135 | 136 | Assert.AreEqual(0, result0.Version, "The first call should be for a new value with version 0"); 137 | Assert.AreNotEqual(result0.CachedDate, result1.CachedDate, "The 2nd call should have expired and caused a new item to be inserted"); 138 | Assert.AreEqual(0, result1.Version, "The 2nd call should have expired. Expired items are removed, so we don't expect the version to have incremented"); 139 | } 140 | 141 | public async Task Method_ExpireAfter_Callback(Cache cache) 142 | { 143 | TimeSpan wait1 = TimeSpan.FromSeconds(1.25); 144 | TimeSpan wait2 = TimeSpan.FromSeconds(0.75); 145 | 146 | Func waitCallback = d => d < 1.0 ? wait1 : wait2; 147 | 148 | CacheStrategy strategy = cache.Method(c => c.CalculateSomeWork()) 149 | .ExpireAfter(waitCallback); 150 | 151 | DateTime firstCacheDate = default(DateTime); 152 | strategy.Get(); 153 | for (int i = 1; i < 10; i++) 154 | { 155 | CachedValue result = strategy.Get(); 156 | Assert.AreEqual(0L, result.Version, "These calls should be cached"); 157 | 158 | var wait = waitCallback(result.Value); 159 | await Task.Delay(TimeSpan.FromSeconds(wait.TotalSeconds / 5.0)); 160 | 161 | firstCacheDate = result.CachedDate; 162 | } 163 | 164 | await Task.Delay(wait1 + wait2); 165 | 166 | CachedValue expiredResult = strategy.Get(); 167 | 168 | Assert.AreNotEqual(firstCacheDate, expiredResult.CachedDate, "This call should have expired after waiting"); 169 | } 170 | 171 | public async Task Method_ExpireAfter(Cache cache) 172 | { 173 | TimeSpan wait = TimeSpan.FromSeconds(1); 174 | 175 | CacheStrategy strategy = cache.Method(c => c.CalculateSomeWork()) 176 | .ExpireAfter(wait); 177 | 178 | DateTime firstCacheDate = default(DateTime); 179 | strategy.Get(); 180 | for (int i = 1; i < 10; i++) 181 | { 182 | CachedValue result = strategy.Get(); 183 | Assert.AreEqual(0L, result.Version, "These calls should be cached"); 184 | 185 | await Task.Delay(TimeSpan.FromSeconds(wait.TotalSeconds / 5.0)); 186 | 187 | firstCacheDate = result.CachedDate; 188 | } 189 | 190 | await Task.Delay(wait); 191 | 192 | CachedValue expiredResult = strategy.Get(); 193 | 194 | Assert.AreNotEqual(firstCacheDate, expiredResult.CachedDate, "This call should have expired after waiting for {0}", wait); 195 | } 196 | 197 | public void Method_Parameterized(Cache cache) 198 | { 199 | double result = cache.Method(c => c.CalculateSomeWork(3)) 200 | .GetValue(); 201 | 202 | Assert.AreNotEqual(0, result, "This should be a calculated value"); 203 | } 204 | 205 | public async Task Method_Async_GetValue_NotDefault(Cache cache) 206 | { 207 | 208 | CacheStrategyAsync strategy = cache.Method(c => c.CalculateSomeWorkAsync(3)); 209 | 210 | double value = await strategy.GetValueAsync(); 211 | 212 | Assert.AreNotEqual(0d, value); 213 | } 214 | 215 | public async Task Method_Async_Parameters(Cache cache) 216 | { 217 | CacheStrategyAsync strategy = cache.Method(c => c.CalculateSomeWorkAsync(3)); 218 | 219 | CachedValue result1 = await strategy.GetAsync(); 220 | CachedValue result2 = await strategy.GetAsync(); 221 | 222 | Assert.AreEqual(result1.Version, result2.Version); 223 | } 224 | 225 | 226 | 227 | public static async Task TestCacheAsync(Func cacheFactory) 228 | { 229 | CacheTester tester = new CacheTester(); 230 | 231 | var methods = tester.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); 232 | foreach (var testMethod in methods) 233 | { 234 | var cache = cacheFactory().WithSource(new Example()); 235 | 236 | var testResult = testMethod.Invoke(tester, new object[] { cache }); 237 | if (testResult is Task) 238 | { 239 | await (testResult as Task); 240 | } 241 | } 242 | } 243 | 244 | } 245 | 246 | 247 | } 248 | -------------------------------------------------------------------------------- /FluentCache.Test.Nuget/FluentCache.Test.Nuget.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FD2BADA7-DF67-4C0D-8BEF-01397191E9EC} 8 | Library 9 | Properties 10 | FluentCache.Test.Nuget 11 | FluentCache.Test.Nuget 12 | v4.6.1 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | 42 | ..\packages\FluentCache.4.0.0.2\lib\netstandard1.1\FluentCache.dll 43 | 44 | 45 | ..\packages\FluentCache.Microsoft.Extensions.Caching.Abstractions.4.0.0.2\lib\netstandard2.0\FluentCache.Microsoft.Extensions.Caching.Abstractions.dll 46 | 47 | 48 | ..\packages\FluentCache.Microsoft.Extensions.Caching.Memory.4.0.0.2\lib\netstandard2.0\FluentCache.Microsoft.Extensions.Caching.Memory.dll 49 | 50 | 51 | ..\packages\FluentCache.Microsoft.Extensions.Caching.Redis.4.0.0.2\lib\netstandard2.0\FluentCache.Microsoft.Extensions.Caching.Redis.dll 52 | 53 | 54 | ..\packages\FluentCache.RuntimeCaching.4.0.0.2\lib\netstandard2.0\FluentCache.RuntimeCaching.dll 55 | 56 | 57 | 58 | ..\packages\Microsoft.Extensions.Caching.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll 59 | 60 | 61 | ..\packages\Microsoft.Extensions.Caching.Memory.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll 62 | 63 | 64 | ..\packages\Microsoft.Extensions.Caching.Redis.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Redis.dll 65 | 66 | 67 | ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll 68 | 69 | 70 | ..\packages\Microsoft.Extensions.Options.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll 71 | 72 | 73 | ..\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll 74 | 75 | 76 | ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 77 | 78 | 79 | ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 80 | 81 | 82 | ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 83 | 84 | 85 | ..\packages\StackExchange.Redis.StrongName.1.2.4\lib\net46\StackExchange.Redis.StrongName.dll 86 | 87 | 88 | 89 | 90 | 91 | ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll 92 | 93 | 94 | 95 | 96 | 97 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /FluentCache.Test/FluentCache.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {5FA1B9E5-45DD-4B93-A25B-F7079FC1901F} 7 | Library 8 | Properties 9 | FluentCache.Test 10 | FluentCache.Test 11 | v4.6.1 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | SAK 20 | SAK 21 | SAK 22 | SAK 23 | 24 | 25 | 26 | true 27 | full 28 | false 29 | bin\Debug\ 30 | DEBUG;TRACE 31 | prompt 32 | 4 33 | 34 | 35 | pdbonly 36 | true 37 | bin\Release\ 38 | TRACE 39 | prompt 40 | 4 41 | 42 | 43 | true 44 | 45 | 46 | signature.pfx 47 | 48 | 49 | 50 | 51 | ..\packages\Microsoft.Extensions.Caching.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll 52 | 53 | 54 | ..\packages\Microsoft.Extensions.Caching.Memory.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll 55 | 56 | 57 | ..\packages\Microsoft.Extensions.Caching.Redis.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Redis.dll 58 | 59 | 60 | ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll 61 | 62 | 63 | ..\packages\Microsoft.Extensions.Options.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll 64 | 65 | 66 | ..\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll 67 | 68 | 69 | ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 70 | 71 | 72 | ..\packages\StackExchange.Redis.StrongName.1.2.4\lib\net46\StackExchange.Redis.StrongName.dll 73 | 74 | 75 | 76 | 77 | 78 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | {a86de79d-0bf9-44b8-aa06-edd8a5d32195} 117 | FluentCache.Microsoft.Extensions.Caching.Abstractions 118 | 119 | 120 | {20dec89c-7f8d-4f2a-87bc-2b5107491818} 121 | FluentCache.Microsoft.Extensions.Caching.Memory 122 | 123 | 124 | {f0ec29e0-740f-4f74-9453-5b6cf69075c3} 125 | FluentCache.Microsoft.Extensions.Caching.Redis 126 | 127 | 128 | {1d658a84-86f9-4128-a898-9d3b40a94407} 129 | FluentCache.RuntimeCaching 130 | 131 | 132 | {5dbfd0c2-a7e5-4f16-bbe0-633e38b6eb6d} 133 | FluentCache 134 | 135 | 136 | 137 | 138 | 139 | 140 | False 141 | 142 | 143 | False 144 | 145 | 146 | False 147 | 148 | 149 | False 150 | 151 | 152 | 153 | 154 | 155 | 156 | 163 | --------------------------------------------------------------------------------