You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 17:06:06