Core项目EventHandler无法获取用户,SignalR消息发送失败解决方案咨询
解决方案:持久化用户名到领域事件避免HttpContext为空问题
核心结论
必须在初始请求阶段获取用户名并传递给领域事件,不能等到SaveChangesAsync发布事件时再通过IUserAccessorService读取HttpContext。原因在于:
- 使用
ConfigureAwait(false)后,事件发布代码会脱离原请求同步上下文,HttpContext可能已被回收或不存在 - 领域事件应该是自包含的,携带所有必要信息,不依赖外部上下文(符合DDD原则)
具体实现步骤
1. 在命令中携带用户名
在UI层(Razor Pages)处理请求时,提前通过IUserAccessorService获取当前用户名,赋值给命令对象:
// 命令定义 public class AddMemberCommand : IRequest<AddMemberCommandResponse> { public int GroupId { get; set; } public Contact NewMember { get; set; } public string UserName { get; set; } // 新增用户名字段 } // Razor Pages中的请求处理代码 public async Task<IActionResult> OnPostAddMember() { var command = new AddMemberCommand { GroupId = _selectedGroupId, NewMember = _newMemberFormData, UserName = _userAccessor.GetUserName() // 提前获取用户名 }; await _mediator.Send(command); return RedirectToPage("./GroupDetails"); }
2. 修改领域事件基类,强制携带用户名
让所有领域事件都内置操作人信息,确保事件本身不依赖外部服务:
public abstract class DomainEvent : INotification { public DomainEvent(string userName) { UserName = userName ?? throw new ArgumentNullException(nameof(userName)); } public string UserName { get; } // 只读属性,确保不可修改 } // 具体的成员添加事件 public class MemberAddedEvent : DomainEvent { public Contact NewMember { get; } public MemberAddedEvent(Contact newMember, string userName) : base(userName) { NewMember = newMember; } }
3. 修改实体方法,接收用户名参数
实体不应该依赖HttpContext或用户访问服务,所以AddMember方法需要从外部传入用户名:
public class Group { public List<Contact> Members { get; set; } = new(); public List<DomainEvent> DomainEvents { get; private set; } = new(); public void AddMember(Contact newMember, string userName) { Members.Add(newMember); var memberAddedEvent = new MemberAddedEvent(newMember, userName); DomainEvents.Add(memberAddedEvent); } }
4. 在命令处理器中传递用户名到实体
处理命令时,将命令中的用户名传递给实体的AddMember方法:
public class AddMemberCommandHandler : IRequestHandler<AddMemberCommand, AddMemberCommandResponse> { private readonly AppDbContext _dbContext; public AddMemberCommandHandler(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<AddMemberCommandResponse> Handle(AddMemberCommand request, CancellationToken cancellationToken) { var group = await _dbContext.Groups.FindAsync(request.GroupId, cancellationToken); if (group == null) { throw new KeyNotFoundException("指定群组不存在"); } group.AddMember(request.NewMember, request.UserName); // 传递用户名 await _dbContext.SaveChangesAsync(cancellationToken); return new AddMemberCommandResponse { Success = true }; } }
5. 事件处理器直接使用事件内置的用户名
不再依赖IUserAccessorService,直接从事件中读取用户名:
public class MemberAddedEventHandler : INotificationHandler<MemberAddedEvent> { private readonly IHubContext<MessageHub> _hubContext; public MemberAddedEventHandler(IHubContext<MessageHub> hubContext) { _hubContext = hubContext; } public async Task Handle(MemberAddedEvent notification, CancellationToken cancellationToken) { var notificationMessage = $"用户{notification.UserName}添加了新成员:{notification.NewMember.Name}"; await _hubContext.Clients.All.SendAsync("ReceiveNotification", notificationMessage, cancellationToken); } }
额外说明
- 这种方式彻底避免了对
HttpContext的依赖,无论事件是同步还是异步处理,都能稳定获取操作人信息 - 符合领域驱动设计中"领域事件应包含所有必要上下文"的原则,事件本身具备完整的业务意义
内容的提问来源于stack exchange,提问作者Steven Bitaxi
相关产品推荐
相关产品推荐

