Skip to content

应用上下文(App/InternalApp)

本页介绍系统全局应用上下文的设计与用法,包括启动期注入、可用的“服务对象”清单、每个对象的作用与示例、以及服务解析和性能注意事项。

适用场景

  • 在“非控制器/非中间件/静态方法/后台任务”中获取 DI 容器中的服务
  • 快速访问配置、环境、HttpContext、当前用户、缓存、多语言、映射、选项配置等

内外部 App

说明:框架把“运行时实现/注入逻辑”与“对外调用入口”分成两个层次:

  • InternalApp(实现层)

    • 实现位置:Ape.Volo.Core/Internal/InternalApp.cs
    • 作用:在启动阶段写入环境、配置、RootServices,并维护临时 Scope/容器(UnmanagedObjects)、服务解析策略、Dispose 逻辑等内部细节。
    • 特性:负责 ConfigureApplication 的三个重载注入、临时 ServiceProvider 的创建与回收,以及性能/线程安全相关的内部实现。
    • 注意:InternalApp 包含框架内部用的 API,不推荐在业务代码中直接调用或依赖其内部状态。
  • App(对外层)

    • 实现位置:Ape.Volo.Core/App.cs
    • 作用:对外暴露安全、稳定的全局访问点(Configuration、HttpContext、GetService/GetRequiredService、GetOptions 等),并委托给 InternalApp 完成实际解析与生命周期管理。
    • 特性:面向业务,提供简单友好的辅助方法,屏蔽 InternalApp 的实现细节与资源管理。
    • 使用规范:在任意位置可通过 App 快速获取服务或环境,但应遵守生命周期最佳实践(避免从 RootServices 长期持有 Scoped 实例;后台任务使用显式创建的 Scope 并在结束时释放)。

启动注入(Program.cs)

应用启动阶段会把关键上下文写入 InternalApp,并由 App 对外暴露统一入口:

csharp
// 配置容器与配置源
builder.Host
    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    .ConfigureAppConfiguration((hostingContext, config) =>
    {
        hostingContext.Configuration.ConfigureApplication(); // 注入 IConfiguration
        config.Sources.Clear();
        config.AddJsonFile(builder.Environment.IsDevelopment() ? "appsettings.Development.json" : "appsettings.json",
            optional: true, reloadOnChange: false)
          .AddJsonFile("IpRateLimit.json", optional: true, reloadOnChange: false);
    })
    .UseSerilogMiddleware()
    .ConfigureContainer<ContainerBuilder>(b => b.RegisterModule(new AutofacExtensions()));

// 注入环境与 IServiceCollection
builder.ConfigureApplication();

var app = builder.Build();

// 注入 RootServices(IServiceProvider)
app.ConfigureApplication();

InternalApp 的三个重载:

  • ConfigureApplication(WebApplicationBuilder):注入 Host/Web 环境与 Services;
  • ConfigureApplication(IConfiguration):注入配置对象;
  • ConfigureApplication(IHost):注入 RootServices。

服务对象一览与示例

以下属性/方法均通过 App 暴露,便于在任意位置调用。

1) Configuration(配置)

  • 类型:IConfiguration
  • 作用:读取应用配置。
  • 示例:
csharp
var conn = App.Configuration["ConnectionStrings:Default"];

2) WebHostEnvironment / HostEnvironment(环境)

  • 类型:IWebHostEnvironment / IHostEnvironment
  • 作用:判断当前环境(Development/Staging/Production)与内容根路径等。
  • 示例:
csharp
if (App.WebHostEnvironment.IsDevelopment()) { /* dev-only */ }
var contentRoot = App.HostEnvironment.ContentRootPath;

3) RootServices(根服务提供器)

  • 类型:IServiceProvider
  • 作用:框架构建完成后的根级 ServiceProvider。
  • 建议:仅用于解析单例;解析 Scoped 服务应优先使用当前请求的 RequestServices。

4) HttpContext(请求上下文)

  • 类型:HttpContext
  • 作用:仅在 Web 请求期间可用;来自 IHttpContextAccessor
  • 示例:
csharp
var traceId = App.HttpContext?.TraceIdentifier;
var userAgent = App.HttpContext?.Request.Headers["User-Agent"].ToString();

5) HttpUser(当前用户)

  • 类型:IHttpUser
  • 作用:封装当前登录用户的帐号、Id、TenantId 等。
  • 示例:
csharp
var account = App.HttpUser?.Account;
var tenantId = App.HttpUser?.TenantId;

6) Cache(缓存)

  • 类型:ICache
  • 作用:统一缓存抽象;在启用 Redis 时可获取底层数据库进行更丰富操作。
  • 示例:
csharp
// 直接使用抽象
var cache = App.Cache;

// 使用底层 Redis 数据库(若启用)
await App.GetService<ICache>().GetDatabase()
    .StringSetAsync("key", "value", TimeSpan.FromMinutes(5));

7) Mapper(对象映射)

  • 类型:IMapper(AutoMapper)
  • 作用:DTO 与实体之间的映射。
  • 示例:
csharp
var dto = App.Mapper.Map<UserDto>(userEntity);

8) L(多语言)

  • 类型:ILocalizationService
  • 作用:多语言资源获取/格式化。
  • 示例:
csharp
var text = App.L.R("Error.AppSettings.DataConnection");

服务解析方法与差异

GetService / GetRequiredService

  • GetService<T>():找不到返回 null;
  • GetRequiredService<T>():找不到抛异常(推荐在“必需依赖”场景使用)。

示例:

csharp
var logger = App.GetRequiredService<ILogger<MySvc>>();
var optional = App.GetService<IMaybeSvc>();

GetServices(多实现解析)

csharp
var strategies = App.GetServices<IPaymentStrategy>();

GetServiceProvider(提供器选择策略)

内部选择顺序:

  1. 若解析的服务为单例且 RootServices 可用 → 直接用 RootServices;
  2. 若当前存在 HttpContext → 使用 HttpContext.RequestServices
  3. 若 RootServices 可用 → 创建新的 Scope 返回其 ServiceProvider(会被加入 UnmanagedObjects,需释放);
  4. 否则临时构建一个新的 ServiceProvider(性能最差,亦需释放)。

释放方式:

csharp
// 在批量或后台场景,使用完后建议释放临时作用域/容器
App.DisposeUnmanagedObjects();

选项(Options)辅助

已封装三种读取方式,对应 ASP.NET Core 的 Options 模型:

  • GetOptions<T>():等价 IOptions<T>.Value(单例,适合稳定配置);
  • GetOptionsMonitor<T>():等价 IOptionsMonitor<T>.CurrentValue(支持变更监听);
  • GetOptionsSnapshot<T>():等价 IOptionsSnapshot<T>.Value(Scoped,针对每次请求快照)。

示例:

csharp
var system = App.GetOptions<SystemOptions>();
var serilog = App.GetOptionsMonitor<SerilogOptions>();
var middleware = App.GetOptionsSnapshot<MiddlewareOptions>();

配合 Options 自动绑定与注册:详见《配置选项(Options)》。

运行时辅助

csharp
// 当前线程 Id
var tid = App.GetThreadId();

// 当前请求 TraceId(无请求时尝试从 Activity 或 HttpContext 获取)
var traceId = App.GetTraceId();

// 统计执行耗时(毫秒)
var cost = App.GetExecutionTime(() => DoWork());

// 查看服务生命周期(Singleton/Scoped/Transient)
var lifetime = App.GetServiceLifetime(typeof(IMyService));

最佳实践与注意事项

  • 解析 Scoped 服务应优先使用当前请求的 RequestServices;避免从 RootServices 解析 Scoped 服务并跨请求持有。
  • 在后台批处理等场景若创建了临时作用域/容器,调用 App.DisposeUnmanagedObjects() 及时释放,避免泄漏。
  • GetRequiredService 用于“硬依赖”,可尽早暴露注册问题;GetService 适合“可选依赖”。
  • HttpContext 可能为 null(如控制台任务/队列消费者),请注意空判断。
  • 读取可变配置建议使用 GetOptionsMonitor<T>();请求范围内的配置快照使用 GetOptionsSnapshot<T>()

常见问题(FAQ)

  1. 为什么 App.HttpContext 为 null?
  • 当前不在 Web 请求线程(如定时任务/消息消费);请使用显式创建的作用域注入所需服务。
  1. 解析到的服务生命周期不符合预期?
  • 使用 App.GetServiceLifetime(type) 检查注册;确保未从 RootServices 长期持有 Scoped 服务。
  1. 创建了很多作用域会泄漏吗?
  • 框架会把临时作用域/容器加入 UnmanagedObjects;请在合适时机调用 App.DisposeUnmanagedObjects(),框架内部带有 GC 间隔节流(默认 5 秒)。

相关文档:

版权所有 © 2021-2026 ApeVolo-Team