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