0Pricing
C# Academy · 课时

使用 IOptions 的强类型选项

使用 IOptions 、IOptionsSnapshot 和 IOptionsMonitor 将配置节绑定到 POCO 类。

使用 IOptions 的强类型选项 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

为什么要使用强类型选项?

使用 IConfiguration["Key"] 读取配置时,得到的是无类型字符串。选项模式会将配置节映射到 C# 类,从而提供编译时安全性、IntelliSense 和验证支持。

定义选项类

创建一个普通的 POCO 类,使其属性名称与 JSON 键匹配。按照惯例,可以添加一个静态的 SectionName 常量来标识配置节。

// Configuration class
public class JwtOptions
{
    public const string SectionName = "Jwt";

    public string SecretKey { get; set; } = string.Empty;
    public string Issuer    { get; set; } = string.Empty;
    public string Audience  { get; set; } = string.Empty;
    public int    ExpiryMinutes { get; set; } = 60;
}

// appsettings.json:
{
  "Jwt": {
    "SecretKey": "my-very-secret-key",
    "Issuer": "https://myapp.com",
    "Audience": "https://myapp.com/api",
    "ExpiryMinutes": 120
  }
}

注册选项

调用 Configure<T>,将配置节绑定到选项类。这会在 DI 中注册 IOptions<T>、IOptionsSnapshot<T> 和 IOptionsMonitor<T>。

// Registration
builder.Services.Configure<JwtOptions>(
    builder.Configuration.GetSection(JwtOptions.SectionName));

// Alternative shorthand:
builder.Services
    .AddOptions<JwtOptions>()
    .BindConfiguration(JwtOptions.SectionName);

// Consuming via IOptions<T>:
public class TokenService
{
    private readonly JwtOptions _opts;

    public TokenService(IOptions<JwtOptions> opts)
        => _opts = opts.Value;

    public string CreateToken()
        => $"Issuer={_opts.Issuer}, Exp={_opts.ExpiryMinutes}m";
}

IOptions 与 IOptionsSnapshot 与 IOptionsMonitor

这三种形式具有不同的生命周期和重新加载行为。请根据使用场景选择合适的形式。

// IOptions<T> — Singleton, reads config ONCE at startup
public class ApiClient(IOptions<ApiOptions> opts)
{
    private readonly ApiOptions _opts = opts.Value; // never changes
}

// IOptionsSnapshot<T> — Scoped, reloads per request
public class ReportService(IOptionsSnapshot<ReportOptions> opts)
{
    private readonly ReportOptions _opts = opts.Value; // fresh per request
}

// IOptionsMonitor<T> — Singleton, live updates + change notifications
public class FeatureService(IOptionsMonitor<FeatureFlags> monitor)
{
    public bool IsEnabled(string feature)
        => monitor.CurrentValue.EnabledFeatures.Contains(feature);
}

使用特性验证选项

为选项属性添加 System.ComponentModel.DataAnnotations 特性,并调用 ValidateDataAnnotations(),即可在启动时快速失败,而不是让无效配置继续运行。

using System.ComponentModel.DataAnnotations;

public class SmtpOptions
{
    [Required]
    public string Host { get; set; } = string.Empty;

    [Range(1, 65535)]
    public int Port { get; set; } = 587;

    [Required, EmailAddress]
    public string FromAddress { get; set; } = string.Empty;
}

// Register with validation:
builder.Services
    .AddOptions<SmtpOptions>()
    .BindConfiguration("Smtp")
    .ValidateDataAnnotations()
    .ValidateOnStart(); // fail at startup, not first use

使用 IValidateOptions 自定义验证

对于复杂的跨属性规则,请实现 IValidateOptions<T>,以完整地编写程序化验证逻辑。

public class JwtOptionsValidator : IValidateOptions<JwtOptions>
{
    public ValidateOptionsResult Validate(string? name, JwtOptions opts)
    {
        var errors = new List<string>();

        if (string.IsNullOrWhiteSpace(opts.SecretKey))
            errors.Add("SecretKey must not be empty");

        if (opts.SecretKey.Length < 32)
            errors.Add("SecretKey must be at least 32 characters");

        if (opts.ExpiryMinutes <= 0)
            errors.Add("ExpiryMinutes must be positive");

        return errors.Any()
            ? ValidateOptionsResult.Fail(errors)
            : ValidateOptionsResult.Success;
    }
}

builder.Services.AddSingleton<IValidateOptions<JwtOptions>, JwtOptionsValidator>();

配置后处理

PostConfigure 会在所有 Configure 调用之后运行,允许您覆盖或派生值,适用于计算属性或特定环境的调整。

builder.Services.Configure<CacheOptions>(
    builder.Configuration.GetSection("Cache"));

// Override in test environment:
builder.Services.PostConfigure<CacheOptions>(opts =>
{
    if (builder.Environment.IsEnvironment("Testing"))
    {
        opts.AbsoluteExpirationMinutes = 1; // very short in tests
        opts.UseDistributedCache = false;   // use in-memory cache
    }
});

// PostConfigure always runs LAST, even after AddOptions validators

命名选项

当您需要同一选项类型的多个实例时(例如两个 SMTP 服务器),可以使用命名选项来区分它们。

// Register named options:
builder.Services.Configure<SmtpOptions>("Primary",
    builder.Configuration.GetSection("Smtp:Primary"));
builder.Services.Configure<SmtpOptions>("Backup",
    builder.Configuration.GetSection("Smtp:Backup"));

// Consume with IOptionsMonitor (supports named options):
public class EmailSender
{
    private readonly SmtpOptions _primary;
    private readonly SmtpOptions _backup;

    public EmailSender(IOptionsMonitor<SmtpOptions> monitor)
    {
        _primary = monitor.Get("Primary");
        _backup  = monitor.Get("Backup");
    }
}

BindConfiguration 简写

AddOptions().BindConfiguration() 链是现代化的流式写法,可在一个表达式中完成注册、绑定、验证和快速失败。

// Full registration chain:
builder.Services
    .AddOptions<DatabaseOptions>()
    .BindConfiguration("Database")          // bind section
    .ValidateDataAnnotations()               // attribute validation
    .Validate(opts =>                        // custom rule
        opts.MaxPoolSize >= opts.MinPoolSize,
        "MaxPoolSize must be >= MinPoolSize")
    .ValidateOnStart();                      // fail at startup

// Shorthand for simple cases:
builder.Services.AddOptions<AppOptions>()
    .BindConfiguration(AppOptions.SectionName)
    .ValidateOnStart();

实战:功能开关选项

一个完整的功能开关选项配置方案,支持重新加载,因此无需重新部署即可在 appsettings 中更改开关。

public class FeatureFlags
{
    public bool EnableNewCheckout  { get; set; }
    public bool EnableAISearch     { get; set; }
    public bool EnableBetaDashboard { get; set; }
}

// appsettings.json:
// { "FeatureFlags": { "EnableNewCheckout": true, ... } }

builder.Services
    .AddOptions<FeatureFlags>()
    .BindConfiguration("FeatureFlags")
    .ValidateOnStart();

// In a controller or service:
public class CheckoutController : ControllerBase
{
    private readonly FeatureFlags _flags;

    public CheckoutController(IOptionsMonitor<FeatureFlags> m)
        => _flags = m.CurrentValue;

    [HttpGet("/checkout")]
    public IActionResult Index() =>
        _flags.EnableNewCheckout
            ? Ok("new checkout")
            : Ok("legacy checkout");
}

快速检查

在需要反映实时配置更改的 Singleton 服务中,应使用哪一种 IOptions 变体?

回顾:使用 IOptions 的强类型选项

要点:

  • 选项模式通过 Configure<T> 或 AddOptions<T>().BindConfiguration() 将配置节绑定到 POCO
  • IOptions<T>:Singleton,在启动时读取一次
  • IOptionsSnapshot<T>:作用域级别,按请求重新加载——不要将其注入 Singleton
  • IOptionsMonitor<T>:可安全用于 Singleton,提供实时的 CurrentValue + OnChange
  • 使用 DataAnnotations + ValidateDataAnnotations() + ValidateOnStart() 进行验证
  • 使用命名选项创建同一类型的多个实例

常见问题解答

「使用 IOptions 的强类型选项」课时是免费的吗?

是的 — 「使用 IOptions 的强类型选项」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「使用 IOptions 的强类型选项」这节课中我会学到什么?

使用 IOptions 、IOptionsSnapshot 和 IOptionsMonitor 将配置节绑定到 POCO 类。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「使用 IOptions 的强类型选项」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 配置源与提供程序
  2. 使用 IOptions 的强类型选项
  3. 选项验证与命名选项
  4. 机密管理
← 返回 C# Academy