agilelabs-fx-docs main topics/workcontext/aspnet-core.md

ASP.NET Core 中使用 WorkContext

本页说明 ASP.NET Core HTTP 请求中 WorkContext 的创建、读取、释放和常见排障。请求内代码的核心原则是:不要手动创建 WorkContext,直接使用框架自动附着的当前 IWorkContextCore

场景

适用于:

  • MVC Controller / WebAPI Controller。
  • Application Service、Domain Service、Repository 等请求内 Scoped 服务。
  • AutoMapper Resolver、Filter、Middleware 后续业务逻辑。
  • 请求中启动后台任务前的上下文边界判断。

不适用于:

正确入口

HTTP 请求里的 WorkContext 由框架自动创建。源码事实:

  • 文件:AgileLabs.WebApp/WorkContexts/WebWorkContextInitMiddleware.cs
  • 类名:HttpWorkContextInitMiddleware
  • 注册点:AgileLabContext 中调用 application.UseMiddleware<HttpWorkContextInitMiddleware>()

请求进入时,中间件调用:

context.RequestServices.AttachWorkContextForCurrentScope();

请求结束时,中间件调用:

context.RequestServices.DisposeCurrentWorkContext();

因此 Controller、Service、Repository 不需要自己 new WorkContext,也不需要调用 AttachWorkContextForCurrentScope()

请求生命周期

sequenceDiagram
    participant Client as Client
    participant Middleware as HttpWorkContextInitMiddleware
    participant RequestScope as HttpContext.RequestServices
    participant Accessor as IWorkContextAccessor
    participant Action as Controller/Service

    Client->>Middleware: HTTP Request
    Middleware->>RequestScope: AttachWorkContextForCurrentScope()
    RequestScope->>Accessor: SetContext(workContext)
    Middleware->>Action: 执行业务逻辑
    Action->>Action: 注入 IWorkContextCore
    Action-->>Middleware: 返回结果
    Middleware->>RequestScope: DisposeCurrentWorkContext()
    Middleware-->>Client: HTTP Response

示例:Controller 中读取当前上下文

[ApiController]
public class AccountController : ControllerBase
{
    private readonly IWorkContextCore _workContext;

    public AccountController(IWorkContextCore workContext)
    {
        _workContext = workContext;
    }

    [HttpGet("/account/me")]
    public IActionResult Me()
    {
        return Ok(new
        {
            Id = _workContext.Identity.Id,
            Name = _workContext.Identity.Name,
            Culture = _workContext.CultureInfo.Name,
            TimeZone = _workContext.TimeZoneInfo.Id,
            TraceId = _workContext.Activity?.TraceId.ToString()
        });
    }
}

示例:Application Service 中使用当前上下文

public class OrderAppService
{
    private readonly IWorkContextCore _workContext;
    private readonly IOrderRepository _orders;

    public OrderAppService(IWorkContextCore workContext, IOrderRepository orders)
    {
        _workContext = workContext;
        _orders = orders;
    }

    public async Task SubmitAsync(SubmitOrderCommand command)
    {
        var userId = _workContext.Identity.Id;
        var userTimeZone = _workContext.TimeZoneInfo;

        await _orders.SubmitAsync(command.OrderId, userId, userTimeZone);
    }
}

请求内临时切换身份或租户

需要临时切换身份、租户、语言或时区时,不要直接修改父请求上下文。推荐在同线程子 Scope 内处理:

using var scope = _workContext.CreateScopeWithWorkContext(scopeName: "SystemApproval");
var setter = scope.WorkContext.Resolve<IWorkContextCoreSetter>();

setter.SetIdentity(new WorkContextIdentityInfo
{
    Type = "System",
    Id = "system",
    Name = "System",
    IsAuthenticated = true
});

var service = scope.WorkContext.Resolve<IApprovalService>();
await service.ApproveAsync(command);

CreateScopeWithWorkContext() 用于同线程/同执行流临时隔离。Dispose 后框架会恢复父 WorkContext。

请求中启动后台任务

请求中可以启动后台任务,但不能把请求 Scope 里的服务直接传进去。只传递必要的值,并在子任务中创建新的 WorkContext Scope:

public Task SendMailLaterAsync(string mailId)
{
    _ = Task.Run(async () =>
    {
        using var scope = _workContext.CreateScopeWithWorkContextForNewTask(scopeName: "SendMail");
        var mailService = scope.WorkContext.Resolve<IMailService>();
        await mailService.SendAsync(mailId);
    });

    return Task.CompletedTask;
}

更完整的新线程说明见 Task.Run 与新线程中使用 WorkContext

错误用法

  • 在 Controller 中手动 new DefaultWorkContextCore()
  • 在 Controller 中调用 AttachWorkContextForCurrentScope() 重复附着。
  • HttpContext.RequestServices 缓存到字段并在请求结束后继续使用。
  • 把请求内注入出来的 Repository、DbContext、Service 传进 Task.Run
  • 请求内直接修改父 WorkContext 身份来做租户切换或系统身份模拟。

排障

请求里拿不到 IWorkContextCore

检查:

  • HttpWorkContextInitMiddleware 是否进入 ASP.NET Core 管道。
  • 当前代码是否真的在 HTTP 请求内执行。
  • 是否从 RootServiceProvider 解析了业务服务。
  • 是否在请求结束后继续使用请求内对象。

保存数据库时报 workContext为null

EF Core AgileLabDbContext.SaveChanges() 会读取 AgileLabContexts.Context.CurrentWorkContext 来补审计字段。HTTP 请求里通常自动满足;如果报空,优先检查当前保存逻辑是否已经跑到请求外线程,或是否绕过了当前请求 Scope。

身份、时区或语言不是预期值

检查:

  • 认证或请求 Session 是否正确设置了当前身份。
  • 是否在父请求 WorkContext 上直接做了临时切换。
  • 是否创建了子 Scope 但忘记在子 Scope 内解析服务。

相关页面