环境:
- window 10
- .netcore 3.1
- vs2019 16.5.5
- dnSpy v6.1.4 (.NET Core)
一、选项模型概念
关于IOption
:
IOption被称为
选项模型
,它是在.netcore中引入的。它的作用是给我们的应用程序提供配置,这个配置就是一个个的对象模型。
关于“依赖注入”和“选项模型”:
“选项模型”本身是构筑在“依赖注入”框架上的,所以我们要想使用“选项模型”就必须先引入“依赖注入”框架。
关于“配置”和“选项模型”:
IConfiguration
和IOption
在.netcore中被称为配置选项,前者表示配置,后者表示选项模型。它们二者在.netcore中是一对基石。关于IConfiguration的分析请查看上一篇文章:.netcore入门25:.net core源码分析之配置模块(IConfiguration)。
虽然它们二者的关系紧密,而且选项模型
的数据来源大多来自配置
,但它们二者之间并没有直接的依赖关系,也就是说它们二者任何一个都可以单独拿出来使用。后面讲解的时候会先独立分析选项模型
,然后再分析它们二者的结合。
关于选项模型的包:
- 选项模型本身就一个nuget包:
Microsoft.Extensions.Options
。- 选项模型是运行在依赖注入框架上的,所以它引用“依赖注入”框架的抽象包
Microsoft.Extensions.DependencyInjection.Abstractions
。- 如果是控制台程序,为了能使用“选项模型”,我们还要再手动引入“依赖注入”框架的实现包:
Microsoft.Extensions.DependencyInjection
- 如果我们想让“配置”为“选项模型”提供数据来源,那么我们需要手动引入扩展包:
Microsoft.Extensions.Options.ConfigurationExtensions
二、使用选项模型(最简单的用法)
- 第一步:新建控制台程序,添加包:
Microsoft.Extensions.Options
和Microsoft.Extensions.DependencyInjection
- 第二步:控制台编写代码:
namespace ConsoleApp7 { public class Person { public int Id { get; set; } public string Name { get; set; } public override string ToString() { return $"(Person=>Id:{Id},Name:{Name})"; } } public class Student { public int Id { get; set; } public string Name { get; set; } public override string ToString() { return $"(Student=>Id:{Id},Name:{Name})"; } } class Program { public static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); //向依赖框架中注入选项模型服务 services.AddOptions(); //注入Person模型的配置逻辑 services.Configure<Person>(person => { person.Id = 1; person.Name = "小明"; Console.WriteLine("services.Configure<Person>(person =>{})"); }); //注入Student模型的配置逻辑 services.Configure<Student>(student => { student.Id = 1; student.Name = "小刚"; Console.WriteLine("services.Configure<Student>(student =>{})"); }); //创建依赖容器 var provider = services.BuildServiceProvider(); //获取选项模型Person var option = provider.GetService<IOptions<Person>>(); var person = option.Value; //获取选项模型Student var option2 = provider.GetService<IOptions<Student>>(); var student = option2.Value; Console.WriteLine(person); Console.WriteLine(student); Console.WriteLine("ok"); Console.ReadLine(); } } }
- 第三步:运行效果:
三、选项模型用法剖析
选项模型分类:
- 第一:按类别,比如这个模型是
Person
的还是Student
的。 - 第二:按名称,即使同一个类别的模型(如:Person),也要再区分名称(比如:“小明”、“小刚”等)
根据上面分析的选项模型的分类,我们在向依赖容器注入选项模型的初始化逻辑时有如下几种方法:
注入初始化逻辑:
services.Configure<Student>(student =>{})
:名称为空字符串初始化逻辑services.Configure<Student>("小明",student =>{})
:名称为“小明”的初始化逻辑services.ConfigureAll<Student>(student =>{})
:为所有名称的模型注入初始化逻辑
注入初始化后逻辑:
services.PostConfigure<Student>(student =>{})
:名称为空字符串初始化后逻辑services.PostConfigure<Student>("小明",student =>{})
:名称为空字符串初始化后逻辑services.PostConfigureAll<Student>(student =>{})
:为所有名称的模型注入初始化后逻辑
注入初始化类:
services.ConfigureOptions<PersonConfig>();
:将Person类的初始化逻辑放到类PersonConfig
中,其中PersonConfig
的定义如下:
上面列举的注册方法的源码如下:
除了上面直接在IServiceCollection
上扩展的方法外,我们也可以使用OptionsBuilder
进行构建:
var services = new ServiceCollection();
varoptionBuilder = services.AddOptions<Person>("小明");
optionBuilder.Configure(person => { });
optionBuilder.PostConfigure(person => { });
optionBuilder.Validate(person => true);
四、选项模型的验证
一般我们用
Options
模型承载用户的配置,那么既然是配置就可能会有配置出错的问题,微软为我们提供了选项模型验证的功能(optionBuilder.Validate(Action<TModel>action)
),借助这个功能,我们就可以让调用者在获取选项模型实例的时候就能抛出异常,而不是在使用到某一配置项的时候才意外抛出错误。
具体的使用方法看下面实例:
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
var optionBuilder = services.AddOptions<BaseConfig>()
.Configure(config =>
{
config.ThirdApiAddress = ReadThridApiAddress();
})
.Validate(config => config.ThirdApiAddress != null && config.ThirdApiAddress.StartsWith("http"), "第三方接口地址配置错误,请检查!");
var provider = services.BuildServiceProvider();
var option = provider.GetRequiredService<IOptions<BaseConfig>>().Value;
Console.WriteLine("Hello World!");
}
public static string ReadThridApiAddress()
{
return "htt://www.xxxx.com/api/user/verfiy";
}
}
public class BaseConfig
{
public string ThirdApiAddress { get; set; }
}
运行后报错如下:
五、集成配置模型
比如,我们想从Json配置
文件中读取配置内容并转化成选项模型实例,那么我们需要引用Json配置包Microsoft.Extensions.Configuration.Json
,还要配置到选项模型的绑定包Microsoft.Extensions.Options.ConfigurationExtensions
,如下图所示:
接下来就看他们结合的代码:
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
var configBuilder = new ConfigurationBuilder();
//下面两种写法都可以
var config = configBuilder.AddJsonFile("conf.json").Build();
//services.AddOptions<BaseConfig>().Bind(config);
services.Configure<BaseConfig>(config);
var provider = services.BuildServiceProvider();
var baseConfig = provider.GetRequiredService<IOptions<BaseConfig>>().Value;
Console.WriteLine($"baseConfig.ThirdApiAddress={baseConfig.ThirdApiAddress}");
Console.WriteLine("Hello World!");
}
public static string ReadThridApiAddress()
{
return "htt://www.xxxx.com/api/user/verfiy";
}
}
public class BaseConfig
{
public string ThirdApiAddress { get; set; }
}
注意:上面代码中的
conf.json
文件,自己直接在工程根目录下新建个就可以,内容如下:{ "ThirdApiAddress": "http://www.xxxx.com/user/verify" }
另外,在vs中设置conf.json文件属性为输出到目录。
代码运行如下:
六、具名选项模型(IOptionsSnapshot<TOptions>
)
这个具名选项模型其实上面已经说过了,在
《三、选项模型用法剖析》
中,我们说选项模型按照“类别”和“名称”进行分类,那么具有名称的选项模型就是具名选项模型
。
它的使用方式如下:
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
services.Configure<BaseConfig>("development", conf =>
{
conf.ThirdApiAddress = "http://www.xxxx.conm/test";
});
services.Configure<BaseConfig>("production", conf =>
{
conf.ThirdApiAddress = "http://www.xxxx.com/api/user/verify";
});
var provider = services.BuildServiceProvider();
var snapConf = provider.GetRequiredService<IOptionsSnapshot<BaseConfig>>();
var dev = snapConf.Get("development");
Console.WriteLine($"development:ThirdApiAddress={dev.ThirdApiAddress}");
var prod = snapConf.Get("production");
Console.WriteLine($"production:ThirdApiAddress={prod.ThirdApiAddress}");
Console.WriteLine("Hello World!");
}
}
public class BaseConfig
{
public string ThirdApiAddress { get; set; }
}
运行效果如下图:
七、根据配置文件实时更新选项模型()
在上面我们已经将json
配置文件中的内容绑定到了选项模型BaseConfig
中,现在我们要实现的时:在程序运行的时候,如果conf.json
文件的内容改变了,那么重新获取的BaseConfig
实例也会发生变化。
我们还是使用上面的conf.json
配置文件,具体代码如下:
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
var config = new ConfigurationBuilder()
.AddJsonFile("conf.json", optional: false, reloadOnChange: true)
.Build(); ;
services.Configure<BaseConfig>(config);
var provider = services.BuildServiceProvider();
while (true)
{
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<BaseConfig>>();
Console.WriteLine($"optionsMonitor.CurrentValue.ThirdApiAddress={optionsMonitor.CurrentValue.ThirdApiAddress}");
Console.WriteLine("请修改conf.json内容后按回车...");
Console.ReadLine();
}
}
}
public class BaseConfig
{
public string ThirdApiAddress { get; set; }
}
此时conf.json
中的内容如下:
{
"ThirdApiAddress": "http://www.xxxx.com/user/verify"
}
我们运行应用程序,可以看到将当前的值打印出来了,按照提示,我们改变conf.json
文件的内容,然后敲回车,即可查看到json
配置文件的变化已经转换到了选项模型上,如下图所示: