Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Caching) : Fix cache key formatting #285

Merged
merged 13 commits into from
Oct 19, 2022
Merged
Original file line number Diff line number Diff line change
@@ -5,51 +5,75 @@ namespace Masa.BuildingBlocks.Caching;

public abstract class CacheClientBase : ICacheClient
{
public abstract T? Get<T>(string key);
public abstract T? Get<T>(string key, Action<CacheOptions>? action = null);

public abstract Task<T?> GetAsync<T>(string key);
public abstract Task<T?> GetAsync<T>(string key, Action<CacheOptions>? action = null);

public IEnumerable<T?> GetList<T>(params string[] keys)
=> GetList<T>(GetKeys(keys));

public abstract IEnumerable<T?> GetList<T>(IEnumerable<string> keys);
public abstract IEnumerable<T?> GetList<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

public Task<IEnumerable<T?>> GetListAsync<T>(params string[] keys)
=> GetListAsync<T>(GetKeys(keys));

public abstract Task<IEnumerable<T?>> GetListAsync<T>(IEnumerable<string> keys);
public abstract Task<IEnumerable<T?>> GetListAsync<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

public virtual void Set<T>(string key, T value, DateTimeOffset? absoluteExpiration)
=> Set(key, value, new CacheEntryOptions(absoluteExpiration));
public virtual void Set<T>(string key, T value, DateTimeOffset? absoluteExpiration, Action<CacheOptions>? action = null)
=> Set(key, value, new CacheEntryOptions(absoluteExpiration), action);

public virtual void Set<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow)
=> Set(key, value, new CacheEntryOptions(absoluteExpirationRelativeToNow));
public virtual void Set<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null)
=> Set(key, value, new CacheEntryOptions(absoluteExpirationRelativeToNow), action);

public abstract void Set<T>(string key, T value, CacheEntryOptions? options = null);
public abstract void Set<T>(string key, T value, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

public virtual Task SetAsync<T>(string key, T value, DateTimeOffset? absoluteExpiration)
=> SetAsync(key, value, new CacheEntryOptions(absoluteExpiration));
public virtual Task SetAsync<T>(string key, T value, DateTimeOffset? absoluteExpiration, Action<CacheOptions>? action = null)
=> SetAsync(key, value, new CacheEntryOptions(absoluteExpiration), action);

public virtual Task SetAsync<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow)
=> SetAsync(key, value, new CacheEntryOptions(absoluteExpirationRelativeToNow));
public virtual Task SetAsync<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null)
=> SetAsync(key, value, new CacheEntryOptions(absoluteExpirationRelativeToNow), action);

public abstract Task SetAsync<T>(string key, T value, CacheEntryOptions? options = null);
public abstract Task SetAsync<T>(string key, T value, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

public virtual void SetList<T>(Dictionary<string, T?> keyValues, DateTimeOffset? absoluteExpiration)
=> SetList(keyValues, new CacheEntryOptions(absoluteExpiration));
public virtual void SetList<T>(Dictionary<string, T?> keyValues,
DateTimeOffset? absoluteExpiration,
Action<CacheOptions>? action = null)
=> SetList(keyValues, new CacheEntryOptions(absoluteExpiration), action);

public virtual void SetList<T>(Dictionary<string, T?> keyValues, TimeSpan? absoluteExpirationRelativeToNow)
=> SetList(keyValues, new CacheEntryOptions(absoluteExpirationRelativeToNow));
public virtual void SetList<T>(Dictionary<string, T?> keyValues,
TimeSpan? absoluteExpirationRelativeToNow,
Action<CacheOptions>? action = null)
=> SetList(keyValues, new CacheEntryOptions(absoluteExpirationRelativeToNow), action);

public abstract void SetList<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null);
public abstract void SetList<T>(Dictionary<string, T?> keyValues,
CacheEntryOptions? options = null,
Action<CacheOptions>? action = null);

public virtual Task SetListAsync<T>(Dictionary<string, T?> keyValues, DateTimeOffset? absoluteExpiration)
=> SetListAsync(keyValues, new CacheEntryOptions(absoluteExpiration));
public virtual Task SetListAsync<T>(Dictionary<string, T?> keyValues,
DateTimeOffset? absoluteExpiration,
Action<CacheOptions>? action = null)
=> SetListAsync(keyValues, new CacheEntryOptions(absoluteExpiration), action);

public virtual Task SetListAsync<T>(Dictionary<string, T?> keyValues, TimeSpan? absoluteExpirationRelativeToNow)
=> SetListAsync(keyValues, new CacheEntryOptions(absoluteExpirationRelativeToNow));
public virtual Task SetListAsync<T>(Dictionary<string, T?> keyValues,
TimeSpan? absoluteExpirationRelativeToNow,
Action<CacheOptions>? action = null)
=> SetListAsync(keyValues, new CacheEntryOptions(absoluteExpirationRelativeToNow), action);

public abstract Task SetListAsync<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null);
public abstract Task SetListAsync<T>(Dictionary<string, T?> keyValues,
CacheEntryOptions? options = null,
Action<CacheOptions>? action = null);

public abstract void Remove<T>(string key, Action<CacheOptions>? action = null);

public abstract void Remove<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

public abstract Task RemoveAsync<T>(string key, Action<CacheOptions>? action = null);

public abstract Task RemoveAsync<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

public abstract void Refresh<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

public abstract Task RefreshAsync<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

protected static IEnumerable<string> GetKeys(params string[] keys) => keys;
}
Original file line number Diff line number Diff line change
@@ -5,37 +5,44 @@ namespace Masa.BuildingBlocks.Caching;

public abstract class DistributedCacheClientBase : CacheClientBase, IDistributedCacheClient
{
public abstract T? GetOrSet<T>(string key, Func<CacheEntry<T>> setter);
public abstract T? GetOrSet<T>(string key, Func<CacheEntry<T>> setter, Action<CacheOptions>? action = null);

public abstract Task<T?> GetOrSetAsync<T>(string key, Func<CacheEntry<T>> setter);
public abstract Task<T?> GetOrSetAsync<T>(string key, Func<CacheEntry<T>> setter, Action<CacheOptions>? action = null);

public void Refresh(params string[] keys) => Refresh(GetKeys(keys));
public abstract void Refresh(params string[] keys);

public abstract void Refresh(IEnumerable<string> keys);
public abstract Task RefreshAsync(params string[] keys);

public Task RefreshAsync(params string[] keys) => RefreshAsync(GetKeys(keys));
public abstract void Remove(params string[] keys);

public abstract Task RefreshAsync(IEnumerable<string> keys);
public override void Remove<T>(string key, Action<CacheOptions>? action = null)
=> Remove<T>(new[] { key }, action);

public virtual void Remove(params string[] keys) => Remove(GetKeys(keys));
public abstract Task RemoveAsync(params string[] keys);

public abstract void Remove(IEnumerable<string> keys);

public Task RemoveAsync(params string[] keys) => RemoveAsync(GetKeys(keys));

public abstract Task RemoveAsync(IEnumerable<string> keys);
public override Task RemoveAsync<T>(string key, Action<CacheOptions>? action = null)
=> RemoveAsync<T>(new[] { key }, action);

public abstract bool Exists(string key);

public abstract bool Exists<T>(string key, Action<CacheOptions>? action = null);

public abstract Task<bool> ExistsAsync(string key);

public abstract Task<bool> ExistsAsync<T>(string key, Action<CacheOptions>? action = null);

public abstract IEnumerable<string> GetKeys(string keyPattern);

public abstract IEnumerable<string> GetKeys<T>(string keyPattern, Action<CacheOptions>? action = null);

public abstract Task<IEnumerable<string>> GetKeysAsync(string keyPattern);

public abstract IEnumerable<KeyValuePair<string, T?>> GetByKeyPattern<T>(string keyPattern);
public abstract Task<IEnumerable<string>> GetKeysAsync<T>(string keyPattern, Action<CacheOptions>? action = null);

public abstract IEnumerable<KeyValuePair<string, T?>> GetByKeyPattern<T>(string keyPattern, Action<CacheOptions>? action = null);

public abstract Task<IEnumerable<KeyValuePair<string, T?>>> GetByKeyPatternAsync<T>(string keyPattern);
public abstract Task<IEnumerable<KeyValuePair<string, T?>>> GetByKeyPatternAsync<T>(string keyPattern,
Action<CacheOptions>? action = null);

public abstract void Publish(string channel, Action<PublishOptions> options);

@@ -45,27 +52,58 @@ public abstract class DistributedCacheClientBase : CacheClientBase, IDistributed

public abstract Task SubscribeAsync<T>(string channel, Action<SubscribeOptions<T>> options);

public abstract Task<long> HashIncrementAsync(string key, long value = 1);
public abstract Task<long> HashIncrementAsync(
string key,
long value = 1,
Action<CacheOptions>? action = null);

public abstract Task<long> HashDecrementAsync(string key, long value = 1, long defaultMinVal = 0);
public abstract Task<long> HashDecrementAsync(string key,
long value = 1L,
long defaultMinVal = 0L,
Action<CacheOptions>? action = null);

public virtual bool KeyExpire(string key, TimeSpan? absoluteExpirationRelativeToNow)
=> KeyExpire(key, new CacheEntryOptions(absoluteExpirationRelativeToNow));

public virtual bool KeyExpire<T>(string key, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null)
=> KeyExpire<T>(key, new CacheEntryOptions(absoluteExpirationRelativeToNow), action);

public virtual bool KeyExpire(string key, DateTimeOffset absoluteExpiration)
=> KeyExpire(key, new CacheEntryOptions(absoluteExpiration));

public virtual bool KeyExpire<T>(string key, DateTimeOffset absoluteExpiration, Action<CacheOptions>? action = null)
=> KeyExpire<T>(key, new CacheEntryOptions(absoluteExpiration), action);

public abstract bool KeyExpire(string key, CacheEntryOptions? options = null);

public abstract bool KeyExpire<T>(string key, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

public abstract long KeyExpire(IEnumerable<string> keys, CacheEntryOptions? options = null);

public abstract long KeyExpire<T>(IEnumerable<string> keys, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

public virtual Task<bool> KeyExpireAsync(string key, DateTimeOffset absoluteExpiration)
=> KeyExpireAsync(key, new CacheEntryOptions(absoluteExpiration));

public virtual Task<bool> KeyExpireAsync<T>(string key, DateTimeOffset absoluteExpiration, Action<CacheOptions>? action = null)
=> KeyExpireAsync<T>(key, new CacheEntryOptions(absoluteExpiration), action);

public virtual Task<bool> KeyExpireAsync(string key, TimeSpan? absoluteExpirationRelativeToNow)
=> KeyExpireAsync(key, new CacheEntryOptions(absoluteExpirationRelativeToNow));

public virtual Task<bool> KeyExpireAsync<T>(string key, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null)
=> KeyExpireAsync<T>(key, new CacheEntryOptions(absoluteExpirationRelativeToNow), action);

public abstract Task<bool> KeyExpireAsync(string key, CacheEntryOptions? options = null);

public abstract Task<long> KeyExpireAsync(IEnumerable<string> keys, CacheEntryOptions? options = null);
public abstract Task<bool> KeyExpireAsync<T>(string key, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

public abstract Task<long> KeyExpireAsync(
IEnumerable<string> keys,
CacheEntryOptions? options = null);

public abstract Task<long> KeyExpireAsync<T>(
IEnumerable<string> keys,
CacheEntryOptions? options = null,
Action<CacheOptions>? action = null);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.BuildingBlocks.Caching;

public enum CacheKeyType
{
/// <summary>
/// Keep it the same, use the key directly
/// </summary>
None = 1,

/// <summary>
/// Type's name(Type's full name with generic type name) and key combination
/// </summary>
TypeName,

/// <summary>
/// Type Alias and key combination, Format: ${TypeAliasName}{:}{key}
/// </summary>
TypeAlias
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.BuildingBlocks.Caching.Extensions;

/// <summary>
/// It is only used for inheritance by Contrib implementation, and does not support the extension of IServiceCollection to avoid reference barriers
/// </summary>
public static class ServiceCollectionExtensions
{
public static IServiceCollection TryAddDistributedCacheCore(IServiceCollection services, string name)
{
MasaApp.TrySetServiceCollection(services);
services.TryAddSingleton<IDistributedCacheClientFactory, DistributedCacheClientFactoryBase>();
services.TryAddSingleton(serviceProvider
=> serviceProvider.GetRequiredService<IDistributedCacheClientFactory>().Create());

services.TryAddSingleton<ITypeAliasFactory, DefaultTypeAliasFactory>();
services.TryAddSingleton<ITypeAliasProvider, DefaultTypeAliasProvider>();
services.Configure<TypeAliasFactoryOptions>(options => options.TryAdd(name));
return services;
}

public static IServiceCollection TryAddMultilevelCacheCore(IServiceCollection services, string name)
{
MasaApp.TrySetServiceCollection(services);
services.TryAddSingleton<IMultilevelCacheClientFactory, MultilevelCacheClientFactoryBase>();
services.TryAddSingleton(serviceProvider
=> serviceProvider.GetRequiredService<IMultilevelCacheClientFactory>().Create());

services.TryAddSingleton<ITypeAliasFactory, DefaultTypeAliasFactory>();
services.TryAddSingleton<ITypeAliasProvider, DefaultTypeAliasProvider>();
services.Configure<TypeAliasFactoryOptions>(options => options.TryAdd(name));

TryAddDistributedCacheCore(services, name);
return services;
}
}
Original file line number Diff line number Diff line change
@@ -6,21 +6,25 @@ namespace Microsoft.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
public static IServiceCollection TryAddDistributedCacheCore(this IServiceCollection services)
{
MasaApp.TrySetServiceCollection(services);
services.TryAddSingleton<IDistributedCacheClientFactory, DistributedCacheClientFactoryBase>();
services.TryAddSingleton(serviceProvider
=> serviceProvider.GetRequiredService<IDistributedCacheClientFactory>().Create());
return services;
}
public static IServiceCollection AddDistributedCache(
this IServiceCollection services,
Action<DistributedCacheOptions> action,
Action<TypeAliasOptions>? typeAliasOptionsAction = null)
=> services.AddDistributedCache(Microsoft.Extensions.Options.Options.DefaultName, action, typeAliasOptionsAction);

public static IServiceCollection TryAddMultilevelCacheCore(this IServiceCollection services)
public static IServiceCollection AddDistributedCache(
this IServiceCollection services,
string name,
Action<DistributedCacheOptions> action,
Action<TypeAliasOptions>? typeAliasOptionsAction = null)
{
MasaApp.TrySetServiceCollection(services);
services.TryAddSingleton<IMultilevelCacheClientFactory, MultilevelCacheClientFactoryBase>();
services.TryAddSingleton(serviceProvider
=> serviceProvider.GetRequiredService<IMultilevelCacheClientFactory>().Create());
Masa.BuildingBlocks.Caching.Extensions.ServiceCollectionExtensions.TryAddDistributedCacheCore(services, name);
DistributedCacheOptions options = new(services, name);
action.Invoke(options);

if (typeAliasOptionsAction != null)
services.Configure(name, typeAliasOptionsAction);

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.BuildingBlocks.Caching;

public static class CacheKeyHelper
{
public static string FormatCacheKey<T>(string key, CacheKeyType cacheKeyType, Func<string, string>? typeAliasFunc = null)
{
switch (cacheKeyType)
{
case CacheKeyType.None:
return key;
case CacheKeyType.TypeName:
return $"{GetTypeName<T>()}.{key}";
case CacheKeyType.TypeAlias:
if (typeAliasFunc == null)
throw new NotImplementedException();

var typeName = GetTypeName<T>();
return $"{typeAliasFunc.Invoke(typeName)}:{key}";
default:
throw new NotImplementedException();
}
}

public static string GetTypeName<T>()
{
var type = typeof(T);
if (type.IsGenericType)
{
var dictType = typeof(Dictionary<,>);
if (type.GetGenericTypeDefinition() == dictType)
return type.Name + "[" + type.GetGenericArguments()[1].Name + "]";

return type.Name + "[" + type.GetGenericArguments()[0].Name + "]";
}

return typeof(T).Name;
}
}
Original file line number Diff line number Diff line change
@@ -5,30 +5,6 @@ namespace Masa.BuildingBlocks.Caching;

public static class SubscribeHelper
{
/// <summary>
/// Formats the memory cache key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>A string.</returns>
public static string FormatMemoryCacheKey<T>(string key)
{
var type = typeof(T);
if (type.IsGenericType)
{
var dictType = typeof(Dictionary<,>);
if (type.GetGenericTypeDefinition() == dictType)
key += type.Name + "[" + type.GetGenericArguments()[1].Name + "]";
else
key += type.Name + "[" + type.GetGenericArguments()[0].Name + "]";
}
else
{
key += typeof(T).Name;
}

return key;
}

/// <summary>
/// Formats the subscribe channel.
/// </summary>
Original file line number Diff line number Diff line change
@@ -5,117 +5,177 @@ namespace Masa.BuildingBlocks.Caching;

public interface ICacheClient
{
T? Get<T>(string key);
T? Get<T>(string key, Action<CacheOptions>? action = null);

Task<T?> GetAsync<T>(string key);
Task<T?> GetAsync<T>(string key, Action<CacheOptions>? action = null);

IEnumerable<T?> GetList<T>(params string[] keys);

IEnumerable<T?> GetList<T>(IEnumerable<string> keys);
IEnumerable<T?> GetList<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

Task<IEnumerable<T?>> GetListAsync<T>(params string[] keys);

Task<IEnumerable<T?>> GetListAsync<T>(IEnumerable<string> keys);
Task<IEnumerable<T?>> GetListAsync<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="value">Cache value</param>
/// <param name="absoluteExpiration">Absolute Expiration,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void Set<T>(string key, T value, DateTimeOffset? absoluteExpiration);
void Set<T>(string key, T value, DateTimeOffset? absoluteExpiration, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="value">Cache value</param>
/// <param name="absoluteExpirationRelativeToNow">Absolute Expiration Relative To Now,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void Set<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow);
void Set<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="value">Cache value</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void Set<T>(string key, T value, CacheEntryOptions? options = null);
void Set<T>(string key, T value, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="value">Cache value</param>
/// <param name="absoluteExpiration">Absolute Expiration,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
Task SetAsync<T>(string key, T value, DateTimeOffset? absoluteExpiration);
Task SetAsync<T>(string key, T value, DateTimeOffset? absoluteExpiration, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="value">Cache value</param>
/// <param name="absoluteExpirationRelativeToNow">Absolute Expiration Relative To Now,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
Task SetAsync<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow);
Task SetAsync<T>(string key, T value, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="value">Cache value</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
Task SetAsync<T>(string key, T value, CacheEntryOptions? options = null);
Task SetAsync<T>(string key, T value, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

/// <summary>
/// Batch setting cache
/// </summary>
/// <param name="keyValues">A collection of key-value pairs</param>
/// <param name="absoluteExpiration">Absolute Expiration,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void SetList<T>(Dictionary<string, T?> keyValues, DateTimeOffset? absoluteExpiration);
void SetList<T>(Dictionary<string, T?> keyValues, DateTimeOffset? absoluteExpiration, Action<CacheOptions>? action = null);

/// <summary>
/// Batch setting cache
/// </summary>
/// <param name="keyValues">A collection of key-value pairs</param>
/// <param name="absoluteExpirationRelativeToNow">Absolute Expiration Relative To Now,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void SetList<T>(Dictionary<string, T?> keyValues, TimeSpan? absoluteExpirationRelativeToNow);
void SetList<T>(Dictionary<string, T?> keyValues, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null);

/// <summary>
/// Batch setting cache
/// </summary>
/// <param name="keyValues">A collection of key-value pairs</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void SetList<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null);
void SetList<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

/// <summary>
/// Batch setting cache
/// </summary>
/// <param name="keyValues">A collection of key-value pairs</param>
/// <param name="absoluteExpiration">Absolute Expiration,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
Task SetListAsync<T>(Dictionary<string, T?> keyValues, DateTimeOffset? absoluteExpiration);
Task SetListAsync<T>(Dictionary<string, T?> keyValues, DateTimeOffset? absoluteExpiration, Action<CacheOptions>? action = null);

/// <summary>
/// Batch setting cache
/// </summary>
/// <param name="keyValues">A collection of key-value pairs</param>
/// <param name="absoluteExpirationRelativeToNow">Absolute Expiration Relative To Now,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
Task SetListAsync<T>(Dictionary<string, T?> keyValues, TimeSpan? absoluteExpirationRelativeToNow);
Task SetListAsync<T>(Dictionary<string, T?> keyValues, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null);

/// <summary>
/// Batch setting cache
/// </summary>
/// <param name="keyValues">A collection of key-value pairs</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action"></param>
/// <typeparam name="T"></typeparam>
Task SetListAsync<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null);
Task SetListAsync<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

/// <summary>
/// delete cache key
/// </summary>
/// <param name="key">cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void Remove<T>(string key, Action<CacheOptions>? action = null);

/// <summary>
/// delete cache key set
/// </summary>
/// <param name="keys">A collection of cache keys, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
void Remove<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

/// <summary>
/// delete cache key
/// </summary>
/// <param name="key">cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task RemoveAsync<T>(string key, Action<CacheOptions>? action = null);

/// <summary>
/// delete cache key set
/// </summary>
/// <param name="keys">A collection of cache keys, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task RemoveAsync<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys">A collection of cache keys, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
void Refresh<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys">A collection of cache keys, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
Task RefreshAsync<T>(IEnumerable<string> keys, Action<CacheOptions>? action = null);
}
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ public interface ICachingBuilder
IServiceCollection Services { get; }

/// <summary>
/// Gets the name of the client configured by this builder.
/// Get the name of IDistributedCacheClient or IMultilevelCacheClient, used for multiple IDistributedCacheClient or IMultilevelCacheClient.
/// </summary>
string Name { get; }
}
Original file line number Diff line number Diff line change
@@ -5,22 +5,16 @@ namespace Masa.BuildingBlocks.Caching;

public interface IDistributedCacheClient : ICacheClient
{
T? GetOrSet<T>(string key, Func<CacheEntry<T>> setter);
T? GetOrSet<T>(string key, Func<CacheEntry<T>> setter, Action<CacheOptions>? action = null);

Task<T?> GetOrSetAsync<T>(string key, Func<CacheEntry<T>> setter);
Task<T?> GetOrSetAsync<T>(string key, Func<CacheEntry<T>> setter, Action<CacheOptions>? action = null);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys"></param>
void Refresh(params string[] keys);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys"></param>
void Refresh(IEnumerable<string> keys);

/// <summary>
/// Flush cache time to live
/// </summary>
@@ -31,29 +25,85 @@ public interface IDistributedCacheClient : ICacheClient
/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys"></param>
/// <returns></returns>
Task RefreshAsync(IEnumerable<string> keys);

/// <param name="keys">A collection of cache keys</param>
void Remove(params string[] keys);

void Remove(IEnumerable<string> keys);

/// <summary>
/// Remove cache key
/// </summary>
/// <param name="keys">A collection of cache keys</param>
/// <returns></returns>
Task RemoveAsync(params string[] keys);

Task RemoveAsync(IEnumerable<string> keys);

/// <summary>
/// Get whether the cache key exists
/// </summary>
/// <param name="key">Complete cache key, cache key is no longer formatted</param>
/// <returns></returns>
bool Exists(string key);

/// <summary>
/// Get whether the cache key exists
/// </summary>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
bool Exists<T>(string key, Action<CacheOptions>? action = null);

/// <summary>
/// Get whether the cache key exists
/// </summary>
/// <param name="key">Complete cache key, cache key is no longer formatted</param>
/// <returns></returns>
Task<bool> ExistsAsync(string key);

/// <summary>
/// Get whether the cache key exists
/// </summary>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task<bool> ExistsAsync<T>(string key, Action<CacheOptions>? action = null);

/// <summary>
/// Only get the key, do not trigger the update expiration time policy
/// </summary>
/// <param name="keyPattern">Complete keyPattern, no longer formatted, such as: app_*</param>
/// <returns></returns>
IEnumerable<string> GetKeys(string keyPattern);

/// <summary>
/// The set of cached keys that match the rules according to the key fuzzy matching
/// Obtain the set of keys that meet the rules according to the obtained type and KeyPattern
/// </summary>
/// <param name="keyPattern">keyPattern, used to change the global cache configuration information, eg: 1*</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
IEnumerable<string> GetKeys<T>(string keyPattern, Action<CacheOptions>? action = null);

/// <summary>
/// Only get the key, do not trigger the update expiration time policy
/// </summary>
/// <param name="keyPattern">Complete keyPattern, no longer formatted, such as: app_*</param>
/// <returns></returns>
Task<IEnumerable<string>> GetKeysAsync(string keyPattern);

IEnumerable<KeyValuePair<string, T?>> GetByKeyPattern<T>(string keyPattern);
/// <summary>
/// The set of cached keys that match the rules according to the key fuzzy matching
/// Obtain the set of keys that meet the rules according to the obtained type and KeyPattern
/// </summary>
/// <param name="keyPattern">keyPattern, used to change the global cache configuration information, eg: 1*</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task<IEnumerable<string>> GetKeysAsync<T>(string keyPattern, Action<CacheOptions>? action = null);

IEnumerable<KeyValuePair<string, T?>> GetByKeyPattern<T>(string keyPattern, Action<CacheOptions>? action = null);

Task<IEnumerable<KeyValuePair<string, T?>>> GetByKeyPatternAsync<T>(string keyPattern);
Task<IEnumerable<KeyValuePair<string, T?>>> GetByKeyPatternAsync<T>(string keyPattern, Action<CacheOptions>? action = null);

void Publish(string channel, Action<PublishOptions> options);

@@ -63,72 +113,159 @@ public interface IDistributedCacheClient : ICacheClient

Task SubscribeAsync<T>(string channel, Action<SubscribeOptions<T>> options);

Task<long> HashIncrementAsync(string key, long value = 1L);
/// <summary>
/// Increment Hash
/// </summary>
/// <param name="key">cache key</param>
/// <param name="value">incremental increment, must be greater than 0</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<long> HashIncrementAsync(string key, long value = 1L, Action<CacheOptions>? action = null);

/// <summary>
/// Descending Hash
/// </summary>
/// <param name="key">cache key</param>
/// <param name="value">decrement increment, must be greater than 0</param>
/// <param name="defaultMinVal">critical value, must be greater than or equal to 0</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<long> HashDecrementAsync(string key, long value = 1L, long defaultMinVal = 0L);
Task<long> HashDecrementAsync(string key, long value = 1L, long defaultMinVal = 0L, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">cache key</param>
/// <param name="key">Complete cache key</param>
/// <param name="absoluteExpirationRelativeToNow">absolute Expiration Relative To Now,Permanently valid when null</param>
/// <returns></returns>
bool KeyExpire(string key, TimeSpan? absoluteExpirationRelativeToNow);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">cache key</param>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="absoluteExpirationRelativeToNow">absolute Expiration Relative To Now,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
bool KeyExpire<T>(string key, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">Complete cache key</param>
/// <param name="absoluteExpiration">absolute Expiration,Permanently valid when null</param>
/// <returns></returns>
bool KeyExpire(string key, DateTimeOffset absoluteExpiration);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">cache key</param>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="absoluteExpiration">absolute Expiration,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
bool KeyExpire<T>(string key, DateTimeOffset absoluteExpiration, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">Complete cache key</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <returns></returns>
bool KeyExpire(string key, CacheEntryOptions? options = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
bool KeyExpire<T>(string key, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

/// <summary>
/// Refresh the lifetime of the cache key set
/// </summary>
/// <param name="keys">A collection of cache keys</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <returns>Get the number of caches that have successfully refreshed the cache key life cycle</returns>
long KeyExpire(IEnumerable<string> keys, CacheEntryOptions? options = null);

/// <summary>
/// Refresh the lifetime of the cache key set
/// </summary>
/// <param name="keys">A collection of cache keys, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns>Get the number of caches that have successfully refreshed the cache key life cycle</returns>
long KeyExpire<T>(IEnumerable<string> keys, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">cache key</param>
/// <param name="key">Complete cache key</param>
/// <param name="absoluteExpirationRelativeToNow">absolute Expiration Relative To Now,Permanently valid when null</param>
/// <returns></returns>
Task<bool> KeyExpireAsync(string key, TimeSpan? absoluteExpirationRelativeToNow);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">cache key</param>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="absoluteExpirationRelativeToNow">absolute Expiration Relative To Now,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<bool> KeyExpireAsync<T>(string key, TimeSpan? absoluteExpirationRelativeToNow, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">Complete cache key</param>
/// <param name="absoluteExpiration">absolute Expiration,Permanently valid when null</param>
/// <returns></returns>
Task<bool> KeyExpireAsync(string key, DateTimeOffset absoluteExpiration);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">cache key</param>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="absoluteExpiration">absolute Expiration,Permanently valid when null</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<bool> KeyExpireAsync<T>(string key, DateTimeOffset absoluteExpiration, Action<CacheOptions>? action = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">Complete cache key</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <returns></returns>
Task<bool> KeyExpireAsync(string key, CacheEntryOptions? options = null);

/// <summary>
/// Set cache lifetime
/// </summary>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<bool> KeyExpireAsync<T>(string key, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);

/// <summary>
/// Batch setting cache lifetime
/// </summary>
/// <param name="keys">cache key</param>
/// <param name="keys">A collection of cache keys</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <returns></returns>
Task<long> KeyExpireAsync(IEnumerable<string> keys, CacheEntryOptions? options = null);

/// <summary>
/// Batch setting cache lifetime
/// </summary>
/// <param name="keys">A collection of cache keys, the actual cache key will decide whether to format the cache key according to the global configuration and Action</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<long> KeyExpireAsync<T>(IEnumerable<string> keys, CacheEntryOptions? options = null, Action<CacheOptions>? action = null);
}
Original file line number Diff line number Diff line change
@@ -5,79 +5,97 @@ namespace Masa.BuildingBlocks.Caching;

public interface IMultilevelCacheClient : ICacheClient
{
T? Get<T>(string key, Action<T?> valueChanged);
T? Get<T>(string key, Action<T?> valueChanged, Action<CacheOptions>? action = null);

Task<T?> GetAsync<T>(string key, Action<T?> valueChanged);
Task<T?> GetAsync<T>(string key, Action<T?> valueChanged, Action<CacheOptions>? action = null);

/// <summary>
/// Get cache, set cache if cache does not exist
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action
/// <param name="distributedCacheEntryFunc">Distributed cache information returned when the memory cache does not exist</param>
/// <param name="memoryCacheEntryOptions">Memory cache lifetime configuration,which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T? GetOrSet<T>(string key, Func<CacheEntry<T>> distributedCacheEntryFunc, CacheEntryOptions? memoryCacheEntryOptions = null);
T? GetOrSet<T>(string key,
Func<CacheEntry<T>> distributedCacheEntryFunc,
CacheEntryOptions? memoryCacheEntryOptions = null,
Action<CacheOptions>? action = null);

T? GetOrSet<T>(string key, CombinedCacheEntry<T> combinedCacheEntry);
/// <summary>
/// Get cache, set cache if cache does not exist
/// </summary>
/// <param name="key">Cache key, the actual cache key will decide whether to format the cache key according to the global configuration and Action
/// <param name="combinedCacheEntry">Cache key information, used to configure the execution of Handler when the cache does not exist, and the memory cache life cycle</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T? GetOrSet<T>(string key, CombinedCacheEntry<T> combinedCacheEntry, Action<CacheOptions>? action = null);

/// <summary>
/// Get cache, set cache if cache does not exist
/// </summary>
/// <param name="key">Cache key</param>
/// <param name="distributedCacheEntryFunc">Distributed cache information returned when the memory cache does not exist</param>
/// <param name="memoryCacheEntryOptions">Memory cache lifetime configuration,which is consistent with the default configuration when it is empty</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
Task<T?> GetOrSetAsync<T>(string key, Func<CacheEntry<T>> distributedCacheEntryFunc, CacheEntryOptions? memoryCacheEntryOptions = null);
Task<T?> GetOrSetAsync<T>(
string key,
Func<CacheEntry<T>> distributedCacheEntryFunc,
CacheEntryOptions? memoryCacheEntryOptions = null,
Action<CacheOptions>? action = null);

Task<T?> GetOrSetAsync<T>(string key, CombinedCacheEntry<T> combinedCacheEntry);
Task<T?> GetOrSetAsync<T>(string key, CombinedCacheEntry<T> combinedCacheEntry, Action<CacheOptions>? action = null);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys">Set of cache keys</param>
void Refresh<T>(params string[] keys);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys">Set of cache keys</param>
void Refresh<T>(IEnumerable<string> keys);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys">Set of cache keys</param>
Task RefreshAsync<T>(params string[] keys);

/// <summary>
/// Flush cache time to live
/// </summary>
/// <param name="keys">Set of cache keys</param>
Task RefreshAsync<T>(IEnumerable<string> keys);

void Remove<T>(params string[] keys);

void Remove<T>(IEnumerable<string> keys);

Task RemoveAsync<T>(params string[] keys);

Task RemoveAsync<T>(IEnumerable<string> keys);

void Set<T>(string key, T value, CacheEntryOptions? distributedOptions, CacheEntryOptions? memoryOptions);
void Set<T>(string key,
T value,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null);

void Set<T>(string key, T value, CombinedCacheEntryOptions? options);
void Set<T>(string key, T value, CombinedCacheEntryOptions? options, Action<CacheOptions>? action = null);

Task SetAsync<T>(string key, T value, CacheEntryOptions? distributedOptions, CacheEntryOptions? memoryOptions);
Task SetAsync<T>(
string key,
T value,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null);

Task SetAsync<T>(string key, T value, CombinedCacheEntryOptions? options);
Task SetAsync<T>(string key, T value, CombinedCacheEntryOptions? options, Action<CacheOptions>? action = null);

void SetList<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? distributedOptions, CacheEntryOptions? memoryOptions);
void SetList<T>(
Dictionary<string, T?> keyValues,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null);

void SetList<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options);
void SetList<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options, Action<CacheOptions>? action = null);

Task SetListAsync<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? distributedOptions, CacheEntryOptions? memoryOptions);
Task SetListAsync<T>(
Dictionary<string, T?> keyValues,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null);

Task SetListAsync<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options);
Task SetListAsync<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options, Action<CacheOptions>? action = null);
}
Original file line number Diff line number Diff line change
@@ -5,110 +5,136 @@ namespace Masa.BuildingBlocks.Caching;

public abstract class MultilevelCacheClientBase : CacheClientBase, IMultilevelCacheClient
{
public abstract T? Get<T>(string key, Action<T?> valueChanged);
public abstract T? Get<T>(string key, Action<T?> valueChanged, Action<CacheOptions>? action = null);

public abstract Task<T?> GetAsync<T>(string key, Action<T?> valueChanged);
public abstract Task<T?> GetAsync<T>(string key, Action<T?> valueChanged, Action<CacheOptions>? action = null);

public virtual T? GetOrSet<T>(string key, Func<CacheEntry<T>> distributedCacheEntryFunc, CacheEntryOptions? memoryCacheEntryOptions = null)
public virtual T? GetOrSet<T>(string key,
Func<CacheEntry<T>> distributedCacheEntryFunc,
CacheEntryOptions? memoryCacheEntryOptions = null,
Action<CacheOptions>? action = null)
=> GetOrSet(key, new CombinedCacheEntry<T>()
{
DistributedCacheEntryFunc = distributedCacheEntryFunc,
MemoryCacheEntryOptions = memoryCacheEntryOptions
});
}, action);

public abstract T? GetOrSet<T>(string key, CombinedCacheEntry<T> combinedCacheEntry);
public abstract T? GetOrSet<T>(string key, CombinedCacheEntry<T> combinedCacheEntry, Action<CacheOptions>? action = null);

public virtual Task<T?> GetOrSetAsync<T>(string key, Func<CacheEntry<T>> distributedCacheEntryFunc,
CacheEntryOptions? memoryCacheEntryOptions = null)
public virtual Task<T?> GetOrSetAsync<T>(string key,
Func<CacheEntry<T>> distributedCacheEntryFunc,
CacheEntryOptions? memoryCacheEntryOptions = null,
Action<CacheOptions>? action = null)
=> GetOrSetAsync(key, new CombinedCacheEntry<T>()
{
DistributedCacheEntryFunc = distributedCacheEntryFunc,
MemoryCacheEntryOptions = memoryCacheEntryOptions
});
}, action);

public abstract Task<T?> GetOrSetAsync<T>(string key, CombinedCacheEntry<T> combinedCacheEntry);
public abstract Task<T?> GetOrSetAsync<T>(string key, CombinedCacheEntry<T> combinedCacheEntry, Action<CacheOptions>? action = null);

public void Refresh<T>(params string[] keys) => Refresh<T>(GetKeys(keys));

public abstract void Refresh<T>(IEnumerable<string> keys);

public Task RefreshAsync<T>(params string[] keys) => RefreshAsync<T>(GetKeys(keys));

public abstract Task RefreshAsync<T>(IEnumerable<string> keys);

public void Remove<T>(params string[] keys)
=> Remove<T>(GetKeys(keys));

public abstract void Remove<T>(IEnumerable<string> keys);
public override void Remove<T>(string key, Action<CacheOptions>? action = null)
=> Remove<T>(new[] { key }, action);

public Task RemoveAsync<T>(params string[] keys)
=> RemoveAsync<T>(GetKeys(keys));

public abstract Task RemoveAsync<T>(IEnumerable<string> keys);
public override Task RemoveAsync<T>(string key, Action<CacheOptions>? action = null)
=> RemoveAsync<T>(new[] { key }, action);

public virtual void Set<T>(string key, T value, CacheEntryOptions? distributedOptions, CacheEntryOptions? memoryOptions)
public virtual void Set<T>(string key,
T value,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null)
=> Set(key, value, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = distributedOptions,
MemoryCacheEntryOptions = memoryOptions
});
}, action);

public override void Set<T>(string key, T value, CacheEntryOptions? options = null)
public override void Set<T>(string key,
T value,
CacheEntryOptions? options = null,
Action<CacheOptions>? action = null)
=> Set(key, value, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = options
});
}, action);

public abstract void Set<T>(string key, T value, CombinedCacheEntryOptions? options);
public abstract void Set<T>(string key, T value, CombinedCacheEntryOptions? options, Action<CacheOptions>? action = null);

public virtual Task SetAsync<T>(string key, T value, CacheEntryOptions? distributedOptions, CacheEntryOptions? memoryOptions)
public virtual Task SetAsync<T>(string key,
T value,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null)
=> SetAsync(key, value, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = distributedOptions,
MemoryCacheEntryOptions = memoryOptions
});
}, action);

public override Task SetAsync<T>(string key, T value, CacheEntryOptions? options = null)
public override Task SetAsync<T>(string key, T value, CacheEntryOptions? options = null, Action<CacheOptions>? action = null)
=> SetAsync(key, value, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = options
});
}, action);

public abstract Task SetAsync<T>(string key, T value, CombinedCacheEntryOptions? options);
public abstract Task SetAsync<T>(string key, T value, CombinedCacheEntryOptions? options, Action<CacheOptions>? action = null);

public virtual void SetList<T>(
Dictionary<string, T?> keyValues,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions)
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null)
=> SetList(keyValues, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = distributedOptions,
MemoryCacheEntryOptions = memoryOptions
});
}, action);

public override void SetList<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null) where T : default
public override void SetList<T>(
Dictionary<string, T?> keyValues,
CacheEntryOptions? options = null,
Action<CacheOptions>? action = null) where T : default
=> SetList(keyValues, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = options
});
}, action);

public abstract void SetList<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options);
public abstract void SetList<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options,
Action<CacheOptions>? action = null);

public virtual Task SetListAsync<T>(
Dictionary<string, T?> keyValues,
CacheEntryOptions? distributedOptions,
CacheEntryOptions? memoryOptions)
CacheEntryOptions? memoryOptions,
Action<CacheOptions>? action = null)
=> SetListAsync(keyValues, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = distributedOptions,
MemoryCacheEntryOptions = memoryOptions
});
}, action);

public override Task SetListAsync<T>(Dictionary<string, T?> keyValues, CacheEntryOptions? options = null) where T : default
public override Task SetListAsync<T>(
Dictionary<string, T?> keyValues,
CacheEntryOptions? options = null,
Action<CacheOptions>? action = null) where T : default
=> SetListAsync(keyValues, new CombinedCacheEntryOptions()
{
DistributedCacheEntryOptions = options
});
}, action);

public abstract Task SetListAsync<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options);
public abstract Task SetListAsync<T>(
Dictionary<string, T?> keyValues,
CombinedCacheEntryOptions? options,
Action<CacheOptions>? action = null);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.BuildingBlocks.Caching;

public class CacheOptions
{
public CacheKeyType? CacheKeyType { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.BuildingBlocks.Caching;

public class DistributedCacheOptions
{
public IServiceCollection Services { get; }

/// <summary>
/// Gets the name of the client configured by this builder.
/// </summary>
public string Name { get; }

public DistributedCacheOptions(IServiceCollection services, string name)
{
Services = services;
Name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public class DefaultTypeAliasFactory : MasaFactoryBase<ITypeAliasProvider, TypeAliasRelationOptions>, ITypeAliasFactory
{
protected override string DefaultServiceNotFoundMessage => "Default TypeAlias not found";

protected override string SpecifyServiceNotFoundMessage => "Please make sure you have used [{0}] TypeAlias, it was not found";

protected override MasaFactoryOptions<TypeAliasRelationOptions> FactoryOptions => _optionsMonitor.Value;

private readonly IOptions<TypeAliasFactoryOptions> _optionsMonitor;

public DefaultTypeAliasFactory(IServiceProvider serviceProvider) : base(serviceProvider)
{
_optionsMonitor = serviceProvider.GetRequiredService<IOptions<TypeAliasFactoryOptions>>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public class DefaultTypeAliasProvider : ITypeAliasProvider
{
private readonly object _lock = new();
private ConcurrentDictionary<string, Lazy<string>>? _dicCache;
private readonly TypeAliasOptions? _options;
private DateTime? _lastDateTime;

public DefaultTypeAliasProvider(TypeAliasOptions? options)
{
_options = options;
}

public string GetAliasName(string typeName)
{
if (_options == null || _options.GetAllTypeAliasFunc == null)
throw new NotImplementedException();

if (_dicCache == null || _dicCache.IsEmpty)
{
RefreshTypeAlias();
}
var aliasName = _dicCache?.GetOrAdd(typeName, key => new Lazy<string>(() =>
{
RefreshTypeAlias();

if (_dicCache.TryGetValue(key, out var alias))
return alias.Value;

throw new ArgumentNullException(key, $"not found type alias by {typeName}");

}, LazyThreadSafetyMode.ExecutionAndPublication));
return aliasName?.Value ?? throw new ArgumentNullException(typeName, $"not found type alias by {typeName}");
}

private void RefreshTypeAlias()
{
if (_lastDateTime != null && (DateTime.UtcNow - _lastDateTime.Value).TotalSeconds < _options!.RefreshTypeAliasInterval)
{
return;
}

lock (_lock)
{
_lastDateTime = DateTime.UtcNow;
_dicCache?.Clear();
_dicCache ??= new ConcurrentDictionary<string, Lazy<string>>();

var typeAliases = _options!.GetAllTypeAliasFunc!.Invoke();
foreach (var typeAlias in typeAliases)
{
_dicCache[typeAlias.Key] = new Lazy<string>(typeAlias.Value);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public interface ITypeAliasFactory : IMasaFactory<ITypeAliasProvider>
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public interface ITypeAliasProvider
{
string GetAliasName(string typeName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public class TypeAliasFactoryOptions : MasaFactoryOptions<TypeAliasRelationOptions>
{
public void TryAdd(string name)
{
if (Options.Any(options => options.Name == name))
return;

var typeAliasRelationOptions = new TypeAliasRelationOptions(
name,
serviceProvider
=> new DefaultTypeAliasProvider(serviceProvider.GetService<IOptionsFactory<TypeAliasOptions>>()?.Create(name))
);
Options.Add(typeAliasRelationOptions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public class TypeAliasOptions
{
/// <summary>
/// Refresh TypeAlias minimum interval time
/// default: 30s
/// </summary>
public long RefreshTypeAliasInterval { get; set; } = 30;

public Func<Dictionary<string, string>>? GetAllTypeAliasFunc { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public class TypeAliasRelationOptions : MasaRelationOptions<ITypeAliasProvider>
{
public TypeAliasRelationOptions(string name, Func<IServiceProvider, ITypeAliasProvider> func) : base(name)
{
Func = func;
}
}
Original file line number Diff line number Diff line change
@@ -6,3 +6,4 @@
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.Extensions.Options;
global using System.Collections.Concurrent;
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;

public static class OptionsConfigurationServiceCollectionExtensions
public static class ServiceCollectionExtensions
{
/// <summary>
/// Only consider using MasaConfiguration and database configuration using local configuration
@@ -38,4 +38,16 @@ public static IServiceCollection AddConfigure<TOptions>(
services.Configure<TOptions>(name, isRoot ? configuration : configurationSection);
return services;
}

public static IConfiguration GetLocalConfiguration(
this IServiceCollection services,
string sectionName)
{
var serviceProvider = services.BuildServiceProvider();
IConfiguration? configuration = serviceProvider.GetService<Masa.BuildingBlocks.Configuration.IMasaConfiguration>()?.Local ??
serviceProvider.GetService<IConfiguration>();
if (configuration == null)
throw new NotSupportedException(); //Need to make sure IConfiguration has been injected in DI
return configuration.GetSection(sectionName);
}
}
Original file line number Diff line number Diff line change
@@ -3,4 +3,3 @@

global using Masa.BuildingBlocks.Data.Mapping.Options;
global using Masa.BuildingBlocks.Data.Mapping.Options.Enum;
global using Microsoft.Extensions.DependencyInjection;
Original file line number Diff line number Diff line change
@@ -8,4 +8,3 @@
global using Masa.Contrib.Authentication.OpenIdConnect.Cache.Storage.Stores;
global using Masa.Contrib.Caching.Distributed.StackExchangeRedis;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
Original file line number Diff line number Diff line change
@@ -16,11 +16,17 @@ public static IServiceCollection AddOidcCache(this IServiceCollection services,

public static IServiceCollection AddOidcCache(this IServiceCollection services, RedisConfigurationOptions options)
{
services.AddStackExchangeRedisCache(Constants.DEFAULT_CLIENT_NAME, options).AddMultilevelCache(new MultilevelCacheOptions()
{
SubscribeKeyType = SubscribeKeyType.SpecificPrefix,
SubscribeKeyPrefix = Constants.DEFAULT_SUBSCRIBE_KEY_PREFIX
});
services.AddMultilevelCache(
Constants.DEFAULT_CLIENT_NAME,
distributedCacheOptions => distributedCacheOptions.UseStackExchangeRedisCache(options),
null,
multilevelCacheOptions =>
{
multilevelCacheOptions.SubscribeKeyType = SubscribeKeyType.SpecificPrefix;
multilevelCacheOptions.SubscribeKeyPrefix = Constants.DEFAULT_SUBSCRIBE_KEY_PREFIX;
}
);

services.AddSingleton<MemoryCacheProvider>();
services.AddSingleton<IClientCache, ClientCache>();
services.AddSingleton<IApiScopeCache, ApiScopeCache>();
Original file line number Diff line number Diff line change
@@ -11,7 +11,5 @@
global using Masa.Contrib.Authentication.OpenIdConnect.Cache.Models;
global using Masa.Contrib.Authentication.OpenIdConnect.Cache.Utils;
global using Masa.Contrib.Caching.Distributed.StackExchangeRedis;
global using Masa.Contrib.Caching.MultilevelCache;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using System.Diagnostics.CodeAnalysis;
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.VisualStudio.TestTools.UnitTesting;
global using Moq;
Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@

global using Masa.BuildingBlocks.Authentication.OpenIdConnect.Cache.Caches;
global using Masa.BuildingBlocks.Authentication.OpenIdConnect.Domain.Entities;
global using Masa.BuildingBlocks.Authentication.OpenIdConnect.Domain.Enums;
global using Masa.Contrib.Authentication.OpenIdConnect.Cache.Models;
global using Masa.Contrib.Authentication.OpenIdConnect.Cache.Utils;
global using Masa.Contrib.Caching.Distributed.StackExchangeRedis;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.VisualStudio.TestTools.UnitTesting;
global using Masa.BuildingBlocks.Authentication.OpenIdConnect.Domain.Enums;
global using Masa.Contrib.Authentication.OpenIdConnect.Cache.Models;
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.BuildingBlocks.Caching;

public static class DistributedCacheOptionsExtensions
{
/// <summary>
/// Add distributed Redis cache
/// </summary>
/// <param name="distributedOptions"></param>
/// <param name="redisSectionName">redis node name, not required, default: RedisConfig(Use local configuration)</param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
public static DistributedCacheOptions UseStackExchangeRedisCache(
this DistributedCacheOptions distributedOptions,
string redisSectionName = Const.DEFAULT_REDIS_SECTION_NAME,
JsonSerializerOptions? jsonSerializerOptions = null)
{
distributedOptions.Services.AddConfigure<RedisConfigurationOptions>(redisSectionName, distributedOptions.Name);
return distributedOptions.UseStackExchangeRedisCache(jsonSerializerOptions);
}

/// <summary>
/// Add distributed Redis cache
/// </summary>
/// <param name="distributedOptions"></param>
/// <param name="configuration"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
public static DistributedCacheOptions UseStackExchangeRedisCache(
this DistributedCacheOptions distributedOptions,
IConfiguration configuration,
JsonSerializerOptions? jsonSerializerOptions = null)
{
distributedOptions.Services.Configure<RedisConfigurationOptions>(distributedOptions.Name, configuration);
return distributedOptions.UseStackExchangeRedisCache(jsonSerializerOptions);
}

public static DistributedCacheOptions UseStackExchangeRedisCache(
this DistributedCacheOptions distributedOptions,
Action<RedisConfigurationOptions> action,
JsonSerializerOptions? jsonSerializerOptions = null)
{
var redisConfigurationOptions = new RedisConfigurationOptions();
action.Invoke(redisConfigurationOptions);
distributedOptions.UseStackExchangeRedisCache(redisConfigurationOptions, jsonSerializerOptions);
return distributedOptions;
}

public static DistributedCacheOptions UseStackExchangeRedisCache(
this DistributedCacheOptions distributedOptions,
RedisConfigurationOptions redisConfigurationOptions,
JsonSerializerOptions? jsonSerializerOptions = null)
{
distributedOptions.Services.UseStackExchangeRedisCache(
distributedOptions.Name,
redisConfigurationOptions,
jsonSerializerOptions);
return distributedOptions;
}

#region internal methods

/// <summary>
/// Add distributed Redis cache
/// </summary>
/// <param name="distributedOptions"></param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
private static DistributedCacheOptions UseStackExchangeRedisCache(
this DistributedCacheOptions distributedOptions,
JsonSerializerOptions? jsonSerializerOptions = null)
{
distributedOptions.Services.Configure<DistributedCacheFactoryOptions>(options =>
{
if (options.Options.Any(opt => opt.Name == distributedOptions.Name))
return;

var cacheRelationOptions = new CacheRelationOptions<IDistributedCacheClient>(distributedOptions.Name, serviceProvider =>
{
var distributedCacheClient = new RedisCacheClient(
serviceProvider.GetRequiredService<IOptionsMonitor<RedisConfigurationOptions>>(),
distributedOptions.Name,
jsonSerializerOptions,
serviceProvider.GetRequiredService<ITypeAliasFactory>().Create(distributedOptions.Name)
);
return distributedCacheClient;
});
options.Options.Add(cacheRelationOptions);
});
return distributedOptions;
}

internal static void UseStackExchangeRedisCache(
this IServiceCollection services,
string name,
RedisConfigurationOptions redisConfigurationOptions,
JsonSerializerOptions? jsonSerializerOptions = null)
{
services.Configure<RedisConfigurationOptions>(name, options =>
{
options.AbsoluteExpiration = redisConfigurationOptions.AbsoluteExpiration;
options.AbsoluteExpirationRelativeToNow = redisConfigurationOptions.AbsoluteExpirationRelativeToNow;
options.SlidingExpiration = redisConfigurationOptions.SlidingExpiration;
options.Servers = redisConfigurationOptions.Servers;
options.AbortOnConnectFail = redisConfigurationOptions.AbortOnConnectFail;
options.AllowAdmin = redisConfigurationOptions.AllowAdmin;
options.ClientName = redisConfigurationOptions.ClientName;
options.ChannelPrefix = redisConfigurationOptions.ChannelPrefix;
options.ConnectRetry = redisConfigurationOptions.ConnectRetry;
options.ConnectTimeout = redisConfigurationOptions.ConnectTimeout;
options.DefaultDatabase = redisConfigurationOptions.DefaultDatabase;
options.Password = redisConfigurationOptions.Password;
options.Proxy = redisConfigurationOptions.Proxy;
options.Ssl = redisConfigurationOptions.Ssl;
options.SyncTimeout = redisConfigurationOptions.SyncTimeout;
});

services.Configure<DistributedCacheFactoryOptions>(options =>
{
if (options.Options.Any(opt => opt.Name == name))
return;

var cacheRelationOptions = new CacheRelationOptions<IDistributedCacheClient>(name, serviceProvider =>
{
var distributedCacheClient = new RedisCacheClient(
redisConfigurationOptions,
jsonSerializerOptions,
serviceProvider.GetRequiredService<ITypeAliasFactory>().Create(name)
);
return distributedCacheClient;
});
options.Options.Add(cacheRelationOptions);
});

services.TryAddSingleton<ITypeAliasFactory, DefaultTypeAliasFactory>();
services.TryAddSingleton<ITypeAliasProvider, DefaultTypeAliasProvider>();
}

#endregion

}
Original file line number Diff line number Diff line change
@@ -4,8 +4,13 @@
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Deprecated method, will be removed in 1.0
/// </summary>
public static class ServiceCollectionExtensions
{
[Obsolete(
"AddStackExchangeRedisCache has expired, please use services.AddDistributedCache(options => options.UseStackExchangeRedisCache()) or services.AddMultilevelCache(options => options.UseStackExchangeRedisCache()) instead")]
public static ICachingBuilder AddStackExchangeRedisCache(
this IServiceCollection services,
Action<RedisConfigurationOptions> action)
@@ -17,6 +22,8 @@ public static ICachingBuilder AddStackExchangeRedisCache(
redisConfigurationOptions);
}

[Obsolete(
"AddStackExchangeRedisCache has expired, please use services.AddDistributedCache(options => options.UseStackExchangeRedisCache()) or services.AddMultilevelCache(options => options.UseStackExchangeRedisCache()) instead")]
public static ICachingBuilder AddStackExchangeRedisCache(this IServiceCollection services,
RedisConfigurationOptions redisConfigurationOptions)
=> services.AddStackExchangeRedisCache(
@@ -28,6 +35,8 @@ public static ICachingBuilder AddStackExchangeRedisCache(this IServiceCollection
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
[Obsolete(
"AddStackExchangeRedisCache has expired, please use services.AddDistributedCache(options => options.UseStackExchangeRedisCache()) or services.AddMultilevelCache(options => options.UseStackExchangeRedisCache()) instead")]
public static ICachingBuilder AddStackExchangeRedisCache(this IServiceCollection services)
=> services.AddStackExchangeRedisCache(Microsoft.Extensions.Options.Options.DefaultName);

@@ -39,36 +48,21 @@ public static ICachingBuilder AddStackExchangeRedisCache(this IServiceCollection
/// <param name="redisSectionName">redis node name, not required, default: RedisConfig(Use local configuration)</param>
/// <param name="jsonSerializerOptions"></param>
/// <returns></returns>
[Obsolete(
"AddStackExchangeRedisCache has expired, please use services.AddDistributedCache(options => options.UseStackExchangeRedisCache()) or services.AddMultilevelCache(options => options.UseStackExchangeRedisCache()) instead")]
public static ICachingBuilder AddStackExchangeRedisCache(
this IServiceCollection services,
string name,
string redisSectionName = Const.DEFAULT_REDIS_SECTION_NAME,
JsonSerializerOptions? jsonSerializerOptions = null)
{
services.TryAddDistributedCacheCore();

services.AddConfigure<RedisConfigurationOptions>(redisSectionName, name);

services.Configure<DistributedCacheFactoryOptions>(options =>
{
if (options.Options.Any(opt => opt.Name == name))
return;

var cacheRelationOptions = new CacheRelationOptions<IDistributedCacheClient>(name, serviceProvider =>
{
var distributedCacheClient = new RedisCacheClient(
serviceProvider.GetRequiredService<IOptionsMonitor<RedisConfigurationOptions>>(),
name,
jsonSerializerOptions
);
return distributedCacheClient;
});
options.Options.Add(cacheRelationOptions);
});

Masa.BuildingBlocks.Caching.Extensions.ServiceCollectionExtensions.TryAddDistributedCacheCore(services, name);
new DistributedCacheOptions(services, name).UseStackExchangeRedisCache(redisSectionName, jsonSerializerOptions);
return new CachingBuilder(services, name);
}

[Obsolete(
"AddStackExchangeRedisCache has expired, please use services.AddDistributedCache(options => options.UseStackExchangeRedisCache()) or services.AddMultilevelCache(options => options.UseStackExchangeRedisCache()) instead")]
public static ICachingBuilder AddStackExchangeRedisCache(
this IServiceCollection services,
string name,
@@ -83,48 +77,17 @@ public static ICachingBuilder AddStackExchangeRedisCache(
jsonSerializerOptions);
}

[Obsolete(
"AddStackExchangeRedisCache has expired, please use services.AddDistributedCache(options => options.UseStackExchangeRedisCache()) or services.AddMultilevelCache(options => options.UseStackExchangeRedisCache()) instead")]
public static ICachingBuilder AddStackExchangeRedisCache(
this IServiceCollection services,
string name,
RedisConfigurationOptions redisConfigurationOptions,
JsonSerializerOptions? jsonSerializerOptions = null)
{
services.TryAddDistributedCacheCore();

services.Configure<RedisConfigurationOptions>(name, options =>
{
options.AbsoluteExpiration = redisConfigurationOptions.AbsoluteExpiration;
options.AbsoluteExpirationRelativeToNow = redisConfigurationOptions.AbsoluteExpirationRelativeToNow;
options.SlidingExpiration = redisConfigurationOptions.SlidingExpiration;
options.Servers = redisConfigurationOptions.Servers;
options.AbortOnConnectFail = redisConfigurationOptions.AbortOnConnectFail;
options.AllowAdmin = redisConfigurationOptions.AllowAdmin;
options.ClientName = redisConfigurationOptions.ClientName;
options.ChannelPrefix = redisConfigurationOptions.ChannelPrefix;
options.ConnectRetry = redisConfigurationOptions.ConnectRetry;
options.ConnectTimeout = redisConfigurationOptions.ConnectTimeout;
options.DefaultDatabase = redisConfigurationOptions.DefaultDatabase;
options.Password = redisConfigurationOptions.Password;
options.Proxy = redisConfigurationOptions.Proxy;
options.Ssl = redisConfigurationOptions.Ssl;
options.SyncTimeout = redisConfigurationOptions.SyncTimeout;
});

services.Configure<DistributedCacheFactoryOptions>(options =>
{
if (options.Options.Any(opt => opt.Name == name))
return;
Masa.BuildingBlocks.Caching.Extensions.ServiceCollectionExtensions.TryAddDistributedCacheCore(services, name);

var cacheRelationOptions = new CacheRelationOptions<IDistributedCacheClient>(name, _ =>
{
var distributedCacheClient = new RedisCacheClient(
redisConfigurationOptions,
jsonSerializerOptions
);
return distributedCacheClient;
});
options.Options.Add(cacheRelationOptions);
});
services.UseStackExchangeRedisCache(name, redisConfigurationOptions, jsonSerializerOptions);

return new CachingBuilder(services, name);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

// ReSharper disable once CheckNamespace
namespace Masa.Contrib.Caching.Distributed.StackExchangeRedis;

internal static class Const
Original file line number Diff line number Diff line change
@@ -70,6 +70,11 @@ public class RedisConfigurationOptions : CacheEntryOptions
/// </summary>
public int SyncTimeout { get; set; } = 1000;

public CacheOptions GlobalCacheOptions { get; set; } = new()
{
CacheKeyType = CacheKeyType.TypeName
};

public static implicit operator ConfigurationOptions(RedisConfigurationOptions options)
{
var configurationOptions = new ConfigurationOptions
Original file line number Diff line number Diff line change
@@ -17,6 +17,33 @@ Install-Package Masa.Contrib.Caching.Distributed.StackExchangeRedis

#### Usage 1:

1. Add Redis cache

``` C#
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
var redisConfigurationOptions = new RedisConfigurationOptions()
{
DefaultDatabase = 1,
ConnectionPoolSize = 10,
Servers = new List<RedisServerOptions>()
{
new("localhost", 6379)
}
};
distributedCacheOptions.UseStackExchangeRedisCache(redisConfigurationOptions);
});
```

2. Get `IDistributedCacheClient` from DI and use the corresponding method

``` C#
string key = "test_1";
distributedCacheClient.Set(key, "test_content");
```

#### Usage 2:

1. Configure `appsettings.json`

``` C#
@@ -37,33 +64,81 @@ Install-Package Masa.Contrib.Caching.Distributed.StackExchangeRedis
2. Add Redis cache

``` C#
builder.Services.AddStackExchangeRedisCache();
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
// Redis configuration information is obtained through `IOptionsMonitor<RedisConfigurationOptions>`
distributedCacheOptions.UseStackExchangeRedisCache();
});
```

> By default, the `RedisConfig` node of the local configuration is read, and the configuration supports [`Options Mode`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options), which supports the `IOptionsMonitor<RedisConfigurationOptions>` to get Redis configuration information
3. Get `IDistributedCacheClient` from DI

``` C#
string key = "test_1";
distributedCacheClient.Set(key, "test_content");
```

#### Usage 2:
#### Usage 3:

1. Add Redis cache
1. Use [Dcc](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.md)

``` C#
builder.Services.AddStackExchangeRedisCache(new RedisConfigurationOptions()
builder.Services.AddMasaConfiguration(configurationBuilder =>
{
DefaultDatabase = 1,
ConnectionPoolSize = 10,
Servers = new List<RedisServerOptions>()
{
new("localhost", 6379)
}
configurationBuilder.UseDcc();
});
```

2. Get `IDistributedCacheClient` from DI and use the corresponding method
> Please [Reference](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.md)
2 Use the configuration where redis is located

``` C#
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
var configuration = builder.GetMasaConfiguration().ConfigurationApi.GetSection("{Replace-Your-RedisOptions-AppId}").GetSection("{Replace-Your-RedisOptions-ConfigObjectName}");
distributedCacheOptions.UseStackExchangeRedisCache(configuration);
});
```

3. Get `IDistributedCacheClient` from DI and use the corresponding method

``` C#
string key = "test_1";
distributedCacheClient.Set(key, "test_content");
```

#### Usage 4:

1. Use [Dcc](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.md)

``` C#
builder.Services.AddMasaConfiguration(configurationBuilder =>
{
configurationBuilder.UseDcc();

// Use the manual mapping function provided by MasaConfiguration to support the option mode, and support to obtain Redis configuration information through IOptionsMonitor<RedisConfigurationOptions>
configurationBuilder.UseMasaOptions(option => option.MappingConfigurationApi<RedisConfigurationOptions>("Replace-Your-RedisOptions-AppId", "Replace-Your-RedisOptions-ConfigObjectName", "{Replace-Your-DistributedCacheName}"));
});
```

> Please [Reference](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.md)
2 Use the configuration where redis is located

``` C#
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
// Redis configuration information is obtained through `IOptionsMonitor<RedisConfigurationOptions>`
distributedCacheOptions.UseStackExchangeRedisCache());
});
```

> Since the local `RedisConfig` node does not exist, support [Options mode](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) is skipped by default (avoid providing options with MasaConfiguration [Options mode](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) repeat)
3. Get `IDistributedCacheClient` from DI and use the corresponding method

``` C#
string key = "test_1";
Original file line number Diff line number Diff line change
@@ -15,7 +15,34 @@ Install-Package Masa.Contrib.Caching.Distributed.StackExchangeRedis

### 入门

#### 用法1:
#### 用法1:

1. 添加Redis缓存

```C#
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
var redisConfigurationOptions = new RedisConfigurationOptions()
{
DefaultDatabase = 1,
ConnectionPoolSize = 10,
Servers = new List<RedisServerOptions>()
{
new("localhost", 6379)
}
};
distributedCacheOptions.UseStackExchangeRedisCache(redisConfigurationOptions);
});
```

2. 从DI获取`IDistributedCacheClient`,并使用相应的方法

``` C#
string key = "test_1";
distributedCacheClient.Set(key, "test_content");
```

#### 用法2:

1. 配置`appsettings.json`

@@ -37,33 +64,81 @@ Install-Package Masa.Contrib.Caching.Distributed.StackExchangeRedis
2. 添加Redis缓存

```C#
builder.Services.AddStackExchangeRedisCache();
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
// Redis配置信息是通过`IOptionsMonitor<RedisConfigurationOptions>`来获取
distributedCacheOptions.UseStackExchangeRedisCache();
});
```

> 默认读取本地配置的`RedisConfig`节点,并配置支持[`选项模式`](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/options),支持了通过`IOptionsMonitor<RedisConfigurationOptions>`来获取Redis配置的信息
3. 从DI获取`IDistributedCacheClient`

``` C#
string key = "test_1";
distributedCacheClient.Set(key, "test_content");
```

#### 用法2
#### 用法3

1. 添加Redis缓存
1. 使用[Dcc](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.zh-CN.md)

```C#
builder.Services.AddStackExchangeRedisCache(new RedisConfigurationOptions()
builder.Services.AddMasaConfiguration(configurationBuilder =>
{
DefaultDatabase = 1,
ConnectionPoolSize = 10,
Servers = new List<RedisServerOptions>()
{
new("localhost", 6379)
}
configurationBuilder.UseDcc();
});
```

2. 从DI获取`IDistributedCacheClient`,并使用相应的方法
> Dcc配置使用请[参考](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.zh-CN.md)
2 使用redis所在的配置

```C#
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
var configuration = builder.GetMasaConfiguration().ConfigurationApi.GetSection("{Replace-Your-RedisOptions-AppId}").GetSection("{Replace-Your-RedisOptions-ConfigObjectName}");
distributedCacheOptions.UseStackExchangeRedisCache(configuration);
});
```

3. 从DI获取`IDistributedCacheClient`,并使用相应的方法

``` C#
string key = "test_1";
distributedCacheClient.Set(key, "test_content");
```

#### 用法4:

1. 使用[Dcc](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.zh-CN.md)

```C#
builder.Services.AddMasaConfiguration(configurationBuilder =>
{
configurationBuilder.UseDcc();

// 使用 MasaConfiguration提供的手动映射功能,使其支持 选项模式,支持通过 IOptionsMonitor<RedisConfigurationOptions> 来获取Redis配置的信息
configurationBuilder.UseMasaOptions(option => option.MappingConfigurationApi<RedisConfigurationOptions>("Replace-Your-RedisOptions-AppId", "Replace-Your-RedisOptions-ConfigObjectName", "{Replace-Your-DistributedCacheName}"));
});
```

> Dcc配置使用请[参考](../../../Configuration/ConfigurationApi/Masa.Contrib.Configuration.ConfigurationApi.Dcc/README.zh-CN.md)
2 使用redis所在的配置

``` C#
builder.Services.AddDistributedCache(distributedCacheOptions =>
{
// Redis配置信息是通过`IOptionsMonitor<RedisConfigurationOptions>`来获取
distributedCacheOptions.UseStackExchangeRedisCache());
});
```

> 由于本地`RedisConfig`节点不存在,默认会跳过支持[选项模式](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/options) (避免与MasaConfiguration提供[选项模式](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/options)重复)
3. 从DI获取`IDistributedCacheClient`,并使用相应的方法

``` C#
string key = "test_1";

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -8,20 +8,22 @@ public abstract class RedisCacheClientBase : DistributedCacheClientBase
protected static readonly Guid UniquelyIdentifies = Guid.NewGuid();
protected ISubscriber Subscriber;
protected IDatabase Db;
protected readonly JsonSerializerOptions JsonSerializerOptions;
protected CacheEntryOptions CacheEntryOptions;
protected readonly JsonSerializerOptions GlobalJsonSerializerOptions;
protected CacheEntryOptions GlobalCacheEntryOptions;
protected CacheOptions GlobalCacheOptions;

protected RedisCacheClientBase(RedisConfigurationOptions redisConfigurationOptions,
JsonSerializerOptions? jsonSerializerOptions)
{
GlobalCacheOptions = redisConfigurationOptions.GlobalCacheOptions;
var redisConfiguration = GetRedisConfigurationOptions(redisConfigurationOptions);
IConnectionMultiplexer? connection = ConnectionMultiplexer.Connect(redisConfiguration);
Db = connection.GetDatabase();
Subscriber = connection.GetSubscriber();

JsonSerializerOptions = jsonSerializerOptions ?? new JsonSerializerOptions().EnableDynamicTypes();
GlobalJsonSerializerOptions = jsonSerializerOptions ?? new JsonSerializerOptions().EnableDynamicTypes();

CacheEntryOptions = new CacheEntryOptions
GlobalCacheEntryOptions = new CacheEntryOptions
{
AbsoluteExpiration = redisConfiguration.AbsoluteExpiration,
AbsoluteExpirationRelativeToNow = redisConfiguration.AbsoluteExpirationRelativeToNow,
@@ -48,15 +50,26 @@ private static RedisConfigurationOptions GetRedisConfigurationOptions(RedisConfi
if (value.HasValue && !value.IsNullOrEmpty)
{
isExist = true;
return value.ConvertToValue<T>(JsonSerializerOptions);
return value.ConvertToValue<T>(GlobalJsonSerializerOptions);
}

isExist = false;
return default;
}

protected CacheEntryOptions GetCacheEntryOptions(CacheEntryOptions? options = null)
=> options ?? CacheEntryOptions;
=> options ?? GlobalCacheEntryOptions;

protected CacheOptions GetCacheOptions(Action<CacheOptions>? action)
{
if (action != null)
{
CacheOptions cacheOptions = new CacheOptions();
action.Invoke(cacheOptions);
return cacheOptions;
}
return GlobalCacheOptions;
}

protected static PublishOptions GetAndCheckPublishOptions(string channel, Action<PublishOptions> setup)
{
Original file line number Diff line number Diff line change
@@ -3,14 +3,11 @@

global using Masa.BuildingBlocks.Caching;
global using Masa.Contrib.Caching.Distributed.StackExchangeRedis;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.Extensions.Options;
global using StackExchange.Redis;
global using System.Collections;
global using System.ComponentModel;
global using System.Diagnostics;
global using System.Dynamic;
global using System.IO.Compression;
global using System.Text;
global using System.Text.Json;
global using System.Text.Json.Serialization;
Original file line number Diff line number Diff line change
@@ -738,4 +738,50 @@ public void TestGetAbsoluteExpiration()
options = new CacheEntryOptions(creationTime.AddSeconds(1));
Assert.AreEqual(creationTime.AddSeconds(1), options.GetAbsoluteExpiration(creationTime));
}

[TestMethod]
public void TestExists()
{
var configurationOptions = GetConfigurationOptions();
configurationOptions.GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.TypeName
};
var distributedCacheClient = new RedisCacheClient(configurationOptions);
var key = "redis.exist";
distributedCacheClient.Set(key, "1");
Assert.IsFalse(distributedCacheClient.Exists(key));

Assert.IsTrue(distributedCacheClient.Exists<string>(key));
Assert.IsTrue(distributedCacheClient.Exists<string>(key));

distributedCacheClient.Remove(key);
Assert.IsTrue(distributedCacheClient.Exists<string>(key));

distributedCacheClient.Remove<string>(key);
Assert.IsFalse(distributedCacheClient.Exists<string>(key));
}

[TestMethod]
public async Task TestExistsAsync()
{
var configurationOptions = GetConfigurationOptions();
configurationOptions.GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.TypeName
};
var distributedCacheClient = new RedisCacheClient(configurationOptions);
var key = "redis.exist";
await distributedCacheClient.SetAsync(key, "1");
Assert.IsFalse(await distributedCacheClient.ExistsAsync(key));

Assert.IsTrue(await distributedCacheClient.ExistsAsync<string>(key));
Assert.IsTrue(await distributedCacheClient.ExistsAsync<string>(key));

await distributedCacheClient.RemoveAsync(key);
Assert.IsTrue(await distributedCacheClient.ExistsAsync<string>(key));

await distributedCacheClient.RemoveAsync<string>(key);
Assert.IsFalse(await distributedCacheClient.ExistsAsync<string>(key));
}
}
Original file line number Diff line number Diff line change
@@ -3,21 +3,27 @@

namespace Masa.Contrib.Caching.Distributed.StackExchangeRedis.Tests;

#pragma warning disable CS0618
[TestClass]
public class StackExchangeRedisCacheTest : TestBase
{
[TestMethod]
public void TestAddStackExchangeRedisCache()
{
var services = new ServiceCollection();
services.AddStackExchangeRedisCache(option =>
services.AddDistributedCache(distributedCacheOptions => distributedCacheOptions.UseStackExchangeRedisCache(option =>
{
option.DefaultDatabase = 1;
option.Servers = new List<RedisServerOptions>()
{
new(REDIS_HOST)
};
});
option.GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.None
};
}));

var serviceProvider = services.BuildServiceProvider();

var options = serviceProvider.GetService<IOptions<RedisConfigurationOptions>>();
@@ -46,13 +52,21 @@ public void TestAddMultiStackExchangeRedisCache()
{
new(REDIS_HOST)
};
option.GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.None
};
});
services.AddStackExchangeRedisCache("test2", new RedisConfigurationOptions()
{
DefaultDatabase = 2,
Servers = new List<RedisServerOptions>()
{
new(REDIS_HOST)
},
GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.None
}
});
var serviceProvider = services.BuildServiceProvider();
@@ -127,13 +141,21 @@ public void TestAddStackExchangeRedisCacheRepeat()
{
new(REDIS_HOST)
};
options.GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.None
};
});
services.AddStackExchangeRedisCache(new RedisConfigurationOptions()
{
DefaultDatabase = 2,
Servers = new List<RedisServerOptions>()
{
new(REDIS_HOST)
},
GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.None
}
});
var serviceProvider = services.BuildServiceProvider();
@@ -150,7 +172,6 @@ public void TestAddStackExchangeRedisCacheRepeat()
Assert.AreEqual(1, ((IDatabase)value).Database);
}


[TestMethod]
public void TestAddStackExchangeRedisCacheRepeatByConfiguration()
{
@@ -176,11 +197,182 @@ public void TestAddStackExchangeRedisCacheRepeatByConfiguration()
public void TestCachingBuilder()
{
var services = new ServiceCollection();
var cachingBuilder = services.AddStackExchangeRedisCache();
var cachingBuilder = services.AddStackExchangeRedisCache(options =>
{
options.Servers = new List<RedisServerOptions>()
{
new()
};
});
Assert.AreEqual(Options.DefaultName, cachingBuilder.Name);
Assert.AreEqual(services, cachingBuilder.Services);

cachingBuilder = services.AddStackExchangeRedisCache("test");
cachingBuilder = services.AddStackExchangeRedisCache("test", options =>
{
options.Servers = new List<RedisServerOptions>()
{
new()
};
});
Assert.AreEqual("test", cachingBuilder.Name);
}

[TestMethod]
public void TestFormatCacheKey()
{
var services = new ServiceCollection();
services.AddDistributedCache("test", distributedCacheOptions => distributedCacheOptions.UseStackExchangeRedisCache(options =>
{
options.Servers = new List<RedisServerOptions>()
{
new()
};
options.GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.TypeName
};
}));

services.AddDistributedCache("test2", distributedCacheOptions => distributedCacheOptions.UseStackExchangeRedisCache(options =>
{
options.Servers = new List<RedisServerOptions>()
{
new(),
};
options.DefaultDatabase = 0;
options.GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.None
};
}));
var serviceProvider = services.BuildServiceProvider();
var distributedCacheClientFactory = serviceProvider.GetRequiredService<IDistributedCacheClientFactory>();
var key = "redisConfig";
var distributedCacheClient = distributedCacheClientFactory.Create("test");
Assert.IsNotNull(distributedCacheClient);
distributedCacheClient.Remove<string>(key);
distributedCacheClient.Set(key, "redis configuration json");
var value = distributedCacheClient.Get<string>(key);
Assert.AreEqual("redis configuration json", value);

var distributedCacheClient2 = distributedCacheClientFactory.Create("test2");
Assert.IsNotNull(distributedCacheClient2);

Assert.IsFalse(distributedCacheClient2.Exists(key));
Assert.IsFalse(distributedCacheClient2.Exists<string>(key));

distributedCacheClient2.Set(key, "redis configuration2 json");
var value2 = distributedCacheClient2.Get<string>(key);
Assert.AreEqual("redis configuration2 json", value2);

distributedCacheClient.Remove<string>(key);
distributedCacheClient2.Remove<string>(key);
}

[TestMethod]
public void TestFormatCacheKeyByTypeNameAlias()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddDistributedCache("test", distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache("RedisConfig4");
});
var serviceProvider = builder.Services.BuildServiceProvider();
var distributedCacheClient = serviceProvider.GetRequiredService<IDistributedCacheClientFactory>().Create("test");
Assert.IsNotNull(distributedCacheClient);

Assert.ThrowsException<NotImplementedException>(() =>
{
distributedCacheClient.GetOrSet("redisConfiguration", () => new CacheEntry<string>("redis configuration2 json"));
});
}

[TestMethod]
public void TestFormatCacheKeyByTypeNameAlias2()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddDistributedCache("test", distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache("RedisConfig4");
});
builder.Services.Configure("test", (TypeAliasOptions options) =>
{
options.GetAllTypeAliasFunc = () => new Dictionary<string, string>()
{
{ "String", "s" }
};
});
var serviceProvider = builder.Services.BuildServiceProvider();
var distributedCacheClient = serviceProvider.GetRequiredService<IDistributedCacheClientFactory>().Create("test");
Assert.IsNotNull(distributedCacheClient);

var value = distributedCacheClient.GetOrSet("redisConfiguration", () => new CacheEntry<string>("redis configuration2 json"));
Assert.AreEqual("redis configuration2 json", value);
distributedCacheClient.Remove<string>("redisConfiguration");
}

[TestMethod]
public void TestFormatCacheKeyByTypeNameAlias3()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddDistributedCache(
distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache("RedisConfig4");
},
typeAliasOptions =>
{
typeAliasOptions.GetAllTypeAliasFunc = () => new Dictionary<string, string>()
{
{ "String", "s" }
};
});
builder.Services.Configure((TypeAliasOptions options) =>
{
options.GetAllTypeAliasFunc = () => new Dictionary<string, string>()
{
{ "String", "s" }
};
});
var serviceProvider = builder.Services.BuildServiceProvider();
var distributedCacheClient = serviceProvider.GetRequiredService<IDistributedCacheClientFactory>().Create();
Assert.IsNotNull(distributedCacheClient);

var value = distributedCacheClient.GetOrSet("redisConfiguration", () => new CacheEntry<string>("redis configuration2 json"));
Assert.AreEqual("redis configuration2 json", value);
distributedCacheClient.Remove<string>("redisConfiguration");
}

[TestMethod]
public void TestFormatCacheKeyByTypeNameAlias4()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddDistributedCache(
distributedCacheOptions =>
{
distributedCacheOptions.UseStackExchangeRedisCache(builder.Configuration.GetSection("RedisConfig4"));
},
typeAliasOptions =>
{
typeAliasOptions.GetAllTypeAliasFunc = () => new Dictionary<string, string>()
{
{ "String", "s" }
};
});
builder.Services.Configure((TypeAliasOptions options) =>
{
options.GetAllTypeAliasFunc = () => new Dictionary<string, string>()
{
{ "String", "s" }
};
});
var serviceProvider = builder.Services.BuildServiceProvider();
var distributedCacheClient = serviceProvider.GetRequiredService<IDistributedCacheClientFactory>().Create();
Assert.IsNotNull(distributedCacheClient);

var value = distributedCacheClient.GetOrSet("redisConfiguration", () => new CacheEntry<string>("redis configuration2 json"));
Assert.AreEqual("redis configuration2 json", value);
distributedCacheClient.Remove<string>("redisConfiguration");
}
}
#pragma warning restore CS0618
Original file line number Diff line number Diff line change
@@ -9,7 +9,13 @@ public class TestBase

protected static RedisConfigurationOptions GetConfigurationOptions()
{
var redisConfigurationOptions = new RedisConfigurationOptions();
var redisConfigurationOptions = new RedisConfigurationOptions()
{
GlobalCacheOptions = new CacheOptions()
{
CacheKeyType = CacheKeyType.None
}
};
redisConfigurationOptions.Servers.Add(new RedisServerOptions());
return redisConfigurationOptions;
}
Original file line number Diff line number Diff line change
@@ -7,7 +7,10 @@
}
],
"DefaultDatabase": 3,
"ConnectionPoolSize": 10
"ConnectionPoolSize": 10,
"GlobalCacheOptions": {
"CacheKeyType": 1
}
},
"RedisConfig2": {
"Servers": [
@@ -17,7 +20,10 @@
}
],
"DefaultDatabase": 5,
"ConnectionPoolSize": 11
"ConnectionPoolSize": 11,
"GlobalCacheOptions": {
"CacheKeyType": 1
}
},
"RedisConfig3": {
"Servers": [
@@ -27,6 +33,22 @@
}
],
"DefaultDatabase": 6,
"ConnectionPoolSize": 11
"ConnectionPoolSize": 11,
"GlobalCacheOptions": {
"CacheKeyType": 1
}
},
"RedisConfig4": {
"Servers": [
{
"Host": "localhost",
"Port": 6379
}
],
"DefaultDatabase": 6,
"ConnectionPoolSize": 11,
"GlobalCacheOptions": {
"CacheKeyType": 3
}
}
}
Original file line number Diff line number Diff line change
@@ -10,86 +10,6 @@ public CustomerDistributedCacheClient(CacheEntryOptions? cacheEntryOptions)
{
}

public override T? Get<T>(string key) where T : default
{
throw new NotImplementedException();
}

public override Task<T?> GetAsync<T>(string key) where T : default
{
throw new NotImplementedException();
}

public override IEnumerable<T?> GetList<T>(IEnumerable<string> keys) where T : default
{
throw new NotImplementedException();
}

public override Task<IEnumerable<T?>> GetListAsync<T>(IEnumerable<string> keys) where T : default
{
throw new NotImplementedException();
}

public override T? Get<T>(string key, Action<T?> valueChanged) where T : default
{
throw new NotImplementedException();
}

public override Task<T?> GetAsync<T>(string key, Action<T?> valueChanged) where T : default
{
throw new NotImplementedException();
}

public override T? GetOrSet<T>(string key, CombinedCacheEntry<T> combinedCacheEntry) where T : default
{
throw new NotImplementedException();
}

public override Task<T?> GetOrSetAsync<T>(string key, CombinedCacheEntry<T> combinedCacheEntry) where T : default
{
throw new NotImplementedException();
}

public override void Refresh<T>(IEnumerable<string> keys)
{
throw new NotImplementedException();
}

public override Task RefreshAsync<T>(IEnumerable<string> keys)
{
throw new NotImplementedException();
}

public override void Remove<T>(IEnumerable<string> keys)
{
throw new NotImplementedException();
}

public override Task RemoveAsync<T>(IEnumerable<string> keys)
{
throw new NotImplementedException();
}

public override void Set<T>(string key, T value, CombinedCacheEntryOptions? options)
{
throw new NotImplementedException();
}

public override Task SetAsync<T>(string key, T value, CombinedCacheEntryOptions? options)
{
throw new NotImplementedException();
}

public override void SetList<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options) where T : default
{
throw new NotImplementedException();
}

public override Task SetListAsync<T>(Dictionary<string, T?> keyValues, CombinedCacheEntryOptions? options) where T : default
{
throw new NotImplementedException();
}

public MemoryCacheEntryOptions? GetBaseMemoryCacheEntryOptions(CacheEntryOptions? cacheEntryOptions)
=> base.GetMemoryCacheEntryOptions(cacheEntryOptions);
}
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NSubstitute" Version="$(NSubstitutePackageVersion)" />
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

namespace Masa.Contrib.Caching.MultilevelCache.Tests;

#pragma warning disable CS0618
[TestClass]
public class MultilevelCacheClientTest : TestBase
{
@@ -23,6 +24,10 @@ public void Initialize()
_distributedCacheClient,
new CacheEntryOptions(TimeSpan.FromSeconds(10)),
SubscribeKeyType.SpecificPrefix,
new CacheOptions()
{
CacheKeyType = CacheKeyType.None
},
"test");
InitializeData();
}
@@ -33,7 +38,7 @@ public void TestGet()
Assert.AreEqual("success", _multilevelCacheClient.Get<string>("test_multilevel_cache"));
Assert.AreEqual(99.99m, _multilevelCacheClient.Get<decimal>("test_multilevel_cache_2"));

_memoryCache.Remove(SubscribeHelper.FormatMemoryCacheKey<decimal>("test_multilevel_cache_2"));
_memoryCache.Remove(CacheKeyHelper.FormatCacheKey<decimal>("test_multilevel_cache_2", CacheKeyType.TypeName));
Assert.AreEqual(99.99m, _multilevelCacheClient.Get<decimal>("test_multilevel_cache_2"));

Assert.AreEqual(null, _multilevelCacheClient.Get<string>("test10"));
@@ -346,26 +351,27 @@ public void TestRefresh()
{
"test20"
};
var distributedCacheClient = Substitute.For<IDistributedCacheClient>();

var memoryCache = Substitute.For<IMemoryCache>();
var multilevelCacheClient = new MultilevelCacheClient(memoryCache,
distributedCacheClient,
Mock<IMemoryCache> memoryCache = new();
Mock<IDistributedCacheClient> distributedCacheClient = new();
distributedCacheClient.Setup(client => client.Refresh<string>(It.IsAny<IEnumerable<string>>(), It.IsAny<Action<CacheOptions>?>()))
.Verifiable();
memoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<object>.IsAny)).Verifiable();

var multilevelCacheClient = new MultilevelCacheClient(memoryCache.Object,
distributedCacheClient.Object,
new CacheEntryOptions(TimeSpan.FromSeconds(10)),
SubscribeKeyType.SpecificPrefix,
new CacheOptions()
{
CacheKeyType = CacheKeyType.None
},
"test");

multilevelCacheClient.Refresh<string>(keys);

Received.InOrder(() =>
{
distributedCacheClient.Refresh(keys);

Parallel.ForEach(keys, key =>
{
_memoryCache.TryGetValue(SubscribeHelper.FormatMemoryCacheKey<string>(key), out _);
});
});
memoryCache.Verify(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<object>.IsAny), Times.Once);
distributedCacheClient.Verify(client => client.Refresh<string>(It.IsAny<IEnumerable<string>>(), It.IsAny<Action<CacheOptions>?>()),
Times.Once);
}

[TestMethod]
@@ -375,26 +381,28 @@ public async Task TestRefreshAsync()
{
"test20"
};
var distributedCacheClient = Substitute.For<IDistributedCacheClient>();

var memoryCache = Substitute.For<IMemoryCache>();
var multilevelCacheClient = new MultilevelCacheClient(memoryCache,
distributedCacheClient,
Mock<IMemoryCache> memoryCache = new();
Mock<IDistributedCacheClient> distributedCacheClient = new();
distributedCacheClient.Setup(client
=> client.RefreshAsync<string>(It.IsAny<IEnumerable<string>>(), It.IsAny<Action<CacheOptions>?>()))
.Verifiable();
memoryCache.Setup(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<object>.IsAny)).Verifiable();

var multilevelCacheClient = new MultilevelCacheClient(memoryCache.Object,
distributedCacheClient.Object,
new CacheEntryOptions(TimeSpan.FromSeconds(10)),
SubscribeKeyType.SpecificPrefix,
new CacheOptions()
{
CacheKeyType = CacheKeyType.None
},
"test");

await multilevelCacheClient.RefreshAsync<string>(keys);

Received.InOrder(async () =>
{
await distributedCacheClient.RefreshAsync(keys);

Parallel.ForEach(keys, key =>
{
_memoryCache.TryGetValue(SubscribeHelper.FormatMemoryCacheKey<string>(key), out _);
});
});
memoryCache.Verify(cache => cache.TryGetValue(It.IsAny<string>(), out It.Ref<object>.IsAny), Times.Once);
distributedCacheClient.Verify(
client => client.RefreshAsync<string>(It.IsAny<IEnumerable<string>>(), It.IsAny<Action<CacheOptions>?>()), Times.Once);
}

[TestMethod]
@@ -439,10 +447,31 @@ private void InitializeData()
private static IMultilevelCacheClient InitializeByCacheEntryOptionsIsNull()
{
var services = new ServiceCollection();
services.AddStackExchangeRedisCache("test", RedisConfigurationOptions).AddMultilevelCache();
services.AddStackExchangeRedisCache("test", RedisConfigurationOptions).AddMultilevelCache(_ =>
{
});
var serviceProvider = services.BuildServiceProvider();
var cacheClientFactory = serviceProvider.GetRequiredService<IMultilevelCacheClientFactory>();
var multilevelCacheClient = cacheClientFactory.Create("test");
return multilevelCacheClient;
}

[TestMethod]
public void TestMultilevelCache()
{
var services = new ServiceCollection();
services.AddMultilevelCache(
distributedCacheOptions => distributedCacheOptions.UseStackExchangeRedisCache(_ =>
{
}),
new MultilevelCacheOptions()
{
SubscribeKeyPrefix = "masa",
SubscribeKeyType = SubscribeKeyType.ValueTypeFullNameAndKey
});
var serviceProvider = services.BuildServiceProvider();
var multilevelCacheClient = serviceProvider.GetRequiredService<IMultilevelCacheClient>();
Assert.IsNotNull(multilevelCacheClient);
}
}
#pragma warning restore CS0618
Loading