选项模式使用的 Microsoft.Extensions.Options.ConfigurationExtensions 包已在 ASP.NET Core 应用中隐式引用。
全文中我们全部使用一个名称为 MyOptions 的类:
public class MyOptions : IDisposable
{
public MyOptions()
{
Option1 = "value_ctor";
}
public string Option1 { get; set; }
[Range(0, int.MaxValue)]
public int Option2 { get; set; } = 5;
public void Dispose()
{
Console.WriteLine($"MyOptions Disposed:{GetHashCode()}");
}
}
配置文件 appsettings.json 的内容:
{
"option1": "value_json",
"option2": -1,
"subsection": {
"option1": "subvalue_json",
"option2": 55555
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
本人在进行测试时,发现 选项模式的实例并不会走 Dispose() 方法 。
在选项模式中实例的生命周期由注入时使用的接口决定,
有IOptions
、 IOptionsMonitor
和 IOptionsSnapshot
共三个,
其中 IOptions
、 IOptionsMonitor
的生命周期是单例模式,生命周期和 AddSingleton
类似,
IOptions
没有数据热更新,读取的值永远不会变。
IOptionsMonitor
和 IOptionsSnapshot
有数据热更新,会保持最新值。
剩下的 IOptionsSnapshot
是作用域服务(快照),生命周期和 AddScoped
类似。
MyOptions 类已通过 Configure 添加到服务容器并绑定到配置:
#region ConfigureServices
services.Configure<MyOptions>(Configuration);
#endregion
#region [ApiController]
private readonly MyOptions _options;
public HomeController(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
}
[HttpGet]
public MyOptions Index() { return _options; }
#endregion
访问 /Home/Index 得到的值为:
{"option1":"value_json","option2":-1}
可以看到,配置文件中的值已将实例的默认值覆盖了。
备注
使用自定义 ConfigurationBuilder 从设置文件加载选项配置:
#region ConfigureServices var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true); var config = configBuilder.Build(); services.Configure<MyOptions>(config); #endregion
使用委托设置选项值:
#region ConfigureServices
services.Configure<MyOptions>(myOptions =>
{
myOptions.Option1 = "value_delegate";
myOptions.Option2 = 2333;
});
#endregion
访问 /Home/Index 得到的值为:
{"option1":"value_delegate","option2":2333}
可以看到,MyOptions 使用委托配置绑定覆盖了默认值,
当启用多个配置服务时,指定的最后一个配置源优于其他源 。
需要配置值的部分应用时可以使用子选项配置
#region ConfigureServices
services.Configure<MyOptions>(Configuration.GetSection("subsection"));
#endregion
访问 /Home/Index 得到的值为:
{"option1":"subvalue_json","option2":55555}
使用选项框架时,配置文件默认是热更新的。
我们运行 子选项配置 中的代码
访问 /Home/Index 得到的值为:
{"option1":"subvalue_json","option2":55555}
程序运行时,我们将 appsettings.json
文件中的 subsection
节点改为如下:
"subsection": {
"option1": "subvalue_json new",
"option2": 12345
},
再次访问 /Home/Index 得到的值为:
{"option1":"subvalue_json new","option2":12345}
命名选项支持允许应用在命名选项配置之间进行区分
#region ConfigureServices
services.Configure<MyOptions>("options_1", Configuration);
services.Configure<MyOptions>("options_2", myOptions =>
{
myOptions.Option1 = "options_2_value1_action";
});
#endregion
#region [ApiController]
private readonly MyOptions _options;
private readonly MyOptions _options_1;
private readonly MyOptions _options_2;
public HomeController(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
_options_1 = options.Get("options_1");
_options_2 = options.Get("options_2");
}
[HttpGet]
public List<MyOptions> Index()
{
var result = new List<MyOptions>() {
_options,_options_1,_options_2
};
return result;
}
#endregion
访问 /Home/Index 得到的值为:
[
{"option1":"value_ctor","option2":5},
{"option1":"value_json","option2":-1},
{"option1":"options_2_value1_action","option2":5}
]
可以看到,各个MyOptions 实例的不同,
无命名的取的是默认值,options_1 取的是配置文件的值,options_2 取的是委托配置。
可以使用 ConfigureAll 方法配置所有选项实例。
#region ConfigureServices
services.Configure<MyOptions>("options_1", Configuration);
services.Configure<MyOptions>("options_2", myOptions =>
{
myOptions.Option1 = "options_2_value1_action";
});
services.ConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "ConfigureAll value";
});
#endregion
访问 /Home/Index 得到的值为:
[
{"option1":"ConfigureAll value","option2":5},
{"option1":"ConfigureAll value","option2":-1},
{"option1":"ConfigureAll value","option2":5}
]
可以看到每个MyOptions实例的 option1 都被覆盖了
OptionsBuilder 简化了创建命名选项的过程,
#region ConfigureServices
services.AddOptions<MyOptions>().Configure(o => o.Option1 = "options_builder");
services.AddOptions<MyOptions>("options_1").Configure(o =>
{
Configuration.Bind(o);
// o.Option1 = "default value";
// o.Option2 = 2000;
});
#endregion
访问 /Home/Index 得到的值为:
[
{"option1":"options_builder","option2":5},
{"option1":"default value","option2":2000},
{"option1":"value_ctor","option2":5}
]
可以使用DI服务进行配置
#region ConfigEntity
public class AppConfig { public string Option1 { get; set; } = "SAP"; }
public class PrjConfig { public int Option2 { get; set; } = 69; }
#endregion
#region ConfigureServices
services.AddScoped<AppConfig>();
services.AddScoped<PrjConfig>();
services.AddOptions<MyOptions>("optionalName")
.Configure<AppConfig, PrjConfig>((o, s, b) =>
{
o.Option1 = s.Option1;
o.Option2 = b.Option2;
});
#endregion
#region [ApiController]
private readonly MyOptions _options;
public HomeController(IOptionsSnapshot<MyOptions> options)
{
_options = options.Get("optionalName");
}
[HttpGet]
public MyOptions Index() { return _options; }
#endregion
访问 /Home/Index 得到的值为:
{"option1":"SAP","option2":69}
使用 IPostConfigureOptions
设置后期配置
#region ConfigureServices
// PostConfigure 可用于后期配置
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
// PostConfigure 可用于对命名选项进行后期配置
services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
// PostConfigureAll 对所有配置实例进行后期配置
services.PostConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
#endregion
添加验证的时候只能用 OptionsBuilder API。
现在将 HomeController 的 ctor 修改为如下:
#region [ApiController]
private readonly MyOptions _options;
public HomeController(IOptionsSnapshot<MyOptions> options)
{
_options = options.Value;
}
[HttpGet]
public MyOptions Index() { return _options; }
#endregion
添加选项验证有三种方法:
IValidateOptions
函数#region ConfigureServices
services.AddOptions<MyOptions>().Configure(options =>
{
Configuration.Bind(options);
}).Validate(options =>
{
return options.Option2 > 0;
}, "Option2 必须大于0");
#endregion
因为配置文件中 Option2 等于 -1 所以当我们访问 /Home/Index 时:
可以看到报错的内容为我们填写的 Validate 的第二个参数。
#region ConfigureServices
services.AddOptions<MyOptions>().Configure(options =>
{
Configuration.Bind(options);
}).ValidateDataAnnotations();
#endregion
#region MyOptions Class
[Range(0, int.MaxValue)]
public int Option2 { get; set; } = 5;
#endregion
因为配置文件中 Option2 等于 -1 所以当我们访问 /Home/Index 时:
首先我们需要实现 IValidateOptions :
#region IValidateOptions
public class MyValidateOptions : IValidateOptions<MyOptions>
{
public ValidateOptionsResult Validate(string name, MyOptions options)
{
if (options.Option2 <= 0)
{
return ValidateOptionsResult.Fail("Option2 必须大于0");
}
else
{
return ValidateOptionsResult.Success;
}
}
}
#endregion
#region ConfigureServices
services.AddOptions<MyOptions>().Configure(options =>
{
Configuration.Bind(options);
}).Services.AddSingleton<IValidateOptions<MyOptions>, MyValidateOptions>();
#endregion
因为配置文件中 Option2 等于 -1 所以当我们访问 /Home/Index 时:
可用于 Startup.Configure 中,因为在 Configure 方法执行之前已生成服务
public void Configure(IApplicationBuilder app,
IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
ASP.NET Core 中的选项模式