如何在.NET不同DI作用域中访问或传递CallContext存储的上下文数据
问题原因
你当前的CallContext注册为Scoped生命周期,DI容器会为每个独立的作用域创建全新的CallContext实例:父作用域中写入的值只存在于父作用域的CallContext对象中,新创建的子作用域拿到的是新实例,自然无法读取到父作用域写入的数据。
解决方案
方案1:调整CallContext生命周期为Singleton(最简便)
你CallContext内部用AsyncLocal存储数据,而AsyncLocal本身是和当前异步执行流绑定的,同一个异步流(同一个请求)内数据共享,不同异步流(不同请求)数据完全隔离,即使CallContext是单例也不会出现不同请求数据串扰的问题。
仅需修改Startup中CallContext的注册逻辑即可:
// 原来的注册 // services.AddScoped<CallContext>(); // 修改为单例注册 services.AddSingleton<CallContext>();
修改后所有作用域拿到的都是同一个CallContext实例,同一个请求流内任意子作用域都可以读取到之前写入的值。
方案2:手动复制父作用域数据到子作用域
如果你必须保留CallContext的Scoped生命周期,可以在创建子作用域后手动同步数据:
修改CustomerService的Log方法中创建子作用域的部分:
using (var scope = _serviceScopeFactory.CreateScope()) { // 拿到子作用域的CallContext实例 var childCallContext = scope.ServiceProvider.GetRequiredService<CallContext>(); // 复制父作用域的所有值到子作用域 childCallContext.SetData("ThreadId", _callContext.GetData("ThreadId")); childCallContext.SetData("URL", _callContext.GetData("URL")); var customerPortalService = scope.ServiceProvider.GetRequiredService<ISecondCustomerService>(); customerPortalService.Log(); }
方案3:直接复用IHttpContextAccessor
你当前需要存储的TraceIdentifier和请求URL本身就是HttpContext的内置属性,IHttpContextAccessor是自带的单例服务,内部也是基于AsyncLocal实现,同一个请求内任意作用域都可以直接读取到对应的值,完全不需要自己封装CallContext存储这两个值:
直接在SecondCustomerService中注入IHttpContextAccessor即可读取:
public class SecondCustomerService : ISecondCustomerService { private IHttpContextAccessor _httpContextAccessor; public SecondCustomerService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public void Log() { var threadId = _httpContextAccessor.HttpContext?.TraceIdentifier; var url = _httpContextAccessor.HttpContext != null ? $"{_httpContextAccessor.HttpContext.Request.Scheme}://{_httpContextAccessor.HttpContext.Request.Host.Value}{_httpContextAccessor.HttpContext.Request.Path.Value}" : null; } }
内容的提问来源于stack exchange,提问作者Mousam
相关产品推荐
相关产品推荐

