C#中的选项模式
[[academy-video-youtube({"vid": "ko1Ie9gDydY", "start_time": "0", "title": "The Options Pattern in C#", "creator": "Tim Corey", "length": "10m 15s"})]]
.NET 应用程序中的配置通常位于appsettings.json、环境变量或用户秘密中。 将配置数据从文件中提取到您的类中以干净、可测试的方式进行的工作是Options模式解决的。 而不是手动读取JSON键或传递原始字符串,您将配置部分绑定到强类型的C#类,并让依赖注入在需要的地方传递它。
在他的视频"The Options Pattern in C#"中,Tim Corey 介绍了选项模式的三种变体(IOptionsSnapshot 和 IOptionsMonitor),并在 Blazor 服务器应用中演示了每一个,同时展示了如何添加验证,以便当配置丢失或格式错误时您的应用程序能够快速失败。 如果您正在构建任何从配置文件中读取并使用依赖注入的.NET项目,该模式是基础。
设置:一个POCO模型和appsettings.json
[0:34 - 1:46] Tim从一个Blazor web应用程序开始(服务器渲染,没有客户端交互),已经有两个组件到位。 第一个是在 appsettings.json 中的一个部分,其中包含在CloudInfo部分下的三个键值对:
// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}第二个组件是一个普通的C#类(POCO),其属性映射JSON键:
public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}Tim 指出,配置源不一定要具体是appsettings.json。 它可以是secrets.json或任何组合。 Options模式从应用程序中注册的任何配置提供程序中读取,POCO上的属性名称按约定匹配JSON键。
在Program.cs中注册选项
[2:25 - 3:15] 将配置连接到依赖注入需要两个方法调用在Program.cs中:
// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");AddOptions<t>() 注册类型到 DI。 BindConfiguration("CloudInfo") 告诉框架哪个配置段要映射。字符串"CloudInfo" 对应于appsettings.json中的 JSON 键。 如果部分名称和类名不匹配,字符串参数就是框架用于查找正确数据的依据。
IOptions:单一实例方法
[3:15 - 5:18] 注册完成后,Tim 使用IOptions<CloudInfoOptions>将配置注入到Blazor页面中:
@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}关键细节是IOptions<t>被注册为单例。 配置值在应用程序启动时读取一次,并在进程的生存期内缓存。 如果您在应用程序运行时更改IOptions将不会反映这些变化。
对于大多数应用程序,这是正确的选择。配置在运行时很少更改,单一实例寿命意味着每个请求零开销。
IOptionsSnapshot:作用域重新加载
[5:18 - 6:55] Tim 将IOptionsSnapshot以演示 scoped 变体:
@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig@inject IOptionsSnapshot<CloudInfoOptions> CloudConfigIOptionsSnapshot<t> 在每次请求中重新读取配置(scoped 生命周期)。 如果您在应用程序运行时修改appsettings.json,下一个 web 请求将会获取到新值。 以前的请求保持自己的快照,因此没有中间请求的不一致性。
这对于需要在不重新启动的情况下生效的配置更改的应用程序中很有用,例如切换功能标志、更新API端点或旋转存储连接字符串。 权衡是每个请求的小成本来重新读取配置,这对于大多数工作负载来说微不足道。
IOptionsMonitor:实时更改通知
[6:55 - 8:34] 第三个变体,IOptionsMonitor<t>,比快照重新加载更进一步。 它主动监视配置更改,并可以在值更新时触发回调:
@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}与IOptionsMonitor 可以在长时间运行的操作中检测到变化。 Tim指出,这对于后台服务、SignalR集线器或无HTTP请求的单一生存期组件最为相关。
添加验证
[8:34 - 10:05] Tim覆盖的最后一个部分是验证。 Options模式支持在首次访问配置时运行的内联验证规则:
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));Tim 通过移除appsettings.json并启动应用程序,演示了失败情况。 结果是立即未处理的异常:"CloudInfoOptions验证失败"。应用程序拒绝启动不完整的配置,这是您想要的行为。 在启动时发现缺失值比在凌晨2点生产时遇到空引用要好得多。
对于更复杂的验证场景(交叉属性检查、条件规则),您可以连接多个IValidateOptions<t>作为专用验证类。
总结:三个接口,一个模式
[10:05 - 10:10] Options模式提供了配置存储与消费之间的清晰分离。 IOptions 覆盖了大多数配置是静态的用例。 IOptionsSnapshot 处理需要在请求之间拾取变化的应用程序。 IOptionsMonitor 服务于需要实时感知配置更新的长时间运行组件。 验证确保在缺少所需值时您的应用程序发出响亮的失败信号。
结论
[10:10 - 10:15] 总结一下:在.Validate()以在启动时捕获丢失的值而不是在运行时。
该模式适用于任何.NET项目类型:Blazor、ASP.NET Core、控制台应用程序、工作服务。 注册在任何地方都是相同的。
示例提示:如果您不确定要使用哪个接口,请从IOptions<t>开始。 它是最简单的,没有每个请求的开销,并且涵盖了大多数配置在启动时设置且不更改的情形。 只有在您有具体的运行时重新加载需要时才移动到IOptionsMonitor。
在他的YouTube频道上观看完整视频,获得更多关于C#配置模式的见解。

