亲宝软件园·资讯

展开

.NET Core 选项模式【Options】 怎样使用.NET Core 选项模式【Options】

詹伟 人气:0

ASP.NET Core引入了Options模式,使用类来表示相关的设置组。简单的来说,就是用强类型的类来表达配置项,这带来了很多好处。利用了系统的依赖注入,并且还可以利用配置系统。它使我们可以采用依赖注入的方法直接使用绑定的一个POCO对象,这个POCO对象就叫做Options对象。也可以叫做配置对象。

以下大多内容来自官方文档,我只是个翻译官或者叫搬运工吧!

引入Options扩展包

PM>Package-install Microsoft.Extensions.Options

绑定分层配置

在appsetting.json文件增加如下配置

"Position": {
  "Title": "Editor",
  "Name": "Joe Smith"
 }

创建以下 PositionOptions 类:

public class PositionOptions
{
  public const string Position = "Position";

  public string Title { get; set; }
  public string Name { get; set; }
}

选项类:

类绑定

调用 ConfigurationBinder.Bind 将 PositionOptions 类绑定到 Position 部分。然后就可以用了,当然这种方式在开发.NET Core种并不常用,一般采用依赖注入的方式注入。

var positionOptions = new PositionOptions();
Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

使用 ConfigurationBinder.Get 可能比使用 ConfigurationBinder.Bind 更方便。

positionOptions = Configuration.GetSection(PositionOptions.Position).Get<PositionOptions>();

依赖项注入服务容器

修改ConfigureServices方法

public void ConfigureServices(IServiceCollection services)
{
  services.Configure<PositionOptions>(Configuration.GetSection(
                    PositionOptions.Position));
  services.AddRazorPages();
}

通过使用前面的代码,以下代码将读取位置选项:

public class Test2Model : PageModel
{
  private readonly PositionOptions _options;

  public Test2Model(IOptions<PositionOptions> options)
  {
    _options = options.Value;
  }

  public ContentResult OnGet()
  {
    return Content($"Title: {_options.Title} \n" +
            $"Name: {_options.Name}");
  }
}

选项接口
初学者会发现这个框架有3个主要的面向消费者的接口:IOptions、IOptionsMonitor以及IOptionsSnapshot。

这三个接口初看起来很类似,所以很容易引起困惑,什么场景下该用哪个接口呢?

1.IOptions

(1)在应用启动后读取配置数据。

(2)命名选项

2.IOptionsSnapshot

3.IOptionsMonitor

(1)更改通知
(2)命名选项
(3)可重载配置
(4)选择性选项失效

使用 IOptionsSnapshot 读取已更新的数据

IOptionsMonitor 和 IOptionsSnapshot 之间的区别在于:

public class TestSnapModel : PageModel
{
  private readonly MyOptions _snapshotOptions;

  public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
  {
    _snapshotOptions = snapshotOptionsAccessor.Value;
  }

  public ContentResult OnGet()
  {
    return Content($"Option1: {_snapshotOptions.Option1} \n" +
            $"Option2: {_snapshotOptions.Option2}");
  }
}

IOptionsMonitor

public class TestMonitorModel : PageModel
{
  private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

  public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
  {
    _optionsDelegate = optionsDelegate;
  }

  public ContentResult OnGet()
  {
    return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
            $"Option2: {_optionsDelegate.CurrentValue.Option2}");
  }
}

命名选项支持使用 IConfigureNamedOptions

命名选项:

appsettings.json文件

{
 "TopItem": {
  "Month": {
   "Name": "Green Widget",
   "Model": "GW46"
  },
  "Year": {
   "Name": "Orange Gadget",
   "Model": "OG35"
  }
 }
}

下面的类用于每个节,而不是创建两个类来绑定 TopItem:Month 和 TopItem:Year

public class TopItemSettings
{
  public const string Month = "Month";
  public const string Year = "Year";

  public string Name { get; set; }
  public string Model { get; set; }
}

依赖注入容器

public void ConfigureServices(IServiceCollection services)
{
  services.Configure<TopItemSettings>(TopItemSettings.Month,
                    Configuration.GetSection("TopItem:Month"));
  services.Configure<TopItemSettings>(TopItemSettings.Year,
                    Configuration.GetSection("TopItem:Year"));

  services.AddRazorPages();
}

服务应用

public class TestNOModel : PageModel
{
  private readonly TopItemSettings _monthTopItem;
  private readonly TopItemSettings _yearTopItem;

  public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
  {
    _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
    _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
  }
}

使用 DI 服务配置选项

在配置选项时,可以通过以下两种方式通过依赖关系注入访问服务:

建议将配置委托传递给 Configure,因为创建服务较复杂。 在调用 Configure 时,创建类型等效于框架执行的操作。 调用 Configure 会注册临时泛型 IConfigureNamedOptions,它具有接受指定的泛型服务类型的构造函数。

选项验证

appsettings.json 文件

{
 "MyConfig": {
  "Key1": "My Key One",
  "Key2": 10,
  "Key3": 32
 }
}

下面的类绑定到 "MyConfig" 配置节,并应用若干 DataAnnotations 规则:

public class MyConfigOptions
{
  public const string MyConfig = "MyConfig";

  [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
  public string Key1 { get; set; }
  [Range(0, 1000,
    ErrorMessage = "Value for {0} must be between {1} and {2}.")]
  public int Key2 { get; set; }
  public int Key3 { get; set; }
}

使用IValidateOptions更复杂的配置

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
  public MyConfigOptions _config { get; private set; }

  public MyConfigValidation(IConfiguration config)
  {
    _config = config.GetSection(MyConfigOptions.MyConfig)
      .Get<MyConfigOptions>();
  }

  public ValidateOptionsResult Validate(string name, MyConfigOptions options)
  {
    string vor=null;
    var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
    var match = rx.Match(options.Key1);

    if (string.IsNullOrEmpty(match.Value))
    {
      vor = $"{options.Key1} doesn't match RegEx \n";
    }

    if ( options.Key2 < 0 || options.Key2 > 1000)
    {
      vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
    }

    if (_config.Key2 != default)
    {
      if(_config.Key3 <= _config.Key2)
      {
        vor += "Key3 must be > than Key2.";
      }
    }

    if (vor != null)
    {
      return ValidateOptionsResult.Fail(vor);
    }

    return ValidateOptionsResult.Success;
  }
}

IValidateOptions 允许将验证代码移出 StartUp 并将其移入类中。

使用前面的代码,使用以下代码在 Startup.ConfigureServices 中启用验证

public void ConfigureServices(IServiceCollection services)
{
  services.Configure<MyConfigOptions>(Configuration.GetSection(
                    MyConfigOptions.MyConfig));
  services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
               <MyConfigOptions>, MyConfigValidation>());
  services.AddControllersWithViews();
}

选项后期配置

使用 IPostConfigureOptions 设置后期配置。进行所有 IConfigureOptions 配置后运行后期配置

services.PostConfigure<MyOptions>(myOptions =>
{
  myOptions.Option1 = "post_configured_option1_value";
});

使用 PostConfigureAll 对所有配置实例进行后期配置

在启动期间访问选项

IOptions 和 IOptionsMonitor 可用于 Startup.Configure 中,因为在 Configure 方法执行之前已生成服务。

public void Configure(IApplicationBuilder app, 
  IOptionsMonitor<MyOptions> optionsAccessor)
{
  var option1 = optionsAccessor.CurrentValue.Option1;
}

结论

IOptions<>是单例,因此一旦生成了,除非通过代码的方式更改,它的值是不会更新的。

IOptionsMonitor<>也是单例,但是它通过IOptionsChangeTokenSource<> 能够和配置文件一起更新,也能通过代码的方式更改值。

IOptionsSnapshot<>是范围,所以在配置文件更新的下一次访问,它的值会更新,但是它不能跨范围通过代码的方式更改值,只能在当前范围(请求)内有效。

所以你应该根据你的实际使用场景来选择到底是用这三者中的哪一个。

一般来说,如果你依赖配置文件,那么首先考虑IOptionsMonitor<>,如果不合适接着考虑IOptionsSnapshot<>,最后考虑IOptions<>。

有一点需要注意,在ASP.NET Core应用中IOptionsMonitor可能会导致同一个请求中选项的值不一致,当你正在修改配置文件的时候,这可能会引发一些奇怪的bug。

如果这个对你很重要,请使用IOptionsSnapshot,它可以保证同一个请求中的一致性,但是它可能会带来轻微的性能上的损失。
如果你是在app启动的时候自己构造Options(比如在Startup类中):

services.Configure<TestOptions>(opt => opt.Name = "Test");

IOptions<>最简单,也许是一个不错的选择。

加载全部内容

相关教程
猜你喜欢
用户评论