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 后续业务逻辑。
- 请求中启动后台任务前的上下文边界判断。
不适用于:
Task.Run、线程池、新线程里的业务处理。请看 Task.Run 与新线程中使用 WorkContext。- Hangfire Job。请看 Hangfire 中使用 WorkContext。
正确入口
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 内解析服务。