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);
}
本文详细介绍了ASP.NETCore中的Options模式,包括核心接口、配置流程、命名选项、DI配置和验证选项的方法,帮助开发者理解和使用Options功能进行应用配置管理。

1627

被折叠的 条评论
为什么被折叠?



