.net core 通过DI在IHostedService中利用用户上下文
我有一系列的类库,它们用于.net core 通过DI在IHostedService中利用用户上下文,.net-core,asp.net-core-mvc,ihostedservice,.net Core,Asp.net Core Mvc,Ihostedservice,我有一系列的类库,它们用于asp.netcore中间件和IHostedService 要获取用户上下文,我可以插入IHttpContextAccessor来获取HttpContext用户: 公共类MyLibrary { 公共MyLibrary(IHttpContextAccessor访问器) { //设置访问器-没问题 } 公共异步任务DoWorkAsync(SomeObject负载) { //从访问器获取用户 //做些工作 } } 更抽象一点,我有一个带有HttpUserAccessor实
asp.netcore
中间件和IHostedService
要获取用户上下文,我可以插入IHttpContextAccessor
来获取HttpContext
用户:
公共类MyLibrary
{
公共MyLibrary(IHttpContextAccessor访问器)
{
//设置访问器-没问题
}
公共异步任务DoWorkAsync(SomeObject负载)
{
//从访问器获取用户
//做些工作
}
}
更抽象一点,我有一个带有HttpUserAccessor
实现的IUserAccessor
:
公共类HttpUserAccessor:IUserAccessor
{
IHttpContextAccessor\uHttpAccessor;
公共HttpUserAccessor(IHttpContextAccessor访问器)
{
_httpaccessor=存取器;
}
公共字符串GetUser()
{
//从_httpaccessor返回用户
}
}
然后,MyLibrary
不需要IHttpContextAccessor
依赖项:
公共类MyLibrary
{
公共MyLibrary(IUserAccessor存取器)
{
//设置访问器-没问题
}
公共异步任务DoWorkAsync(SomeObject负载)
{
//从访问器获取用户
//做些工作
}
}
MyIHostedService
正在从队列中弹出消息,其中消息包括:
- 用户上下文,以及
- 要传递给MyLibrary.DoWorkAsync的序列化的
SomeObject
公共类MyHostedService:IHostedService
{
IServiceScopeProvider\u服务范围工厂;
公共MyHostedService(IServiceScopeFactory服务ScopeFactory)
{
_serviceScopeFactory=servicesScopeFactory;
}
公共任务StartSync(CancellationToken CancellationToken)
{ ... }
公共任务StopAsync(CancellationToken CancellationToken)
{ ... }
公共异步任务ExecuteAsync(CancellationToken stoppingToken)
{
foreach(队列中的var消息)
{
使用(var scope=\u serviceScopeFactory.CreateScope())
{
//todo:告诉IUserAccessor用户是什么消息!
var payload=//从队列消息创建一个SomeObject
var mylibrary=_services.GetRequiredService();
等待myLibrary.DoWorkAsync(有效负载);
}
}
}
}
因此,我的问题是,MyHostedService
如何存储消息。用户
以使自定义IUserAccessor
可以通过DI以线程安全的方式访问它
MyHostedService如何存储message.User,使自定义IUserAccessor可以通过DI以线程安全的方式访问它
您要查找的是asynchlocal
——它类似于线程局部变量,但作用域是(可能是异步的)代码块,而不是线程
我倾向于使用“提供者”+“存取者”配对:一种类型提供值,另一种类型读取值。这与JS世界中的React上下文在逻辑上是相同的,尽管实现方式有很大不同
AsyncLocal
的一个棘手问题是,您需要在任何更改中覆盖它的值。在这种情况下,这不是一个真正的问题(没有消息处理需要更新“用户”),但在一般情况下,记住这一点很重要。我更喜欢将不可变类型存储在AsyncLocal
中,以确保它们不会直接变异,而不是覆盖值。在本例中,您的“用户”是一个字符串
,它已经是不可变的,所以这是完美的
首先,您需要定义实际的AsyncLocal
,以保存用户值并定义一些低级访问器。我强烈建议使用IDisposable
,以确保AsyncLocal
值在作用域结束时正确取消设置:
public static class AsyncLocalUser
{
private static AsyncLocal<string> _local = new AsyncLocal<string>();
private static IDisposable Set(string newValue)
{
var oldValue = _local.Value;
_local.Value = newValue;
// I use Nito.Disposables; feel free to replace with another IDisposable implementation.
return Disposable.Create(() => _local.Value = oldValue);
}
private static string Get() => _local.Value;
}
访问器也同样简单:
public static class AsyncLocalUser
{
... // see above
public sealed class Accessor : IUserAccessor
{
public string GetUser() => Get();
}
}
您需要将DI设置为指向IUserAccessor
的AsyncLocalUser.Accessor
。您还可以选择向DI添加AsyncLocalUser.Provider
,也可以直接创建它
用法如下所示:
foreach (var message in queue)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var userProvider = new AsyncLocalUser.Provider(); // (or get it from DI)
using (userProvider.SetUser(message.User))
{
var payload = // create a SomeObject from the queue message
var mylibrary = _services.GetRequiredService<MyLibrary>();
await myLibrary.DoWorkAsync(payload);
}
}
}
foreach(队列中的var消息)
{
使用(var scope=\u serviceScopeFactory.CreateScope())
{
var userProvider=new asynchLocalUser.Provider();/(或从DI获取)
使用(userProvider.SetUser(message.User))
{
var payload=//从队列消息创建一个SomeObject
var mylibrary=_services.GetRequiredService();
等待myLibrary.DoWorkAsync(有效负载);
}
}
}
注入IServiceProvider
是一种代码气味。您应该使用IServiceScopeFactory
,但请记住,您所做的工作在请求管道之外。结果可能会有所不同,或者根本不起作用。您不应该在类似于Singleton的东西中依赖作用域服务,IHostedService
就是这样。“还有一些书要读吗?”安迪,说得好。我已经更新了问题中的示例IHostedService
。您关于在IHostedService中管理作用域服务的观点正是我的问题:什么是“正确”的方法?谢谢!我在IHttpContextAccessor
中看到了AsyncLocal
的使用,但是Set
的用法我不清楚。这就澄清了这一点——我很快就会尝试一下!
foreach (var message in queue)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var userProvider = new AsyncLocalUser.Provider(); // (or get it from DI)
using (userProvider.SetUser(message.User))
{
var payload = // create a SomeObject from the queue message
var mylibrary = _services.GetRequiredService<MyLibrary>();
await myLibrary.DoWorkAsync(payload);
}
}
}