diff --git a/.gitignore b/.gitignore index 940794e6..c01b145b 100644 --- a/.gitignore +++ b/.gitignore @@ -286,3 +286,7 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs + +#MACOS + +.DS_Store diff --git a/EasyCaching.sln b/EasyCaching.sln new file mode 100644 index 00000000..92be08e5 --- /dev/null +++ b/EasyCaching.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A0F5CC7E-155F-4726-8DEB-E966950B3FE9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F88D727A-9F9C-43D9-90B1-D4A02BF8BC98}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EBB55F65-7D07-4281-8D5E-7B0CA88E1AD0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Core", "src\EasyCaching.Core\EasyCaching.Core.csproj", "{CE61FAA2-0233-451C-991D-4222ED61C84B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Memory", "src\EasyCaching.Memory\EasyCaching.Memory.csproj", "{B9490432-737B-4518-B851-9D40FD29B392}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Extensions", "src\EasyCaching.Extensions\EasyCaching.Extensions.csproj", "{679ABDB5-7218-4B4E-A632-10612750CD74}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CE61FAA2-0233-451C-991D-4222ED61C84B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE61FAA2-0233-451C-991D-4222ED61C84B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE61FAA2-0233-451C-991D-4222ED61C84B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE61FAA2-0233-451C-991D-4222ED61C84B}.Release|Any CPU.Build.0 = Release|Any CPU + {B9490432-737B-4518-B851-9D40FD29B392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9490432-737B-4518-B851-9D40FD29B392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9490432-737B-4518-B851-9D40FD29B392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9490432-737B-4518-B851-9D40FD29B392}.Release|Any CPU.Build.0 = Release|Any CPU + {679ABDB5-7218-4B4E-A632-10612750CD74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {679ABDB5-7218-4B4E-A632-10612750CD74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {679ABDB5-7218-4B4E-A632-10612750CD74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {679ABDB5-7218-4B4E-A632-10612750CD74}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CE61FAA2-0233-451C-991D-4222ED61C84B} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} + {B9490432-737B-4518-B851-9D40FD29B392} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} + {679ABDB5-7218-4B4E-A632-10612750CD74} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} + EndGlobalSection +EndGlobal diff --git a/src/EasyCaching.Core/CacheEntry.cs b/src/EasyCaching.Core/CacheEntry.cs new file mode 100644 index 00000000..0c41deeb --- /dev/null +++ b/src/EasyCaching.Core/CacheEntry.cs @@ -0,0 +1,53 @@ +namespace EasyCaching.Core +{ + using System; + + /// + /// Cache entry. + /// + public class CacheEntry + { + /// + /// Initializes a new instance of the class. + /// + /// Cache key. + /// Cache value. + /// Absolute expiration relative to now. + public CacheEntry(string cacheKey, object cacheValue, TimeSpan absoluteExpirationRelativeToNow) + { + if (string.IsNullOrWhiteSpace(cacheKey)) + throw new ArgumentNullException(nameof(cacheKey)); + + if (cacheValue == null) + throw new ArgumentNullException(nameof(cacheValue)); + + if (absoluteExpirationRelativeToNow <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException( + nameof(absoluteExpirationRelativeToNow), + absoluteExpirationRelativeToNow, + "The relative expiration value must be positive."); + + this.CacheKey = cacheKey; + this.CacheValue = cacheValue; + this.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow; + } + + /// + /// Gets the cache key. + /// + /// The cache key. + public string CacheKey { get; private set; } + + /// + /// Gets the cache value. + /// + /// The cache value. + public object CacheValue { get; private set; } + + /// + /// Gets the absolute expiration relative to now. + /// + /// The absolute expiration relative to now. + public TimeSpan AbsoluteExpirationRelativeToNow { get; private set; } + } +} diff --git a/src/EasyCaching.Core/EasyCaching.Core.csproj b/src/EasyCaching.Core/EasyCaching.Core.csproj new file mode 100644 index 00000000..810abdba --- /dev/null +++ b/src/EasyCaching.Core/EasyCaching.Core.csproj @@ -0,0 +1,10 @@ + + + + netstandard2.0 + + + + + + diff --git a/src/EasyCaching.Core/EasyCachingManager.cs b/src/EasyCaching.Core/EasyCachingManager.cs new file mode 100644 index 00000000..1a1cecd5 --- /dev/null +++ b/src/EasyCaching.Core/EasyCachingManager.cs @@ -0,0 +1,115 @@ +namespace EasyCaching.Core +{ + using System; + using System.Collections.Generic; + + /// + /// Easycaching manager. + /// + public class EasyCachingManager + { + /// + /// The caching providers. + /// + private readonly IEasyCachingProvider[] _cachingProviders; + + /// + /// Initializes a new instance of the class. + /// + /// Caching providers. + public EasyCachingManager(IEasyCachingProvider[] cachingProviders) + { + if (cachingProviders == null || cachingProviders.Length <= 0) + { + throw new ArgumentNullException(nameof(cachingProviders)); + } + + this._cachingProviders = cachingProviders; + } + + /// + /// Get cacheValue by specified cacheKey. + /// + /// The cacheValue. + /// Cache key. + public object Get(string cacheKey) + { + if (string.IsNullOrWhiteSpace(cacheKey)) + { + throw new ArgumentNullException(nameof(cacheKey)); + } + + object result = null; + + var missed = new HashSet(); + + for (int i = 0; i < _cachingProviders.Length; i++) + { + var cachingProvider = _cachingProviders[i]; + + var cacheValue = cachingProvider.Get(cacheKey); + + if (cacheValue != null) + { + result = cacheValue; + break; + } + else + { + missed.Add(i); + } + } + + if (result == null) + { + result = new EmptyCachingObject(); + } + + //handle missed cache + foreach (var item in missed) + { + var cachingProvider = _cachingProviders[item]; + + var cacheEntry = new CacheEntry(cacheKey, + result, + result.GetType().Equals(typeof(EmptyCachingObject)) + ? TimeSpan.FromSeconds(120) + : TimeSpan.FromSeconds(3600 + new Random().Next(1, 120))); + cachingProvider.Set(cacheEntry); + } + + return result; + } + + /// + /// Set the specified cacheKey, cacheValue and absoluteExpirationRelativeToNow. + /// + /// The set. + /// Cache key. + /// Cache value. + /// Absolute expiration relative to now. + public void Set(string cacheKey, object cacheValue, TimeSpan absoluteExpirationRelativeToNow) + { + var cacheEntry = new CacheEntry(cacheKey, cacheValue, absoluteExpirationRelativeToNow); + this.Set(cacheEntry); + } + + /// + /// Set the specified cacheEntry. + /// + /// The set. + /// Cache entry. + public void Set(CacheEntry cacheEntry) + { + for (int i = 0; i < _cachingProviders.Length; i++) + { + var cachingProvider = _cachingProviders[i]; + + cacheEntry.AbsoluteExpirationRelativeToNow.Add(TimeSpan.FromSeconds(new Random().Next(1, 120))); + + cachingProvider.Set(cacheEntry); + } + } + + } +} \ No newline at end of file diff --git a/src/EasyCaching.Core/EmptyCachingObject.cs b/src/EasyCaching.Core/EmptyCachingObject.cs new file mode 100644 index 00000000..67144e54 --- /dev/null +++ b/src/EasyCaching.Core/EmptyCachingObject.cs @@ -0,0 +1,10 @@ +namespace EasyCaching.Core +{ + /// + /// Empty caching object. + /// + public class EmptyCachingObject + { + + } +} diff --git a/src/EasyCaching.Core/IEasyCachingProvider.cs b/src/EasyCaching.Core/IEasyCachingProvider.cs new file mode 100644 index 00000000..acb00e2c --- /dev/null +++ b/src/EasyCaching.Core/IEasyCachingProvider.cs @@ -0,0 +1,22 @@ +namespace EasyCaching.Core +{ + /// + /// EasyCaching provider. + /// + public interface IEasyCachingProvider + { + /// + /// Set the specified cacheEntry. + /// + /// + /// Cache entry. + void Set(CacheEntry cacheEntry); + + /// + /// Get the specified cacheKey. + /// + /// The cache value. + /// Cache key. + object Get(string cacheKey); + } +} diff --git a/src/EasyCaching.Core/Internal/ICachable.cs b/src/EasyCaching.Core/Internal/ICachable.cs new file mode 100644 index 00000000..fb71c53e --- /dev/null +++ b/src/EasyCaching.Core/Internal/ICachable.cs @@ -0,0 +1,14 @@ +namespace EasyCaching.Core.Internal +{ + /// + /// Cachable. + /// + public interface ICachable + { + /// + /// Gets the cache key. + /// + /// The cache key. + string CacheKey { get; } + } +} diff --git a/src/EasyCaching.Extensions/CachingInterceptor.cs b/src/EasyCaching.Extensions/CachingInterceptor.cs new file mode 100644 index 00000000..b59fdcfc --- /dev/null +++ b/src/EasyCaching.Extensions/CachingInterceptor.cs @@ -0,0 +1,173 @@ +namespace EasyCaching.Extensions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Threading.Tasks; + using AspectCore.DynamicProxy; + using AspectCore.Injector; + using EasyCaching.Core.Internal; + using EasyCaching.Core; + + /// + /// Caching interceptor. + /// + public class CachingInterceptor : AbstractInterceptorAttribute + { + + /// + /// Gets or sets the absolute expiration. + /// + /// The absolute expiration. + public int AbsoluteExpiration { get; set; } = 30; + + + /// + /// Gets or sets the parameter count to generate cache key. + /// + /// The parameter count. + public int ParamCount { get; set; } = 5; + + + /// + /// Gets or sets the cache provider. + /// + /// The cache provider. + [FromContainer] + public IEasyCachingProvider CacheProvider { get; set; } + + /// + /// The link char of cache key. + /// + private char _linkChar = ':'; + + /// + /// Invoke the specified context and next. + /// + /// The invoke. + /// Context. + /// Next. + public async override Task Invoke(AspectContext context, AspectDelegate next) + { + var interceptorAttribute = GetInterceptorAttributeInfo(context.ServiceMethod); + if (interceptorAttribute != null) + { + await ProceedCaching(context, next, interceptorAttribute); + } + else + { + await next(context); + } + } + + /// + /// Gets the QC aching attribute info. + /// + /// The QC aching attribute info. + /// Method. + private CachingInterceptor GetInterceptorAttributeInfo(MethodInfo method) + { + return method.GetCustomAttributes(true).FirstOrDefault(x => x.GetType() == typeof(CachingInterceptor)) as CachingInterceptor; + } + + /// + /// Proceeds the caching. + /// + /// The caching. + /// Context. + /// Next. + /// Attribute. + private async Task ProceedCaching(AspectContext context, AspectDelegate next, CachingInterceptor attribute) + { + var cacheKey = GenerateCacheKey(context, attribute.ParamCount); + + var cacheValue = CacheProvider.Get(cacheKey); + if (cacheValue != null) + { + context.ReturnValue = cacheValue; + return; + } + + await next(context); + + if (!string.IsNullOrWhiteSpace(cacheKey)) + { + CacheEntry entry = new CacheEntry(cacheKey, context.ReturnValue, TimeSpan.FromSeconds(attribute.AbsoluteExpiration)); + CacheProvider.Set(entry); + } + } + + /// + /// Generates the cache key. + /// + /// The cache key. + /// Context. + /// Parameter count. + private string GenerateCacheKey(AspectContext context, int paramCount) + { + var typeName = context.ServiceMethod.DeclaringType.Name; + var methodName = context.ServiceMethod.Name; + var methodArguments = this.FormatArgumentsToPartOfCacheKey(context.ServiceMethod.GetParameters(), paramCount); + + return this.GenerateCacheKey(typeName, methodName, methodArguments); + } + + /// + /// Generates the cache key. + /// + /// The cache key. + /// Type name. + /// Method name. + /// Parameters. + private string GenerateCacheKey(string typeName, string methodName, IList parameters) + { + var builder = new StringBuilder(); + + builder.Append(typeName); + builder.Append(_linkChar); + + builder.Append(methodName); + builder.Append(_linkChar); + + foreach (var param in parameters) + { + builder.Append(param); + builder.Append(_linkChar); + } + + return builder.ToString().TrimEnd(_linkChar); + } + + /// + /// Formats the arguments to part of cache key. + /// + /// The arguments to part of cache key. + /// Method arguments. + /// Max parameter count. + private IList FormatArgumentsToPartOfCacheKey(IList methodArguments, int paramCount = 5) + { + return methodArguments.Select(this.GetArgumentValue).Take(paramCount).ToList(); + } + + /// + /// Gets the argument value. + /// + /// The argument value. + /// Argument. + private string GetArgumentValue(object arg) + { + if (arg is int || arg is long || arg is string) + return arg.ToString(); + + if (arg is DateTime) + return ((DateTime)arg).ToString("yyyyMMddHHmmss"); + + if (arg is ICachable) + return ((ICachable)arg).CacheKey; + + return null; + } + } +} diff --git a/src/EasyCaching.Extensions/EasyCaching.Extensions.csproj b/src/EasyCaching.Extensions/EasyCaching.Extensions.csproj new file mode 100644 index 00000000..74044a6a --- /dev/null +++ b/src/EasyCaching.Extensions/EasyCaching.Extensions.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + + + + + + + + + + diff --git a/src/EasyCaching.Memory/EasyCaching.Memory.csproj b/src/EasyCaching.Memory/EasyCaching.Memory.csproj new file mode 100644 index 00000000..dd07f2d3 --- /dev/null +++ b/src/EasyCaching.Memory/EasyCaching.Memory.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + + + + + + + + + diff --git a/src/EasyCaching.Memory/MemoryCachingProvider.cs b/src/EasyCaching.Memory/MemoryCachingProvider.cs new file mode 100644 index 00000000..cbc9fd56 --- /dev/null +++ b/src/EasyCaching.Memory/MemoryCachingProvider.cs @@ -0,0 +1,49 @@ +namespace EasyCaching.Memory +{ + using System; + using EasyCaching.Core; + using Microsoft.Extensions.Caching.Memory; + + /// + /// MemoryCaching provider. + /// + public class MemoryCachingProvider : IEasyCachingProvider + { + /// + /// The MemoryCache. + /// + private readonly IMemoryCache _cache; + + /// + /// Initializes a new instance of the class. + /// + /// Microsoft MemoryCache. + public MemoryCachingProvider(IMemoryCache cache) + { + this._cache = cache; + } + + /// + /// Get cacheValue by specified cacheKey. + /// + /// The cacheValue. + /// Cache key. + public object Get(string cacheKey) + { + if (string.IsNullOrWhiteSpace(cacheKey)) + throw new ArgumentNullException(nameof(cacheKey)); + + return _cache.Get(cacheKey); + } + + /// + /// Set the specified cacheEntry. + /// + /// The set. + /// Cache entry. + public void Set(CacheEntry cacheEntry) + { + _cache.Set(cacheEntry.CacheKey, cacheEntry.CacheValue, cacheEntry.AbsoluteExpirationRelativeToNow); + } + } +}