Options

Options

基础知识

在aspnetcore里,已经第三方框架中经常见到已Options结尾的选项。我们需要了解Options的原理,以及配置和执行流程。选项是基于容器实现。

依赖项

Microsoft.Extensions.Options:选项的核心包,扩展IServiceCollection接口,只支持内存配置。

Microsoft.Extensions.Options.ConfigurationExtensions:配置文件的扩展,支持IConfiguration进行配置。

Microsoft.Extensions.DependencyInjection:选项必须配合容器使用

Microsoft.Extensions.Options.DataAnnotations:支持数据注解验证

核心接口

IOptions<TOptions>:单实列,不支持命名。

IOptionsSnapshot<TOptions>:每次实列化时更新选项,scope级别,支持命名配置

IOptionsMonitor<TOptions>:监听配置更改并通知,Singleton级别,支持命名配置

IOptionsFactory<TOptions>:管理Options实列,包括创建、配置、验证,支持命名配置

IConfigureOptions:保存配置选项时的委托,一个选项可以添加多个委托进行配置

IPostConfigureOptions:保存配置选项的委托,在IConfigureOptions委托执行之后执行

OptionsChangeTokenSource:用于监听IConfiguration的更改通知

IValidateOptions:配置选项之后的验证

基本使用

public class MvcOptions 
{
    public string Url1 { get; set; }
    public string Url2 { get; set; }
}
var services = new ServiceCollection();
services.Configure<MvcOptions>(config => 
{
    config.Url1 = "Configure1";
});
services.PostConfigure<MvcOptions>(config =>
{
    config.Url2 = "Configure2";
});
var sp = services.BuildServiceProvider();
var options = sp.GetRequiredService<IOptions<MvcOptions>>();
​

源码解读

1.调用Configure会向容器注册一个IConfigureOptions<TOptions>类型的服务,并且记录我们编写的委托

2.调用PostConfigure会向容器注册一个IPostConfigureOptions<TOptions>类型的服务,并且记录我们编写的委托

3.Configure或者PostConfigure都会执行AddOptions();

4.AddOptions();会尝试向容器注册IOptions、IOptionsSnapshot、IOptionsMonitor、IOptionsFactory、IOptionsMonitorCache

5.IOptionsFactory内部实现逻辑是,首先加载所有注册的IConfigureOptions、IPostConfigureOptions、IValidateOptions服务实列Create方法首先通过反射调用选项的无参数构造器来实列化选项。然后通过选项实列来执行所有IConfigureOptions中的委托,然后在执行所有IPostConfigureOptions中委托,然后在执行IValidateOptions<TOptions>中的验证逻辑。

6.IOptions实列依赖IOptionsFactory创建一个名为Options.DefaultName的选项,由于是单实列的,只能执行一次委托来计算选项的值。

7.IOptionsSnapshot依赖IOptionsFactory创建一个名为Options.DefaultName的选项Value,并支持命名Get方法获取选项(内置缓存机制)因为是Scoped所以每次获取新的选项实列都会执行委托来重新计算选项值。

8.IOptionsMonitor依赖IOptionsFactory提供支持命名的Get方法获取选项,虽然IOptionsMonitor是单实列的并且会监听配置更改,可以通过注册OnChange来执行更改之后的逻辑来更新选项。

核心方法

//1.通过委托来配置选项,同一个选项可以按顺序执行多个Configure。
services.Configure<MvcOptions>(a => 
{
    a.Url1 = "132";
});
//2.在所有的Configure执行之后配置选项。同一个选项可以按顺序执行多个PostConfigure。
services.PostConfigure<MvcOptions>(a =>
{
    a.Url1 = "132";
});
//AddOptions返回一个OptionsBuilder,可以连续配置选项,本质还是执行Configure和PostConfigure
services.AddOptions<MvcOptions>()   
    .Configure(a => a.Url1 = "123")
    .PostConfigure(a => a.Url1 = "155");

解析选项

var services = new ServiceCollection();
services.Configure<MvcOptions>(a =>
{
    a.Url1 = "132";
});
var sp = services.BuildServiceProvider();
var optionsFactory = sp.GetRequiredService<IOptionsFactory<MvcOptions>>();
var options = sp.GetRequiredService<IOptions<MvcOptions>>();
var optionsMonitor = sp.GetRequiredService<IOptionsMonitor<MvcOptions>>();
var optionsSnapshot = sp.GetRequiredService<IOptionsSnapshot<MvcOptions>>();

注入容器

services.Configure<MvcOptions>(a =>
{
    a.Url = "132";
});
services.AddSingleton(sp => 
{   //IOptions只能注册成单实例
    return sp.GetRequiredService<IOptions<MvcOptions>>().Value;
});
//必须使用Scoped,因为IOptionsSnapshot是Scoped的,如果是单实列的话,就丢失了自动更改的性质了
services.AddScoped(sp =>
{
    return sp.GetRequiredService<IOptionsSnapshot<MvcOptions>>().Value;
});
var sp = services.BuildServiceProvider();
var options = sp.GetRequiredService<MvcOptions>();

配置支持

需要安装

Microsoft.Extensions.Configuration.Json:用于构建配置

Microsoft.Extensions.Options.ConfigurationExtensions:用于支持配置

{
  "MvcOptions": {
    "Url": "123"
  }
}
var configuration = new ConfigurationManager();
configuration.SetBasePath(Directory.GetCurrentDirectory());
configuration.AddJsonFile("config.json", false, true);
​
var services = new ServiceCollection();
services.Configure<MvcOptions>(configuration.GetSection("MvcOptions"));
var container = services.BuildServiceProvider();
while (true)
{
    Thread.Sleep(2000);
    using (var scope = container.CreateScope())
    {
        //测试更改监听
        var o1 = scope.ServiceProvider.GetRequiredService<IOptions<MvcOptions>>();
        var o2 = scope.ServiceProvider.GetRequiredService<IOptionsSnapshot<MvcOptions>>();
        var o3 = scope.ServiceProvider.GetRequiredService<IOptionsMonitor<MvcOptions>>();
        Console.WriteLine("==================");
        Console.WriteLine($"IOptions:{o1.Value.Url}");
        Console.WriteLine($"IOptionsSnapshot:{o2.Value.Url}");
        Console.WriteLine($"IOptionsMonitor:{o3.CurrentValue.Url}");
        o3.OnChange(o =>
        {            
            Console.WriteLine("选项发生更改了");
        });
    }
}

命名选项

var services = new ServiceCollection();
var configuration = GetConfiguration();
//name=Options.DefaultName
services.Configure<MvcOptions>(c => c.Url1 = "123");
//name="o1"
services.Configure<MvcOptions>("o1", c => c.Url1 = "456");
//name="o2"
services.Configure<MvcOptions>("o2", c => c.Url1 = "789");
​
var container = services.BuildServiceProvider();
var o1 = container.GetRequiredService<IOptionsSnapshot<MvcOptions>>();
var o2 = container.GetRequiredService<IOptionsMonitor<MvcOptions>>();
//name="o1"
Console.WriteLine("IOptionsSnapshot:Named:" + o1.Get("o1").Url);
//name=Options.DefaultName
Console.WriteLine("IOptionsSnapshot:Value:" + o1.Value.Url);
//name="o2"
Console.WriteLine("IOptionsMonitor:Named:" + o2.Get("o2").Url);
//name=Options.DefaultName
Console.WriteLine("IOptionsMonitor:Value:" + o2.CurrentValue.Url);
var optionsFactory = sp.GetRequiredService<IOptionsFactory<MvcOptions>>();
var options = optionsFactory.Create(Options.DefaultName);

DI配置

  • 方式1:推荐

internal class MvcOptionsDep
{
    public void Configure(MvcOptions options)
    {
        options.Url = "6666";
    }
}
 var services = new ServiceCollection();
//注册di服务,只能是单实例
services.AddSingleton<MvcOptionsDep>();
//注册选项
services.AddOptions<MvcOptions>()
    //第一个配置
    .Configure(a => a.Url = "123")
    //后续配置
    .PostConfigure<MvcOptionsDep>((options, dep) => 
    {
        //调用MvcOptionsDep中的方法来进行配置        
        dep.Configure(options);
    });
var container = services.BuildServiceProvider();
var options = container.GetRequiredService<IOptions<MvcOptions>>();
  • 方式2

//你也可以实现IConfigureOptions但是IConfigureOptions的执行顺序优先级比较低(要学会举一反三)
internal class MvcOptionsPostConfigureOptions : IPostConfigureOptions<MvcOptions>
{
    public void PostConfigure(string name, MvcOptions options)
    {
        options.Url = "789";
        //可以编写验证逻辑
        //if (options.Url == "123")
        //{
        //    throw new InvalidDataException("Url不能等于123");
        //}
    }
}
​
var services = new ServiceCollection();
services.AddSingleton<IPostConfigureOptions<MvcOptions>, MvcOptionsPostConfigureOptions>();
var container = services.BuildServiceProvider();
var options = container.GetRequiredService<IOptions<MvcOptions>>();

验证选项

注意验证是一定是在Configure和PostConfigure之后执行的

  • 委托验证

public static void TestValidateDelegate()
{
    try
    {
        var services = new ServiceCollection();
        //不是把选项注入到容器,只是注入了该选项的委托
        services.Configure<MvcOptions>(a => a.Url = "123");
        //得到OptionsBuilder,进行验证,本质还是执行Configure(可以执行无数多个Configre)
        services.AddOptions<MvcOptions>()
            .Validate(options => 
            {
                if (options.Url == null)
                {
                    return false;
                }
                if (!options.Url.StartsWith("aa"))
                {
                    return false;
                }
                return true;
            }, "必须以aa开头");
        var container = services.BuildServiceProvider();
        var options = container.GetRequiredService<IOptions<MvcOptions>>();
        Console.WriteLine(options.Value.Url);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
  • 注解验证

需要安装Microsoft.Extensions.Options.DataAnnotations包

public class MvcOptions
{
    [RegularExpression(@"^aa", ErrorMessage = "必须以aa开头")]
    public string? Url { get; set; }
}
​
try
{
    var services = new ServiceCollection();
    //不是把选项注入到容器,只是注入了该选项的委托
    services.Configure<MvcOptions>(a => a.Url = "123");
    //得到OptionsBuilder,进行验证,本质还是执行Configure(可以执行无数多个Configre)
    services.AddOptions<MvcOptions>()
        .ValidateDataAnnotations();
    var container = services.BuildServiceProvider();
    var options = container.GetRequiredService<IOptions<MvcOptions>>();
    Console.WriteLine(options.Value.Url);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值