ASP.NET Core与MediatR:能否从处理器发送请求?遇对象释放异常
看起来你在处理MediatR嵌套请求时踩了个常见的坑——当你在AddUserHandler里调用_mediator.Send触发AddUserRepository请求时,出现了服务提供者已被释放的异常。我来帮你拆解问题并给出可行的解决办法。
问题原因
这个异常的核心是当前请求的服务范围(Service Scope)已经被释放,但你还在尝试用它来解析第二个请求的处理器。MediatR默认会在请求处理流程结束后自动释放关联的服务范围,如果你的第一个处理器的生命周期和这个scope绑定,当异步操作延迟执行时,很可能scope已经被提前回收了,导致后续调用无法获取服务。
解决方案
方案1:使用嵌套服务范围(推荐)
通过注入IServiceScopeFactory创建一个独立的嵌套scope,在这个新scope内获取MediatR实例来发送第二个请求。这样第二个请求的服务生命周期由新scope管理,不会受第一个请求的scope释放影响。
修改后的处理器代码如下:
public class AddUserHandler : IRequestHandler<AddUser, User> { private readonly IUserRepository _userRepository; private readonly IServiceScopeFactory _serviceScopeFactory; public AddUserHandler(IUserRepository userRepository, IServiceScopeFactory serviceScopeFactory) { _userRepository = userRepository; _serviceScopeFactory = serviceScopeFactory; } public async Task<User> Handle(AddUser request, CancellationToken token) { var user = _userRepository.Add(request.User); // 创建独立的服务范围,确保第二个请求的服务能正常解析 using var scope = _serviceScopeFactory.CreateScope(); var scopedMediator = scope.ServiceProvider.GetRequiredService<IMediator>(); await scopedMediator.Send(new AddUserRepository(user.Id), token); return user; } }
方案2:检查DI注册的生命周期
确保你的MediatR处理器和相关服务注册为Scoped或Transient,而不是Singleton。如果处理器被注册为Singleton,它会持有一个已被释放的scope引用,后续调用必然报错。
在ASP.NET Core中的正确注册示例:
// 自动注册程序集中的所有MediatR服务(默认是Scoped) services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(AddUser).Assembly)); // 或者手动指定处理器的生命周期 services.AddScoped<IRequestHandler<AddUser, User>, AddUserHandler>(); services.AddScoped<IRequestHandler<AddUserRepository, Unit>, AddUserRepositoryHandler>();
方案3:直接集成业务逻辑(适合简单场景)
如果创建仓库的逻辑比较简单,你可以直接在AddUserHandler中注入仓库的仓储服务,跳过MediatR请求,这样能彻底避免scope问题,但会牺牲一部分代码解耦性。
示例代码:
public class AddUserHandler : IRequestHandler<AddUser, User> { private readonly IUserRepository _userRepository; private readonly IRepositoryRepository _repositoryRepository; public AddUserHandler(IUserRepository userRepository, IRepositoryRepository repositoryRepository) { _userRepository = userRepository; _repositoryRepository = repositoryRepository; } public async Task<User> Handle(AddUser request, CancellationToken token) { var user = _userRepository.Add(request.User); // 直接调用创建空仓库的逻辑 await _repositoryRepository.CreateEmptyRepository(user.Id, token); return user; } }
总结
最推荐的是方案1,它既保留了MediatR带来的代码解耦优势,又完美解决了服务范围释放的问题。如果你的业务场景允许,方案3也是一种简洁的选择。
内容的提问来源于stack exchange,提问作者Petr Bogoyavlenskiy

