ASP.NET Core 2.1+NServiceBus7:处理程序外访问消息头遇空引用求助
我之前也碰到过类似的坑,咱们一步步拆解问题,先搞清楚为什么ContextAccessor.Get()会返回空,再给你靠谱的解决办法。
先分析大概率的问题根源
1. 仓储类的生命周期错了
NServiceBus的IMessageHandlerContext是消息处理作用域内的临时对象——每处理一条消息,就会创建一个对应的上下文,处理完就销毁。如果你的仓储类注册成了Singleton(单例),那它的实例在应用启动时就会被创建,完全不在消息处理的作用域里,自然拿不到上下文。
2. 没有正确集成NServiceBus与ASP.NET Core的DI容器
如果你的Endpoint配置里没把NServiceBus和ASP.NET Core的DI绑定到一起,IMessageHandlerContextAccessor可能没被正确注册到作用域中,导致获取不到上下文。
3. 在错误的时机获取上下文
比如你在仓储的构造函数里调用ContextAccessor.Context——这时候消息处理流程还没启动,上下文根本没被初始化,肯定是空的。
解决方案一:用ContextAccessor(推荐正确姿势)
如果坚持要通过IMessageHandlerContextAccessor来获取,按下面的步骤来:
第一步:确保仓储是Scoped/Transient生命周期
在Startup.cs里注册仓储时,别用AddSingleton,改用AddScoped或者AddTransient:
services.AddScoped<IOrderRepository, OrderRepository>();
第二步:正确注入并使用ContextAccessor
仓储类里注入IMessageHandlerContextAccessor,在具体方法内获取上下文(别在构造函数里搞):
public class OrderRepository : IOrderRepository { private readonly IMessageHandlerContextAccessor _contextAccessor; public OrderRepository(IMessageHandlerContextAccessor contextAccessor) { _contextAccessor = contextAccessor; } public async Task SaveOrder(Order order) { var context = _contextAccessor.Context; if (context == null) { throw new InvalidOperationException("请确保在NServiceBus消息处理流程中调用此方法!"); } // 读取自定义消息头 if (context.MessageHeaders.TryGetValue("X-Custom-Header", out var headerValue)) { order.CorrelationId = headerValue; } // 执行你的保存逻辑 // ... } }
第三步:检查NServiceBus的Endpoint配置
确保你已经把NServiceBus和ASP.NET Core的DI容器集成了,这样上下文访问器才会被正确注入到作用域中:
var endpointConfiguration = new EndpointConfiguration("YourEndpointName"); endpointConfiguration.UseTransport<LearningTransport>(); // 替换成你用的Transport // 关键:让NServiceBus使用ASP.NET Core的DI容器 endpointConfiguration.UseContainer<ServiceProviderAdapter>(customization => { customization.ExistingServices(services); });
解决方案二:直接传递上下文(更直观,不易出错)
如果不想依赖ContextAccessor,还有个更简单的方式——在消息处理程序里,直接把IMessageHandlerContext作为参数传给仓储的方法:
消息处理程序代码
public class CreateOrderHandler : IHandleMessages<CreateOrderCommand> { private readonly IOrderRepository _orderRepository; public CreateOrderHandler(IOrderRepository orderRepository) { _orderRepository = orderRepository; } public async Task Handle(CreateOrderCommand message, IMessageHandlerContext context) { var order = new Order(message.OrderId); // 直接把上下文传给仓储方法 await _orderRepository.SaveOrder(order, context); } }
仓储类修改
public async Task SaveOrder(Order order, IMessageHandlerContext context) { if (context.MessageHeaders.TryGetValue("X-Custom-Header", out var headerValue)) { order.CorrelationId = headerValue; } // 执行保存逻辑 // ... }
这种方式完全避开了作用域的问题,逻辑也更清晰,我个人更推荐这种。
最后再排查一遍
如果还是不行,检查这两点:
- 有没有在非消息处理的场景下调用仓储方法?比如API接口直接调用,这时候本来就没有消息上下文,自然会空。
- 确认NServiceBus的版本是7.x,和ASP.NET Core 2.1的兼容性没问题(官方是兼容的)。
内容的提问来源于stack exchange,提问作者Sajan

