4.2 选项
4.2.1 什么是选项
选项是 ASP.NET Core
推荐的动态读取配置的方式,这种方式将配置文件数据用一个强类型来托管,能够实现配置验证、默认值配置、实时读取等功能。
4.2.2 与配置的区别
选项实际上也是配置,但在后者的基础上添加了配置验证、默认值/后期配置设定及提供了多种接口读取配置信息,同时还支持供配置更改通知等强大灵活功能。
所以,除了一次性读取使用的配置以外,都应该选用 选项 替换 配置。
有关配置说明可查看《4.1 配置》 章节。
4.2.3 选项的使用
假设我们需要在系统运行时获取系统名称、版本号及版权信息,这些信息可能随时变化而且需要在多个地方使用。这时就需要将这些信息配置起来。具体步骤如下:
4.2.3.1 配置 appsettings.json
信息
{
"AppInfo": {
"Name": "Furion",
"Version": "1.0.0",
"Company": "Baiqian"
}
}
4.2.3.2 创建 AppInfoOptions
强类型类
using Furion.ConfigurableOptions;
namespace Furion.Application
{
public class AppInfoOptions : IConfigurableOptions
{
public string Name { get; set; }
public string Version { get; set; }
public string Company { get; set; }
}
}
建议所有选项类都应该以 Options
命名结尾。
另外,Furion
框架提供了非常灵活的注册选项服务的方法,只需要继承 IConfigurableOptions
接口即可,该接口位于 Furion.ConfigurableOptions
命名空间下。
4.2.3.3 注册 AppInfoOptions
服务
选项不同于配置,需在应用启动时注册
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Furion.Web.Core
{
[AppStartup(800)]
public sealed class FurWebCoreStartup : AppStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddConfigurableOptions<AppInfoOptions>();
}
}
}
4.2.3.4 读取 AppInfoOptions
信息
在 Furion
框架中,提供了多种读取方式:
- 通过
App.GetConfig<TOptions>(path)
读取(不推荐) - 通过依赖注入以下实例读取:
IOptions<TOptions>
IOptionsSnapshot<TOptions>
IOptionsMonitor<TOptions>
- 通过
App
静态类提供的静态方法获取:App.GetOptions<TOptions>()
App.GetOptionsMonitor<TOptions>()
App.GetOptionsSnapshot<TOptions>()
禁止在主机启动时通过 App.GetOptions<TOptions>
获取选项,如需获取配置选项理应通过 App.GetConfig<TOptions>("配置节点", true)
。
- App.GetConfig<TOptions>(path)
- 依赖注入方式
- App.GetOptions<TOptions>()
using Furion.Application;
using Microsoft.AspNetCore.Mvc;
namespace Furion.Web.Entry.Controllers
{
[Route("api/[controller]")]
public class DefaultController : ControllerBase
{
[HttpGet]
public string Get()
{
// 不推荐采用此方式读取,该方式仅在 ConfigureServices 启动时使用
var appInfo = App.GetConfig<AppInfoOptions>("AppInfo", true);
return $@"名称:{appInfo.Name},
版本:{appInfo.Version},
公司:{appInfo.Company}";
}
}
}
using Furion.Application;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace Furion.Web.Entry.Controllers
{
[Route("api/[controller]")]
public class DefaultController : ControllerBase
{
private readonly AppInfoOptions options1;
private readonly AppInfoOptions options2;
private readonly AppInfoOptions options3;
public DefaultController(
IOptions<AppInfoOptions> options
, IOptionsSnapshot<AppInfoOptions> optionsSnapshot
, IOptionsMonitor<AppInfoOptions> optionsMonitor)
{
options1 = options.Value;
options2 = optionsSnapshot.Value;
options3 = optionsMonitor.CurrentValue;
}
[HttpGet]
public string Get()
{
var info1 = $@"名称:{options1.Name},
版本:{options1.Version},
公司:{options1.Company}";
var info2 = $@"名称:{options2.Name},
版本:{options2.Version},
公司:{options2.Company}";
var info3 = $@"名称:{options3.Name},
版本:{options3.Version},
公司:{options3.Company}";
return $"{info1}-{info2}-{info3}";
}
}
}
using Furion.Application;
using Microsoft.AspNetCore.Mvc;
namespace Furion.Web.Entry.Controllers
{
[Route("api/[controller]")]
public class DefaultController : ControllerBase
{
[HttpGet]
public string Get()
{
var options1 = App.GetOptions<AppInfoOptions>();
var info1 = $@"名称:{options1.Name},
版本:{options1.Version},
公司:{options1.Company}";
var options2 = App.GetOptionsSnapshot<AppInfoOptions>();
var info2 = $@"名称:{options2.Name},
版本:{options2.Version},
公司:{options2.Company}";
var options3 = App.GetOptionsMonitor<AppInfoOptions>();
var info3 = $@"名称:{options3.Name},
版本:{options3.Version},
公司:{options3.Company}";
return $"{info1}-{info2}-{info3}";
}
}
}
4.2.3.5 如何选择读取方式
- 如果选项需要在多个地方使用,则无论任何时候都不推荐使用
App.GetOptions<TOptions>()
- 在可依赖注入类中,依赖注入
IOptions[Snapshot|Monitor]<TOptions>
读取 - 在静态类/非依赖注入类中,选择
App.GetOptions[Snapshot|Monitor]<TOptions>()
读取
4.2.4 选项接口说明
ASP.NET Core
应用提供了多种读取选项的接口:
IOptions<TOptions>
:- 不支持:
- 在应用启动后读取配置数据
- 命名选项
- 注册为单一实例且可以注入到任何服务生存期
- 不支持:
IOptionsSnapshot<TOptions>
:- 在每次请求时应重新计算选项的方案中有用
- 注册为范围内,因此无法注入到单一实例服务
- 支持命名选项
IOptionsMonitor<TOptions>
:- 用于检索选项并管理 TOptions 实例的选项通知。
- 注册为单一实例且可以注入到任何服务生存期。
- 支持:
- 更改通知
- 命名选项
- 可重载配置
- 选择性选项失效
(IOptionsMonitorCache<TOptions>)
在使用 IConfigurableOptionsListener
监听选项后,如要获取最新的配置信息,请使用 App.GetOptionsMonitor<TOptions>()
而不是 App.GetOptions<TOptions>()
。
想了解更多 选项接口
知识可查阅 ASP.NET Core - 选项 - 选项接口 小节。
4.2.5 选项自定义配置
我们知道,选项实际上需要和配置文件特定键值挂钩,那 Furion
是如何准确的找到配置文件中的键值的呢?
4.2.5.1 选项查找键流程
- 没有贴
[OptionsSettings]
特性- 以
Options
结尾, 则去除Options
字符串 - 否则返回
类名称
- 以
- 贴了
[OptionsSettings]
特性- 如果配置了
Path
属性,则返回Path
的值 - 否则返回
类名称
- 如果配置了
- 无[OptionsSettings]
- 有[OptionsSettings]
- 以
Options
结尾,则键名为:AppInfo
public class AppInfoOptions : IConfigurableOptions
{
public string Name { get; set; }
public string Version { get; set; }
public string Company { get; set; }
}
- 不以
Options
结 尾,则键名为:AppInfoSettings
public class AppInfoSettings : IConfigurableOptions
{
public string Name { get; set; }
public string Version { get; set; }
public string Company { get; set; }
}
- 配置了
Path
属性,则键名为:AppSettings:AppInfo
[OptionsSettings("AppSettings:AppInfo")]
public class AppInfoOptions : IConfigurableOptions
{
public string Name { get; set; }
public string Version { get; set; }
public string Company { get; set; }
}
- 没有配置
Path
属性,,则键名为:AppInfoSettings
[OptionsSettings]
public class AppInfoSettings : IConfigurableOptions
{
public string Name { get; set; }
public string Version { get; set; }
public string Company { get; set; }
}
4.2.6 [OptionsSettings]
说明
选项类可以通过 [OptionsSettings]
来配置查找路径值。
Path
:对应配置文件中的键,支持 分层键 字符串,参见:《4.1 配置 - 4.1.3 路径符 查找节点》PostConfigureAll
:选项后期配置,默认false
。ASP.NET Core - 选项 - 选项后期配置
4.2.7 选项验证
选项支持验证配置有效性,在 Furion
框架中,通过 services.AddConfigurableOptions<TOptions>()
注册选项默认启用了验证支持。
包括:
- 特性方式
DataAnnotations
- 自定义复杂验证
IValidateOptions<TOptions>
- 特性方式
- 复杂验证
using Furion.ConfigurableOptions;
using System.ComponentModel.DataAnnotations;
namespace Furion.Application
{
public class AppInfoOptions : IConfigurableOptions
{
[Required(ErrorMessage = "名称不能为空")]
public string Name { get; set; }
[Required, RegularExpression(@"^[0-9][0-9\.]+[0-9]$", ErrorMessage = "不是有效的版本号")]
public string Version { get; set; }
[Required, MaxLength(100)]
public string Company { get; set; }
}
}
- 自定义验证类
AppInfoValidation
并继承IValidateOptions<TOptions>
接口,同时实现Validate
方法。
using Microsoft.Extensions.Options;
using System.Text.RegularExpressions;
namespace Furion.Application
{
public class AppInfoValidation : IValidateOptions<AppInfoOptions>
{
public ValidateOptionsResult Validate(string name, AppInfoOptions options)
{
if (!Regex.IsMatch(options.Version, @"^[0-9][0-9\.]+[0-9]$"))
{
return ValidateOptionsResult.Fail("不是有效的版本号");
}
return ValidateOptionsResult.Success;
}
}
}
- 选项类继承
IConfigurableOptions<TOptions, TOptionsValidation>
接口,并实现该接口。
using Furion.ConfigurableOptions;
using System.ComponentModel.DataAnnotations;
namespace Furion.Application
{
public class AppInfoOptions : IConfigurableOptions<AppInfoOptions, AppInfoValidation>
{
[Required(ErrorMessage = "名称不能为空")]
public string Name { get; set; }
[Required]
public string Version { get; set; }
[Required, MaxLength(100)]
public string Company { get; set; }
// 选项后期配置
public void PostConfigure(AppInfoOptions options, IConfiguration configuration)
{
}
}
}
- 完整代码如下:
using Furion.ConfigurableOptions;
using Microsoft.Extensions.Options;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
namespace Furion.Application
{
// 继承 IConfigurableOptions<TOptions, TOptionsValidation> 接口
public class AppInfoOptions : IConfigurableOptions<AppInfoOptions, AppInfoValidation>
{
[Required(ErrorMessage = "名称不能为空")]
public string Name { get; set; }
[Required]
public string Version { get; set; }
[Required, MaxLength(100)]
public string Company { get; set; }
// 选项后期配置
public void PostConfigure(AppInfoOptions options)
{
}
}
// 创建自定义验证类
public class AppInfoValidation : IValidateOptions<AppInfoOptions>
{
public ValidateOptionsResult Validate(string name, AppInfoOptions options)
{
if (!Regex.IsMatch(options.Version, @"^[0-9][0-9\.]+[0-9]$"))
{
return ValidateOptionsResult.Fail("不是有效的版本号");
}
return ValidateOptionsResult.Success;
}
}
}
IConfigurableOptions<TOptions, TOptionsValidation>
继承自 IConfigurableOptions<TOptions>
,也就是自定义复杂验证默认具有 PostConfigure(TOptions options)
选项后期配置方法。关于《4.2.8 选项后期配置》将在下一小节说明。
4.2.8 选项后期配置
选项后 期配置通俗一点来说,可以在运行时解析值或设定默认值/后期配置等。
在 Furion
框架中,配置选项后期配置很简单,只需要继承 IConfigurableOptions<TOptions>
接口并实现 PostConfigure(TOptions options)
方法。
using Furion.ConfigurableOptions;
using Microsoft.Extensions.Configuration;
using System.ComponentModel.DataAnnotations;
namespace Furion.Application
{
public class AppInfoOptions : IConfigurableOptions<AppInfoOptions>
{
[Required(ErrorMessage = "名称不能为空")]
public string Name { get; set; }
[Required]
public string Version { get; set; }
[Required, MaxLength(100)]
public string Company { get; set; }
public void PostConfigure(AppInfoOptions options, IConfiguration configuration)
{
options.Name ??= "Furion";
options.Version ??= "1.0.0";
options.Company ??= "Baiqian";
}
}
}
IConfigurableOptions<TOptions, TOptionsValidation>
继承自 IConfigurableOptions<TOptions>
,也就是自定义复杂验证默认具有 PostConfigure(TOptions options, IConfiguration configuration)
选项后期配置方法。
4.2.9 选项更改通知(热更新
)
Furion
框架提供了非常简单且灵活的方式监听选项更改,也就是 appsettings.json
或 自定义配置文件发生任何更改都会触发处理方法。
使用非常简单,只需要继承 IConfigurableOptionsListener<TOptions>
接口并实现 void OnListener(TOptions options, IConfiguration configuration)
方法即可。
using Furion.ConfigurableOptions;
namespace Furion.Application
{
public class AppInfoOptions : IConfigurableOptionsListener<AppInfoOptions>
{
public string Name { get; set; }
public string Version { get; set; }
public string Company { get; set; }
public void OnListener(AppInfoOptions options, IConfiguration configuration)
{
var name = options.Name; // 实时的最新值
var version = options.Version; // 实时的最新值
}
public void PostConfigure(AppInfoOptions options, IConfiguration configuration)
{
}
}
}
IConfigurableOptionsListener<TOptions>
继承自 IConfigurableOptions<TOptions>
。
4.2.9.1 关于多次触发问题
在 Furion
底层使用的是 ChangeToken.OnChange
监听文件更改,但是此方式会导致 OnListener
触发两次,这并非是框架的 bug
,而是 .NET Core
本身存在的问题,详见:https://github.com/dotnet/aspnetcore/issues/2542
所以,Furion
框架也给出另一种解决方案可替代 IConfigurableOptionsListener
的方式,也就是通过局部注入 IOptionsMonitor
的方式,如:
public class YourService : IYourService, IDisposable
{
private readonly IDisposable _optionsReloadToken;
private YourOptions _options;
public YourService(IOptionsMonitor<YourOptions> options)
{
(_optionsReloadToken, _options) = (options.OnChange(ReloadOptions), options.CurrentValue);
}
private void ReloadOptions(YourOptions options)
{
_options = options;
}
public void Dispose()
{
_optionsReloadToken?.Dispose();
}
}
这种方式虽然啰嗦,但是可以很好和业务代码契合。
4.2.10 选项的优缺点
-
优点
- 强类型配置
- 提供多种读取方式
- 支持热加载
- 支持设置默认值/后期配置
- 支持在运行环境中动态配置
- 支持验证配置有 效性
- 支持更改通知
- 支持命名选项
-
缺点
- 需要定义对应类型
- 需要在启动时注册
4.2.11 自定义属性 Key
映射
以下内容仅限 Furion v3.4.3+
版本使用。
有时候我们在 appsettings.json
中配置的 Key
和选项定义的属性名不一样,这时候就需要用到 [MapSettings]
特性即可,如:
"AppInfo": {
"Name": "Furion",
"Version": "1.0.0",
"Company_Name": "Baiqian"
}
public class AppInfoOptions : IConfigurableOptions
{
public string Name { get; set; }
public string Version { get; set; }
[MapSettings("Company_Name")]
public string Company { get; set; }
}
[MapSettings]
配置的 Key
会自定应用选项的 Key
作为起始点,如实际上 Company
属性对应的 Key
为:AppInfo:Company_Name
。
4.2.12 反馈与建议
给 Furion 提 Issue。
想了解更多 选项
知识可查阅 ASP.NET Core - 选项 章节。