配置模型与选项
这一页讲应用与框架怎样读配置:框架沿用哪套配置模型、XiHan:<领域> 配置节怎样命名、应用自己的选项怎样绑定与校验、哪些值能在运行中更新。读完你能给应用加一组自己的选项,并知道框架各包从哪个配置节读取什么。前置知识是装配入口:你需要知道 AddXiHan 在什么时候执行、做了什么。
为什么需要
一个应用总有一些值不能写死在代码里:端口、连接地址、超时、容量上限、功能开关。它们在开发机、测试环境和生产集群里各不相同,还要能在不改代码的前提下调整。.NET 自带 Microsoft.Extensions.Configuration,已经把文件、环境变量、命令行这些来源统一成一棵键值树;如果框架再造一套配置模型,应用就要学两套写法、维护两份来源。
XiHan.Framework 因此不另造配置模型:应用与框架都读同一个 IConfiguration。框架只约定两件事:它自己的配置节一律挂在 XiHan:<领域> 下;各包的选项按键显式读取而不经反射绑定,所以在 Native AOT 下照常工作。应用自己的选项照旧用 IOptions<T> / IOptionsMonitor<T>。
配置模型
| 概念 | 说明 |
|---|---|
IConfiguration | .NET 的配置树;appsettings.json、appsettings.<环境>.json、环境变量、命令行都是它的来源 |
| 配置节 | 树上的一个子树,层级以 : 分隔,例如 XiHan:Kernel |
| 框架选项 | 各包里带 ConfigurationSection 常量的 record(KernelOptions、ResilienceOptions、NacosOptions 等),由静态方法 FromConfiguration(IConfiguration) 逐键读取 |
| 应用选项 | 应用自己的类或 record,用 IOptions<T> / IOptionsMonitor<T> 绑定 |
Configuration.Abstractions | 只定义两个 Provider 契约(远程配置客户端与密钥库)以及把它们接进 IConfigurationBuilder 的配置源,见 远程配置与密钥 |
框架没有「默认配置包」:不装任何配置相关的包,appsettings.json 与环境变量就是全部来源。
框架选项的读取时机
特性在装配时读取一次选项并登记为单例。也就是说 AddXiHan 返回之后再改 XiHan:* 下的键,要重启才对框架生效。命名存储实例是例外,它的定义在首次取用时才从 XiHan:ObjectStorage:Stores:<name> 读取,见分片、预签名与命名实例。
AddXiHan 在调用时就求解能力图谱、解析资源绑定、装配全部特性,所以远程配置、密钥这类配置源必须在 AddXiHan 之前加入 builder.Configuration,否则框架的选项与绑定读不到它们。
配置节命名
框架的配置节一律以 XiHan:<领域> 开头,领域词取自规范术语表(Kernel、Persistence、Caching、Localization、ObjectStorage、Configuration 等)。实现包的配置在领域节下再加一段实现名,例如 XiHan:Caching:Redis、XiHan:ObjectStorage:FileSystem;领域节本身放抽象层的选项,例如 XiHan:Localization:DefaultLocale。
同一个键有三种等价写法:
| 写法 | 例子 |
|---|---|
| JSON | { "XiHan": { "Kernel": { "StopTimeoutSeconds": 25 } } } |
| 配置键 | XiHan:Kernel:StopTimeoutSeconds |
| 环境变量 | XiHan__Kernel__StopTimeoutSeconds=25 |
几条约定:
- 时长类的键带单位后缀(
StopTimeoutSeconds、RefreshIntervalSeconds、MultipartExpiryHours),在代码里对应TimeSpan属性。 - 值不合法(负数、无法解析的枚举或布尔值)时
FromConfiguration抛出ArgumentException,应用在启动期失败,不会带着错误配置运行。 - 应用自己的配置节用自己的名字,不放进
XiHan下。下面的示例用Sample。 - 特性清单的
Contributes.Config声明它读取的配置的最近公共节:只读自己子节的写子节(Caching.Hybrid写XiHan:Caching:Hybrid),同时读领域节下公共选项的写领域节(Caching.Redis还读XiHan:Caching:Idempotency,写XiHan:Caching),见特性与清单与规范 RFC-0002。
最小示例
我们给应用加一个 Sample:Banner 选项,并用一个端点返回它的当前值。先写选项类:
// BannerOptions.cs
namespace Shop;
public sealed class BannerOptions
{
public const string Section = "Sample";
public string Banner { get; set; } = "Hello from XiHan";
}再在 Program.cs 里绑定并使用:
// Program.cs
using Microsoft.Extensions.Options;
using Shop;
using XiHan.Framework.Hosting.AspNetCore;
using XiHan.Framework.Profiles.Standard;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<BannerOptions>(builder.Configuration.GetSection(BannerOptions.Section));
builder.AddXiHan(xihan => xihan.UseStandardProfile());
var app = builder.Build();
app.UseXiHan();
// 读 CurrentValue 而不是在启动时缓存,配置源重载后返回新值
app.MapGet("/banner", (IOptionsMonitor<BannerOptions> banner) => banner.CurrentValue.Banner);
app.Run();appsettings.json:
{
"Sample": {
"Banner": "Hello from appsettings.json"
},
"XiHan": {
"Kernel": {
"StopTimeoutSeconds": 25
}
}
}运行后 curl localhost:5000/banner 返回 Hello from appsettings.json。设置环境变量 Sample__Banner="Hello from env" 再启动,返回的就是环境变量的值:环境变量覆盖文件。XiHan:Kernel:StopTimeoutSeconds 由内核的 KernelOptions.FromConfiguration 读取,决定每个特性停止的超时,应用代码不用碰它。
应用自己的选项
IOptions 与 IOptionsMonitor
| 注入类型 | 何时取值 | 适用 |
|---|---|---|
IOptions<T> | 首次取用时绑定一次,之后不变 | 启动后不再变化的值 |
IOptionsMonitor<T> | CurrentValue 每次返回当前值,配置源重载后更新;OnChange 可注册回调 | 需要热更新的值,见 配置目录与热更新 |
IOptionsSnapshot<T> | 每个服务作用域绑定一次 | 一次调用内保持一致、跨调用可变 |
校验
选项值不合法时最好在启动期暴露。.NET 的选项构建器支持声明校验,并能要求在启动时执行:
builder.Services.AddOptions<BannerOptions>()
.Bind(builder.Configuration.GetSection(BannerOptions.Section))
.Validate(static options => !string.IsNullOrWhiteSpace(options.Banner), "Sample:Banner must not be blank.")
.ValidateOnStart();这与框架选项的做法一致:配置错误属于编程与部署错误,应用启动失败,而不是带着坏值运行。这类消息面向开发与运维,用英文,不走本地化,区分方式见结果与错误处理。
Native AOT 下的绑定
Configure<T>(IConfiguration) 默认按反射绑定。要以 Native AOT 发布,在项目文件里打开配置绑定源生成器,绑定代码在编译期生成,IOptionsMonitor<T> 的重载也照常工作:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>也可以像框架一样按键读取、不绑定。AOT 的完整要求见 JSON 序列化与 Native AOT。
配置节一览
下表列出各包在代码里读取的配置节(ConfigurationSection 常量与 FromConfiguration 读取的键)。XiHan:Providers:<能力 ID> 指定某个能力由哪份清单提供,原理见能力图谱与 Provider;XiHan:Bindings 见 资源绑定。
| 配置节 | 所属包 | 用途 |
|---|---|---|
XiHan:Providers | Kernel | <能力 ID> → 清单 ID,指定能力的提供者 |
XiHan:Bindings | Kernel | 资源绑定 |
XiHan:Kernel | Kernel | StartTimeoutSeconds、StopTimeoutSeconds、FailurePolicy、UseBuiltInMiddleware |
XiHan:Kernel:Resilience | Kernel | Resilience 阶段的默认策略,Operations:<操作 ID> 按操作覆盖,见弹性与限流 |
XiHan:Persistence:InMemory | Persistence.InMemory | MaxEntitiesPerType |
XiHan:Persistence:EntityFrameworkCore | Persistence.EntityFrameworkCore | EnsureCreated、HealthProbeIntervalSeconds |
XiHan:Persistence:SqlSugar | Persistence.SqlSugar | InitTables、CommandTimeoutSeconds、Replicas:*(从库的权重与健康探测)、HealthProbeIntervalSeconds |
XiHan:Persistence:Snowflake | Persistence.Abstractions | 雪花标识:WorkerId、Epoch、LeaseTimeToLiveSeconds、ClockDriftToleranceMilliseconds |
XiHan:MultiTenancy | MultiTenancy.Abstractions | DefaultTenant、RequireTenant、TenantClaim、ValidateAgainstStore |
XiHan:MultiTenancy:InMemory | MultiTenancy.InMemory | MaxTenants |
XiHan:MultiTenancy:Tenants | MultiTenancy.InMemory | 从配置装载的租户(DisplayName、IsEnabled、Properties) |
XiHan:MultiTenancy:Database | MultiTenancy.Database | CacheSize、CacheTimeToLiveSeconds、MaxListedTenants |
XiHan:Security | Security.Abstractions | RequireAuthentication、RoleClaim |
XiHan:Security:Outbound | Kernel | 出站地址策略:AllowLoopback、AllowPrivateNetwork、AllowedNetworks、AllowedHosts |
XiHan:Security:InMemory | Security.InMemory | 开发用主体与授予(Principals、Grants) |
XiHan:Security:Database | Security.Database | CacheSize、CacheTimeToLiveSeconds |
XiHan:Security:Jwt | Security.Jwt | Issuer、Audience、Secret、ClockSkewSeconds、NameClaim |
XiHan:Security:OpenIdConnect | Security.OpenIdConnect | Authority、Audience、元数据地址与刷新间隔 |
XiHan:Security:Ip2Region | Security.Ip2Region | xdb 数据文件路径 V4Database、V6Database |
XiHan:Caching:InMemory | Caching.InMemory | MaxEntries、DefaultTimeToLiveSeconds |
XiHan:Caching:Redis | Caching.Redis | Configuration、KeyPrefix、DefaultTimeToLiveSeconds、PublishChanges、HealthProbeIntervalSeconds |
XiHan:Caching:Hybrid | Caching.Hybrid | LocalCapacity、LocalTimeToLiveSeconds |
XiHan:Caching:Remote | Caching.Remote | TimeoutMilliseconds |
XiHan:Caching:Idempotency | Caching.Abstractions | 幂等记录的 TimeToLiveSeconds、InProgressTimeToLiveSeconds |
XiHan:Messaging | Messaging.Abstractions | MaxAttempts、DeadLetterSuffix、InstanceId |
XiHan:Messaging:Outbox | Messaging.Abstractions | 发件箱的 PollIntervalSeconds、BatchSize、MaxAttempts、RetryDelaySeconds、MaxRetryDelaySeconds、DispatchedRetentionSeconds |
XiHan:Messaging:InMemory | Messaging.InMemory | MaxQueuedMessages |
XiHan:Messaging:RabbitMQ | Messaging.RabbitMQ | Configuration、QueuePrefix、PrefetchCount、HealthProbeIntervalSeconds |
XiHan:Messaging:Kafka | Messaging.Kafka | BootstrapServers、GroupId、TopicPrefix、HealthProbeIntervalSeconds、RedeliveryDelaySeconds、Properties:<key> |
XiHan:Scheduling | Scheduling.Abstractions | 调度引擎与领导选举:轮询、租约、并发、错过阈值、历史保留 |
XiHan:Scheduling:InMemory | Scheduling.InMemory | MaxRuns |
XiHan:Scheduling:Database | Scheduling.Database | HealthProbeIntervalSeconds |
XiHan:Scheduling:Kubernetes | Scheduling.Kubernetes | Lease 选举:Lease、Namespace、Holder、ApiServer |
XiHan:Workflow | Workflow.Abstractions | 引擎:锁超时、子流程深度、表达式缓存、保留天数 |
XiHan:Workflow:InMemory | Workflow.InMemory | 各存储容量、轮询周期与批大小 |
XiHan:Workflow:Database | Workflow.Database | 到期书签的轮询与批大小、租约时长、HolderId |
XiHan:Localization | Localization.Abstractions | DefaultLocale、Locales |
XiHan:Localization:Database | Localization.Database | RefreshIntervalSeconds、MaxTranslations |
XiHan:ObjectStorage:FileSystem | ObjectStorage.FileSystem | RootPath、MaxTotalBytes、MaxBlobBytes、MultipartExpiryHours |
XiHan:ObjectStorage:S3 | ObjectStorage.S3 | ServiceUrl、Region、AccessKey、SecretKey、ForcePathStyle 等 |
XiHan:ObjectStorage:Stores | ObjectStorage.Abstractions | 命名存储实例:<name>:Provider 加该 Provider 的设置 |
XiHan:ObjectStorage:StoreHealthProbeIntervalSeconds | ObjectStorage.Abstractions | 命名实例健康探测的间隔(单个键) |
XiHan:ObjectStorage:SignedUrls | Hosting.AspNetCore | 宿主代收发预签名地址的 BaseUrl、Secret |
XiHan:Configuration:Nacos | Configuration.Nacos | Nacos 服务地址、分组、命名空间、登录与超时 |
XiHan:Auditing | Auditing.Abstractions | Selection、IncludeSuccesses |
XiHan:Auditing:InMemory | Auditing.InMemory | Capacity、LogEvents |
XiHan:Auditing:Database | Auditing.Database | QueueCapacity、BatchSize、FlushIntervalSeconds、LogEvents |
XiHan:Observability:Host | Observability.Host | SampleIntervalSeconds |
XiHan:Observability:OpenTelemetry | Observability.OpenTelemetry | 服务名、追踪、指标、日志、采样与 OTLP 导出 |
XiHan:AI | AI.Abstractions | Provider 表、工具名、护栏、知识库与提示词 |
XiHan:Protocols:Http | Protocols.Http | RoutePrefix,Routes:<操作 ID> 覆盖推导的路由 |
XiHan:Protocols:Grpc | Protocols.Grpc | Package,Methods:<操作 ID> 覆盖推导的方法名 |
XiHan:Protocols:Mcp | Protocols.Mcp | ServerName、ServerVersion、Instructions,Tools:<操作 ID> |
XiHan:Protocols:CloudEvents | Protocols.CloudEvents | Inbound、Outbound、Topics:<事件类型>、Webhooks:<主题> |
XiHan:Plugins:OutOfProcess | Plugins.OutOfProcess | 插件目录、授予、握手与启停超时、健康与重启限制 |
XiHan:Plugins:Wasm | Plugins.Wasm | 插件目录、授予、挂载、内存与栈上限、调用超时 |
各键的类型与缺省值以对应包页为准。
常见问题
改了 appsettings.json 里的 XiHan:* 键,为什么运行中的应用没反应
框架选项在 AddXiHan 装配时读取一次并登记为单例,改 XiHan:* 下的键要重启。只有读 IConfiguration 或 IOptionsMonitor<T> 的应用代码会随文件变化更新。
应用的配置节能放在 XiHan 下吗
不要。XiHan:<领域> 保留给框架与 Provider 包,领域词来自规范术语表;应用放进去会与将来新增的包冲突。用应用自己的名字做顶层节。
配置值写错了会怎样
框架选项的 FromConfiguration 对负数、无法解析的枚举与布尔值抛出 ArgumentException,启动失败并在消息里给出键名。应用选项用 ValidateOnStart() 得到同样的效果。
怎样知道某个键的缺省值
以对应包的参考页为准,各页的「配置」小节列出键、类型与缺省;上面的一览表只列键名与用途。
远程配置里的键能覆盖 appsettings 吗
能,前提是它在 AddXiHan 之前加进 builder.Configuration:后加入的配置源覆盖先加入的。完整顺序见 配置目录与热更新的「配置源的优先级」。
相关包与下一步
- Kernel:
KernelOptions、ResilienceOptions与XiHan:Kernel配置节 - Configuration.Abstractions:远程配置与密钥的契约
- Hosting.AspNetCore:装载配置目录的 Web 宿主
- 下一页:配置目录与热更新
