netcore 2.1 带 Scoped 存储库的分布式缓存
netcore 2.1 Distributed cache with Scoped repository
需要一些帮助。
我有一个 .netcore 2.1 API,它通过来自其客户端的 Azure Bearer 令牌进行保护。我想从客户端的不记名令牌中收集用户信息并将其存储在 SQL 数据库中,以便我可以在数据库中标记条目,如果它们是 added/deleted/edited 等等。对于 SQL table 加入因此我需要 SQL.
中的用户信息
下面是我使用 IDistributedCache 实现的缓存服务。在 Init 上,我试图将所有当前存储的用户从 SQL 数据库加载到缓存中,然后在新用户连接时添加到缓存中。
为了捕获整个 API 的连接,我使用了 TypeFilterAttribute 来执行 OnActionExecuting。
问题是 CacheService 是单例并且正在调用 UserRepository - 这是有范围的。这是不允许的。
有什么想法吗?
startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
// Context
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<CacheService>();
// Repositories
services.TryAddScoped<IUserRepository, UserRepository>();
services.AddDistributedMemoryCache();
services.AddMvc(
opts => opts.Filters.Add(new HttpInterceptor())
)
...
CacheService.cs
public class CacheService
{
private readonly IDistributedCache _cache;
private readonly IUserRepository _userRepository;
public CacheService(
IDistributedCache cache,
[FromServices] IUserRepository userRepository
)
{
_cache = cache;
_userRepository = userRepository;
// Populate cache from DB
var users = _userRepository.GetAll().Result;
foreach (var u in users)
{
if (_cache.GetAsync(u.Username).Result == null)
{
var profileSerialised = JsonConvert.SerializeObject(UserToUserProfile(u));
var entry = Encoding.UTF8.GetBytes(profileSerialised);
_cache.SetAsync(u.Username, entry, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
}
}
}
HttpInterceptor.cs
public class HttpInterceptor : TypeFilterAttribute
{
public HttpInterceptor() : base(typeof(IHttpInterceptor))
{
}
private class IHttpInterceptor : IActionFilter
{
private readonly CacheService _cache;
private readonly IUserRepository _userRepository;
public IHttpInterceptor(
CacheService cache,
IUserRepository userRepository)
{
_cache = cache;
_userRepository = userRepository;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated)
{
this._cache.GetUserProfile(context.HttpContext.User.Identity.Name);
}
}
首先,您正在颠倒和倒退地看待这个问题。让一些服务向缓存添加内容,然后让其他代码只是假设缓存中的内容准备就绪,这是灾难的根源。相反,让您的依赖代码直接请求它需要的数据,然后,如果您想缓存它,请使用获取数据的方法来执行。这样一来,您的应用程序代码就不会知道数据的来源;它只是调用一个方法并获取它想要的数据。在引擎盖下,它要么从数据库中提取,要么从缓存中提取,具体取决于 available/preferred.
无论如何,您的缓存服务存在严重问题。首先,它一开始就不应该是单例。没有理由这样做,而且由于您在内部处理范围内的服务,您只会让事情变得比他们需要的更困难。其次,永远不要在构造函数中使用 I/O 。只应在那里进行简单的 variable/prop 初始化。任何需要实际工作的东西都应该放在方法中。如果你真的想在初始化时做一些事情,那么你应该实现一个工厂模式。例如,您可能有类似 CacheServiceFactory
和 Create
方法的东西,returns 一个完全实例化的 CacheService
包括调用任何执行实际工作的方法。
抛开免责声明,一般来说,要在单例中使用范围服务,您必须创建一个范围。每次您想使用该服务时都必须这样做;您不能将服务持久化到您的单身人士 class 上的 ivar。简单地说,你将 IServiceProvider
注入到你的 class 中,它本身是单例范围的,所以你不会有任何问题。然后,当您需要使用范围服务时:
using (var scope = provider.CreateScope())
{
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
// do something with repo
}
这称为服务定位器反模式。之所以这样称呼,是因为这是您真正应该避免做的事情。有时这并不总是可能的。然而,通常情况下,您可以简单地以不同的方式设计事物:例如使服务范围自身。
需要一些帮助。
我有一个 .netcore 2.1 API,它通过来自其客户端的 Azure Bearer 令牌进行保护。我想从客户端的不记名令牌中收集用户信息并将其存储在 SQL 数据库中,以便我可以在数据库中标记条目,如果它们是 added/deleted/edited 等等。对于 SQL table 加入因此我需要 SQL.
中的用户信息下面是我使用 IDistributedCache 实现的缓存服务。在 Init 上,我试图将所有当前存储的用户从 SQL 数据库加载到缓存中,然后在新用户连接时添加到缓存中。
为了捕获整个 API 的连接,我使用了 TypeFilterAttribute 来执行 OnActionExecuting。
问题是 CacheService 是单例并且正在调用 UserRepository - 这是有范围的。这是不允许的。
有什么想法吗?
startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
// Context
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<CacheService>();
// Repositories
services.TryAddScoped<IUserRepository, UserRepository>();
services.AddDistributedMemoryCache();
services.AddMvc(
opts => opts.Filters.Add(new HttpInterceptor())
)
...
CacheService.cs
public class CacheService
{
private readonly IDistributedCache _cache;
private readonly IUserRepository _userRepository;
public CacheService(
IDistributedCache cache,
[FromServices] IUserRepository userRepository
)
{
_cache = cache;
_userRepository = userRepository;
// Populate cache from DB
var users = _userRepository.GetAll().Result;
foreach (var u in users)
{
if (_cache.GetAsync(u.Username).Result == null)
{
var profileSerialised = JsonConvert.SerializeObject(UserToUserProfile(u));
var entry = Encoding.UTF8.GetBytes(profileSerialised);
_cache.SetAsync(u.Username, entry, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
}
}
}
HttpInterceptor.cs
public class HttpInterceptor : TypeFilterAttribute
{
public HttpInterceptor() : base(typeof(IHttpInterceptor))
{
}
private class IHttpInterceptor : IActionFilter
{
private readonly CacheService _cache;
private readonly IUserRepository _userRepository;
public IHttpInterceptor(
CacheService cache,
IUserRepository userRepository)
{
_cache = cache;
_userRepository = userRepository;
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated)
{
this._cache.GetUserProfile(context.HttpContext.User.Identity.Name);
}
}
首先,您正在颠倒和倒退地看待这个问题。让一些服务向缓存添加内容,然后让其他代码只是假设缓存中的内容准备就绪,这是灾难的根源。相反,让您的依赖代码直接请求它需要的数据,然后,如果您想缓存它,请使用获取数据的方法来执行。这样一来,您的应用程序代码就不会知道数据的来源;它只是调用一个方法并获取它想要的数据。在引擎盖下,它要么从数据库中提取,要么从缓存中提取,具体取决于 available/preferred.
无论如何,您的缓存服务存在严重问题。首先,它一开始就不应该是单例。没有理由这样做,而且由于您在内部处理范围内的服务,您只会让事情变得比他们需要的更困难。其次,永远不要在构造函数中使用 I/O 。只应在那里进行简单的 variable/prop 初始化。任何需要实际工作的东西都应该放在方法中。如果你真的想在初始化时做一些事情,那么你应该实现一个工厂模式。例如,您可能有类似 CacheServiceFactory
和 Create
方法的东西,returns 一个完全实例化的 CacheService
包括调用任何执行实际工作的方法。
抛开免责声明,一般来说,要在单例中使用范围服务,您必须创建一个范围。每次您想使用该服务时都必须这样做;您不能将服务持久化到您的单身人士 class 上的 ivar。简单地说,你将 IServiceProvider
注入到你的 class 中,它本身是单例范围的,所以你不会有任何问题。然后,当您需要使用范围服务时:
using (var scope = provider.CreateScope())
{
var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
// do something with repo
}
这称为服务定位器反模式。之所以这样称呼,是因为这是您真正应该避免做的事情。有时这并不总是可能的。然而,通常情况下,您可以简单地以不同的方式设计事物:例如使服务范围自身。