什么是选项

选项是 ASP.NET Core 推荐的动态读取配置的方式,这种方式将配置文件数据用一个强类型来托管,能够实现配置验证、默认值配置、实时读取等功能。

与配置的区别

选项实际上也是配置,但在后者的基础上添加了配置验证、默认值/后期配置设定及提供了多种接口读取配置信息,同时还支持供配置更改通知等强大灵活功能。

所以,除了一次性读取使用的配置以外,都应该选用 选项 替换 配置。

选项的使用

假设我们需要在系统运行时获取系统名称、版本号及版权信息,这些信息可能随时变化而且需要在多个地方使用。这时就需要将这些信息配置起来。具体步骤如下:

配置 appsettings.json 信息

{
  "AppInfo": {
    "Name": "Furion",
    "Version": "1.0.0",
    "Company": "Baiqian"
  }
}

创建 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; }
    }
}

注册 AppInfoOptions 服务

选项不同于配置,需在应用启动时注册

Furion.Web.Core\FurWebCoreStartup.cs
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>();
        }
    }
}

读取 AppInfoOptions 信息

在 Furion 框架中,提供了多种读取方式:

  • 通过 App.GetOptions<TOptions>(jsonKey) 读取(不推荐)
  • 通过依赖注入以下实例读取:
    • IOptions<TOptions>
    • IOptionsSnapshot<TOptions>
    • IOptionsMonitor<TOptions>
  • 通过 App 静态类提供的静态方法获取:
    • App.GetOptions<TOptions>()
    • App.GetOptionsMonitor<TOptions>()
    • App.GetOptionsSnapshot<TOptions>()
using Furion.Application;
using Microsoft.AspNetCore.Mvc;

namespace Furion.Web.Entry.Controllers
{
    [Route("api/[controller]")]
    public class DefaultController : ControllerBase
    {
        [HttpGet]
        public string Get()
        {
            // 不推荐采用此方式读取
            var appInfo = App.GetOptions<AppInfoOptions>("AppInfo");
            return $@"名称:{appInfo.Name},
                      版本:{appInfo.Version},
                      公司:{appInfo.Company}";
        }
    }
}


Furion/App/App.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Furion
{
    /// <summary>
    /// 全局应用类
    /// </summary>
    public static class App
    {
        /// <summary>
        /// 获取选项
        /// </summary>
        /// <typeparam name="TOptions">强类型选项类</typeparam>
        /// <param name="jsonKey">配置中对应的Key</param>
        /// <returns></returns>
        public static TOptions GetOptions<TOptions>(string jsonKey)
            where TOptions : class
            => Configuration.GetSection(jsonKey).Get<TOptions>();

        /// <summary>
        /// 获取选项
        /// </summary>
        /// <typeparam name="TOptions">强类型选项类</typeparam>
        /// <returns></returns>
        public static TOptions GetOptions<TOptions>()
            where TOptions : class
            => ServiceProvider.GetService<IOptions<TOptions>>().Value;

        // Other Codes...
    }
}

如何选择读取方式

  • 如果选项需要在多个地方使用,则无论任何时候都不推荐使用 App.GetOptions<TOptions>(jsonKey)
  • 在可依赖注入类中,依赖注入 IOptions[Snapshot|Monitor]<TOptions> 读取
  • 在静态类/非依赖注入类中,选择 App.GetOptions[Snapshot|Monitor]<TOptions>() 读取

选项接口说明

ASP.NET Core 应用提供了多种读取选项的接口:

  • IOptions<TOptions>
    • 不支持:
      • 在应用启动后读取配置数据
      • 命名选项
    • 注册为单一实例且可以注入到任何服务生存期
  • IOptionsSnapshot<TOptions>
    • 在每次请求时应重新计算选项的方案中有用
    • 注册为范围内,因此无法注入到单一实例服务
    • 支持命名选项
  • IOptionsMonitor<TOptions>
    • 用于检索选项并管理 TOptions 实例的选项通知。
    • 注册为单一实例且可以注入到任何服务生存期。
    • 支持:
      • 更改通知
      • 命名选项
      • 可重载配置
      • 选择性选项失效 (IOptionsMonitorCache<TOptions>)

 选项自定义配置

我们知道,选项实际上需要和配置文件特定键值挂钩,那 Furion 是如何准确的找到配置文件中的键值的呢?

选项查找键流程

  • 没有贴 [OptionsSettings] 特性
    • 以 Options 结尾,则去除 Options 字符串
    • 否则返回 类名称
  • 贴了 [OptionsSettings] 特性
    • 如果配置了 JsonKey 属性,则返回 JsonKey 的值
    • 否则返回 类名称
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; }
}

[OptionsSettings] 说明

选项类可以通过 [OptionsSettings] 来配置查找路径值。

  • JsonKey:对应配置文件中的键,支持 分层键 字符串
  • PostConfigureAll:选项后期配置,默认 false。

选项验证

选项支持验证配置有效性,在 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; }
    }
}

选项后期配置

选项后期配置通俗一点来说,可以在运行时解析值或设定默认值/后期配置等。

在 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";
        }
    }
}

选项更改通知(热更新

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)
    {
    }
}

选项的优缺点

  • 优点

    • 强类型配置
    • 提供多种读取方式
    • 支持热加载
    • 支持设置默认值/后期配置
    • 支持在运行环境中动态配置
    • 支持验证配置有效性
    • 支持更改通知
    • 支持命名选项
  • 缺点

    • 需要定义对应类型
    • 需要在启动时注册