缓存 TryGetValue 与 Get
cache TryGetValue versus Get
这就是我实现 CacheManager 的方式。我面临的问题是 TryGetValue 在 RemoveFromCache 函数中总是 return null。在其中一个令牌过期后调用此函数,因此我试图从缓存中的列表中清除该令牌,而 GetAllTokens 是 return 所有令牌的完整列表。 AddTokenToCache 工作正常。
它是 ASPNET-Core 3.0 上的 WebAPI
CacheManager.cs
public class CacheManager : ICacheManager
{
private IMemoryCache _cache;
public CacheManager(IMemoryCache cache) {
_cache = cache;
}
public void AddTokenToCache(string appName, string tokenString)
{
List<Token> tokens = new List<Token>();
//save this token against the application record in-memory
if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens))
{
if (tokens == null)
tokens = new List<Token>();
}
tokens.Add(new Token
{
AppName = appName,
GeneratedAt = DateTime.Now,
TokenId = tokenString
});
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
;// .SetSlidingExpiration(TimeSpan.FromSeconds(180)); //3 minutes
_cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);
}
public List<Token> GetAllTokens()
{
return _cache.Get<List<Token>>(CacheHelper.CacheKey_Tokens);
}
public bool RemoveFromCache(string tokenId)
{
List<Token> tokens = new List<Token>();
//remove this token from memory
if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens)) {
return false;
}
else
{
if (tokens != null && tokens.Count > 0)
{
//_logger.LogInfo("Processing token");
//trimming quotations from the string
tokenId = tokenId.Substring(1, tokenId.Length - 2);
int index = tokens.FindIndex(t => t.TokenId == tokenId);
if (index >= 0)
tokens.RemoveAt(index);
var cacheEntryOptions = new MemoryCacheEntryOptions();
_cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);
return true;
}
}
return false;
}
}
我的调用顺序是:
- AddTokenToCache(令牌已成功添加到缓存)
- GetAllToken(显示令牌已添加到缓存)
- AddTokenToCache(令牌已成功添加到缓存)
- GetAllToken(显示两个令牌都已添加到缓存)
- 触发调用 RemoveFromCache 的 TokenExpired 事件(令牌为空)
- GetAllToken(显示两个令牌都已添加到缓存)
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ILoggerManager, LoggerManager>();
services.AddMemoryCache();
services.AddDbContext<GEContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllers();
services.AddRazorPages();
services.AddSingleton<ICacheManager, CacheManager>();
RegisterHandlerforTokenExpiredEvent(services);
//other code removed for brevity
}
public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services)
{
var sp = services.BuildServiceProvider();
var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher
var cacheManager = sp.GetService<ICacheManager>(); //subscriber
jwtManager.TokenExpired += cacheManager.OnTokenExpired;
}
那是因为你通过 services.BuildServiceProvider()
构建了另一个 ServiceProvider
:
public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services)
{
var sp = services.BuildServiceProvider(); // this is a different service provider from the default one built by ASP.NET Core itself.
var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher
var cacheManager = sp.GetService<ICacheManager>(); //subscriber
// it doesn't work because the cacheManager is not the same instance that you use in the controllers
jwtManager.TokenExpired += cacheManager.OnTokenExpired;
}
因此,您获得的 ICacheManager
实例 与您在 Controllers/Other 服务中注入的单例 不同。换句话说,您将有两个不同的 ICacheManager
实例 !
作为一条黄金法则,不要 在您的应用程序层代码中通过 services.BuildServiceProvider()
构建 ServiceProvider 的另一个副本,除非您非常确定它适合您。
如何修复
- 与其构建另一个服务提供者副本然后获取另一个实例,不如始终使用 IoC 而不是服务定位器模式。
您的 JWTAuthenticationManager
似乎是单例,您希望在启动时绑定事件处理程序。如果是这样,您可以注册一个 HostedService。
public class MyHostedService : IHostedService
{
private readonly IJWTAuthenticationManager _jWTAuthManager;
private readonly ICacheManager _cacheManager;
// suppose your IJWTAuthenticationManager is a singleton service
public MyHostedService(IJWTAuthenticationManager jWTAuthManager, ICacheManager cacheManager)
{
this._jWTAuthManager = jWTAuthManager;
this._cacheManager = cacheManager;
}
public Task StartAsync(CancellationToken cancellationToken)
{
this._jWTAuthManager.TokenExpired += this._cacheManager.OnTokenExpired;
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
this._jWTAuthManager.TokenExpired -= this._cacheManager.OnTokenExpired;
return Task.CompletedTask;
}
}
并在 Startup 中注册此服务:
services.AddHostedService<MyHostedService>();
另一种不需要 HostedService 并在启动时启动的方法:
在Host.Run()
之前获取服务并绑定事件:
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var jwtMgr = host.Services.GetRequiredService<IJWTAuthenticationManager>();
var cacheMgr = host.Services.GetRequiredService<ICacheManager>();
jwtMgr.TokenExpired = cacheMgr.OnTokenExpired;
host.Run();
}
这就是我实现 CacheManager 的方式。我面临的问题是 TryGetValue 在 RemoveFromCache 函数中总是 return null。在其中一个令牌过期后调用此函数,因此我试图从缓存中的列表中清除该令牌,而 GetAllTokens 是 return 所有令牌的完整列表。 AddTokenToCache 工作正常。
它是 ASPNET-Core 3.0 上的 WebAPI
CacheManager.cs
public class CacheManager : ICacheManager
{
private IMemoryCache _cache;
public CacheManager(IMemoryCache cache) {
_cache = cache;
}
public void AddTokenToCache(string appName, string tokenString)
{
List<Token> tokens = new List<Token>();
//save this token against the application record in-memory
if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens))
{
if (tokens == null)
tokens = new List<Token>();
}
tokens.Add(new Token
{
AppName = appName,
GeneratedAt = DateTime.Now,
TokenId = tokenString
});
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
;// .SetSlidingExpiration(TimeSpan.FromSeconds(180)); //3 minutes
_cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);
}
public List<Token> GetAllTokens()
{
return _cache.Get<List<Token>>(CacheHelper.CacheKey_Tokens);
}
public bool RemoveFromCache(string tokenId)
{
List<Token> tokens = new List<Token>();
//remove this token from memory
if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens)) {
return false;
}
else
{
if (tokens != null && tokens.Count > 0)
{
//_logger.LogInfo("Processing token");
//trimming quotations from the string
tokenId = tokenId.Substring(1, tokenId.Length - 2);
int index = tokens.FindIndex(t => t.TokenId == tokenId);
if (index >= 0)
tokens.RemoveAt(index);
var cacheEntryOptions = new MemoryCacheEntryOptions();
_cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);
return true;
}
}
return false;
}
}
我的调用顺序是:
- AddTokenToCache(令牌已成功添加到缓存)
- GetAllToken(显示令牌已添加到缓存)
- AddTokenToCache(令牌已成功添加到缓存)
- GetAllToken(显示两个令牌都已添加到缓存)
- 触发调用 RemoveFromCache 的 TokenExpired 事件(令牌为空)
- GetAllToken(显示两个令牌都已添加到缓存)
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ILoggerManager, LoggerManager>();
services.AddMemoryCache();
services.AddDbContext<GEContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllers();
services.AddRazorPages();
services.AddSingleton<ICacheManager, CacheManager>();
RegisterHandlerforTokenExpiredEvent(services);
//other code removed for brevity
}
public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services)
{
var sp = services.BuildServiceProvider();
var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher
var cacheManager = sp.GetService<ICacheManager>(); //subscriber
jwtManager.TokenExpired += cacheManager.OnTokenExpired;
}
那是因为你通过 services.BuildServiceProvider()
构建了另一个 ServiceProvider
:
public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services) { var sp = services.BuildServiceProvider(); // this is a different service provider from the default one built by ASP.NET Core itself. var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher var cacheManager = sp.GetService<ICacheManager>(); //subscriber // it doesn't work because the cacheManager is not the same instance that you use in the controllers jwtManager.TokenExpired += cacheManager.OnTokenExpired; }
因此,您获得的 ICacheManager
实例 与您在 Controllers/Other 服务中注入的单例 不同。换句话说,您将有两个不同的 ICacheManager
实例 !
作为一条黄金法则,不要 在您的应用程序层代码中通过 services.BuildServiceProvider()
构建 ServiceProvider 的另一个副本,除非您非常确定它适合您。
如何修复
- 与其构建另一个服务提供者副本然后获取另一个实例,不如始终使用 IoC 而不是服务定位器模式。
您的
JWTAuthenticationManager
似乎是单例,您希望在启动时绑定事件处理程序。如果是这样,您可以注册一个 HostedService。public class MyHostedService : IHostedService { private readonly IJWTAuthenticationManager _jWTAuthManager; private readonly ICacheManager _cacheManager; // suppose your IJWTAuthenticationManager is a singleton service public MyHostedService(IJWTAuthenticationManager jWTAuthManager, ICacheManager cacheManager) { this._jWTAuthManager = jWTAuthManager; this._cacheManager = cacheManager; } public Task StartAsync(CancellationToken cancellationToken) { this._jWTAuthManager.TokenExpired += this._cacheManager.OnTokenExpired; return Task.CompletedTask; } public Task StopAsync(CancellationToken cancellationToken) { this._jWTAuthManager.TokenExpired -= this._cacheManager.OnTokenExpired; return Task.CompletedTask; } }
并在 Startup 中注册此服务:
services.AddHostedService<MyHostedService>();
另一种不需要 HostedService 并在启动时启动的方法:
在Host.Run()
之前获取服务并绑定事件:
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var jwtMgr = host.Services.GetRequiredService<IJWTAuthenticationManager>();
var cacheMgr = host.Services.GetRequiredService<ICacheManager>();
jwtMgr.TokenExpired = cacheMgr.OnTokenExpired;
host.Run();
}