我的编程空间,编程开发者的网络收藏夹
学习永远不晚

.Net Core 中选项Options的具体实现

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

.Net Core 中选项Options的具体实现

.NetCore的配置选项建议结合在一起学习,不了解.NetCore 配置Configuration的同学可以看下我的上一篇文章 [.Net Core配置Configuration具体实现]

由代码开始

定义一个用户配置选项


public class UserOptions
{
    private string instanceId;
    private static int index = 0;
    public UserOptions()
    {
        instanceId = (++index).ToString("00");
        Console.WriteLine($"Create UserOptions Instance:{instanceId}");
    }
    public string Name { get; set; }
    public int Age { get; set; }
    public override string ToString() => $"Name:{Name} Age:{Age} Instance:{instanceId} ";
}
public class UserOptions2
{
    public string Name { get; set; }
    public int Age { get; set; }
    public override string ToString() => $" Name:{Name} Age:{Age}";
}

定义json配置文件:myconfig.json


{
  "UserOption": {
    "Name": "ConfigName-zhangsan",
    "Age": 666
  }
}

创建ServiceCollection


services = new ServiceCollection();
var configBuilder = new ConfigurationBuilder().AddInMemoryCollection().AddJsonFile("myconfig.json", true, true);
var iconfiguration = configBuilder.Build();
services.AddSingleton<IConfiguration>(iconfiguration);

示例代码


services.Configure<UserOptions>(x => { x.Name = "张三"; x.Age = new Random().Next(1, 10000); });
services.AddOptions<UserOptions2>().Configure<IConfiguration>((x, config) => { x.Name = config["UserOption:Name"]; x.Age = 100; }); ;
services.PostConfigure<UserOptions>(x => { x.Name = x.Name + "Post"; x.Age = x.Age; });
services.Configure<UserOptions>("default", x => { x.Name = "Default-张三"; x.Age = new Random().Next(1, 10000); });
services.Configure<UserOptions>("config", configuration.GetSection("UserOption"));
using (var provider = services.BuildServiceProvider())
{
    using (var scope1 = provider.CreateScope())
    {
        PrintOptions(scope1, "Scope1");
    }

    //修改配置文件
    Console.WriteLine(string.Empty);
    Console.WriteLine("修改配置文件");
    var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "myconfig.json");
    File.WriteAllText(filePath, "{\"UserOption\": { \"Name\": \"ConfigName-lisi\", \"Age\": 777}}");
    //配置文件的change回调事件需要一定时间执行
    Thread.Sleep(300);
    Console.WriteLine(string.Empty);

    using (var scope2 = provider.CreateScope())
    {
        PrintOptions(scope2, "Scope2");
    }

    Console.WriteLine(string.Empty);

    using (var scope3 = provider.CreateScope())
    {
        PrintOptions(scope3, "Scope3");
    }
}

static void PrintOptions(IServiceScope scope, string scopeName)
{
    var options1 = scope.ServiceProvider.GetService<IOptions<UserOptions>>();
    Console.WriteLine($"手动注入读取,IOptions,{scopeName}-----{ options1.Value}");

    var options2 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>();
    Console.WriteLine($"配置文件读取,IOptionsSnapshot,{scopeName}-----{ options2.Value}");
    var options3 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>();
    Console.WriteLine($"配置文件根据名称读取,IOptionsSnapshot,{scopeName}-----{ options3.Get("config")}");

    var options4 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>();
    Console.WriteLine($"配置文件读取,IOptionsMonitor,{scopeName}-----{ options4.CurrentValue}");
    var options5 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>();
    Console.WriteLine($"配置文件根据名称读取,IOptionsMonitor,{scopeName}-----{options5.Get("config")}");

    var options6 = scope.ServiceProvider.GetService<IOptions<UserOptions2>>();
    Console.WriteLine($"Options2-----{options6.Value}");
}

代码运行结果

Create UserOptions Instance:01
手动注入读取,IOptions,Scope1----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:02
配置文件读取,IOptionsSnapshot,Scope1----- Name:张三Post Age:835 Instance:02
Create UserOptions Instance:03
配置文件根据名称读取,IOptionsSnapshot,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:03
Create UserOptions Instance:04
配置文件读取,IOptionsMonitor,Scope1----- Name:张三Post Age:1669 Instance:04
Create UserOptions Instance:05
配置文件根据名称读取,IOptionsMonitor,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:05
Options2----- Name:ConfigName-zhangsan Age:100

修改配置文件
Create UserOptions Instance:06

手动注入读取,IOptions,Scope2----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:07
配置文件读取,IOptionsSnapshot,Scope2----- Name:张三Post Age:5460 Instance:07
Create UserOptions Instance:08
配置文件根据名称读取,IOptionsSnapshot,Scope2----- Name:ConfigName-lisi Age:777 Instance:08
配置文件读取,IOptionsMonitor,Scope2----- Name:张三Post Age:1669 Instance:04
配置文件根据名称读取,IOptionsMonitor,Scope2----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100

手动注入读取,IOptions,Scope3----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:09
配置文件读取,IOptionsSnapshot,Scope3----- Name:张三Post Age:5038 Instance:09
Create UserOptions Instance:10
配置文件根据名称读取,IOptionsSnapshot,Scope3----- Name:ConfigName-lisi Age:777 Instance:10
配置文件读取,IOptionsMonitor,Scope3----- Name:张三Post Age:1669 Instance:04
配置文件根据名称读取,IOptionsMonitor,Scope3----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100

通过运行代码得到的结论

  • Options可通过手动初始化配置项配置(可在配置时读取依赖注入的对象)、或通过IConfiguration绑定配置
  • PostConfiger可在Configer基础上继续配置
  • 可通过IOptionsSnapshot或IOptionsMonitor根据配置名称读取配置项,未指定名称读取第一个注入的配置
  • IOptions和IOptionsMonitor生命周期为Singleton,IOptionsSnapshot生命周期为Scope
  • IOptionsMonitor可监听到配置文件变动去动态更新配置项

问题

  • IOptions,IOptionsSnapshot,IOptionsMonitor 如何/何时注入、初始化
  • Options指定名称时内部是如何设置的
  • Options如何绑定的IConfiguration
  • IOptionsMonitor是如何同步配置文件变动的

配合源码解决疑惑

Configure注入


public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
{
    return services.Configure(Microsoft.Extensions.Options.Options.DefaultName, configureOptions);
}

public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions) where TOptions : class
{
 services.AddOptions();
 services.AddSingleton((IConfigureOptions<TOptions>)new ConfigureNamedOptions<TOptions>(name, configureOptions));
 return services;
}

public static IServiceCollection AddOptions(this IServiceCollection services)
{
 services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
 services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
 services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
 services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
 services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
 return services;
}

通过上面的源码可以发现,Options相关类是在AddOptions中注入的,具体的配置项在Configure中注入。

如果不指定Configure的Name,也会有个默认的Name=Microsoft.Extensions.Options.Options.DefaultName

那么我们具体的配置项存到哪里去了呢,在ConfigureNamedOptions这个类中,在Configer函数调用时,只是把相关的配置委托存了起来:


public ConfigureNamedOptions(string name, Action<TOptions> action)
{
 Name = name;
 Action = action;
}

OptionsManager


private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal);

public TOptions Value => Get(Options.DefaultName);

public virtual TOptions Get(string name)
{
 name = name ?? Options.DefaultName;
 return _cache.GetOrAdd(name, () => _factory.Create(name));
}

OptionsManager实现相对较简单,在查询时需要执行Name,如果为空就用默认的Name,如果缓存没有,就用Factory创建一个,否则就读缓存中的选项。

IOptions和IOptionsSnapshot的实现类都是OptionsManager,只是生命周期不同。

OptionsFactory

那么OptionsFactory又是如何创建Options的呢?我们看一下他的构造函数,构造函数将所有Configure和PostConfigure的初始化委托都通过构造函数保存在内部变量中


public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures)
 {
        _setups = setups;
        _postConfigures = postConfigures;
 }

接下来看Create(有删改,与本次研究无关的代码没有贴出来):


 public TOptions Create(string name)
 {
        //首先创建对应Options的实例
  TOptions val = Activator.CreateInstance<TOptions>();
        //循环所有的配置项,依次执行,如果对同一个Options配置了多次,最后一次的赋值生效
  foreach (IConfigureOptions<TOptions> setup in _setups)
  {
   var configureNamedOptions = setup as IConfigureNamedOptions<TOptions>;
   if (configureNamedOptions != null)
   {
                //Configure中会判断传入Name的值与本身的Name值是否相同,不同则不执行Action
                //这解释了我们一开始的示例中,注入了三个UserOptions,但是在IOptionsSnapshot.Value中获取到的是第一个没有名字的
                //因为Value会调用OptionsManager.Get(Options.DefaultName),进而调用Factory的Create(Options.DefaultName)
    configureNamedOptions.Configure(name, val);
   }
   else if (name == Options.DefaultName)
   {
    setup.Configure(val);
   }
  }
        
        //PostConfigure没啥可多说了,名字判断逻辑与Configure一样
  foreach (var postConfigure in _postConfigures)
  {
   postConfigure.PostConfigure(name, val);
  }
  
  return val;
 }

NamedConfigureFromConfigurationOptions

IConfiguration配置Options的方式略有不同

对应Configure扩展方法最终调用的代码在Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions这个类中


public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class
{
 services.AddOptions();
 services.AddSingleton((IOptionsChangeTokenSource<TOptions>)new ConfigurationChangeTokenSource<TOptions>(name, config));
 return services.AddSingleton((IConfigureOptions<TOptions>)new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}

扩展方法里又注入了一个IOptionsChangeTokenSource,这个类的作用是提供一个配置文件变动监听的Token

同时将IConfigureOptions实现类注册成了NamedConfigureFromConfigurationOptions

NamedConfigureFromConfigurationOptions继承了ConfigureNamedOptions,在构造函数中用IConfiguration.Bind实现了生成Options的委托


 public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder)
  : base(name, (Action<TOptions>)delegate(TOptions options)
  {
   config.Bind(options, configureBinder);
  })

所以在Factory的Create函数中,会调用IConfiguration的Bind函数

由于IOptionsSnapshot生命周期是Scope,在配置文件变动后新的Scope中会获取最新的Options

ValidateOptions

OptionsBuilder还包含了一个Validate函数,该函数要求传入一个Func<TOptions,bool>的委托,会注入一个单例的ValidateOptions对象。

在OptionsFactory构建Options的时候会验证Options的有效性,验证失败会抛出OptionsValidationException异常

对于ValidateOptions和PostConfigureOptions都是构建Options实例时需要用到的主要模块,不过使用和内部实现都较为简单,应用场景也不是很多,本文就不对这两个类多做介绍了

结论

在Configure扩展函数中会首先调用AddOptions函数

IOptions,IOptionsSnapshot,IOptionsMonitor都是在AddOptions函数中注入的

Configure配置的选项配置委托最终会保存到ConfigureNamedOptions或NamedConfigureFromConfigurationOptions

IOptions和IOptionsSnapshot的实现类为OptionsManager

OptionsManager通过OptionsFactory创建Options的实例,并会以Name作为键存到字典中缓存实例

OptionsFactory会通过反射创建Options的实例,并调用ConfigureNamedOptions中的委托给实例赋值

现在只剩下最后一个问题了,OptionsMonitor是如何动态更新选项的呢?

其实前面的讲解中已经提到了一个关键的接口IOptionsChangeTokenSource,这个接口提供一个IChangeToken,通过ChangeToken监听这个Token就可以监听到文件的变动,我们来看下OptionsMonitor是否是这样做的吧!


//构造函数
public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
    _factory = factory;
    _sources = sources;
    _cache = cache;
    //循环属于TOptions的所有IChangeToken
    foreach (IOptionsChangeTokenSource<TOptions> source in _sources)
    {
        ChangeToken.OnChange(() => source.GetChangeToken(), delegate(string name)
                             {

                                //清除缓存 
                                name = name ?? Options.DefaultName;
        _cache.TryRemove(name);
                             }, source.Name);
    }
}

 

public virtual TOptions Get(string name)
{
    name = name ?? Options.DefaultName;
    return _cache.GetOrAdd(name, () => _factory.Create(name));
}

果然是这样的吧!

到此这篇关于.Net Core 中选项Options的具体实现的文章就介绍到这了,更多相关.Net Core Options内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

.Net Core 中选项Options的具体实现

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

ASP.NET Core中的Options选项模式怎么配置

这篇文章主要介绍“ASP.NET Core中的Options选项模式怎么配置”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“ASP.NET Core中的Options选项模式怎么配置”文章能帮助大家解
2023-06-29

如何在.Net Core中实现选择数据热更新

本篇文章为大家展示了如何在.Net Core中实现选择数据热更新,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。实践 通过AddSingleton单例方式注入,然后使用 IOptionsMonitor
2023-06-06

Java+Selenium实现控制浏览器的启动选项Options

这篇文章主要为大家详细介绍了如何使用java代码利用selenium控制浏览器的启动选项Options的代码操作,文中的示例代码讲解详细,感兴趣的可以了解一下
2023-01-11

.NET Core中RabbitMQ使用死信队列的实现

.NETCore中RabbitMQ死信队列用于存储因处理失败而被拒绝的消息。可通过设置x-dead-letter-exchange和x-dead-letter-routing-key属性或使用DeadLetterAdvancedConsumer特性实现。死信策略定义了消息被拒绝后的处理方式,包括拒绝退避、死信重路由或丢弃。最佳实践包括定义明确的死信策略、定期监视队列、使用发布者重试机制、定期清除队列以避免增长过大。
.NET Core中RabbitMQ使用死信队列的实现
2024-04-02

Android checkbox的listView(多选,全选,反选)具体实现方法

布局文件:[html] 代码如下: 2022-06-06

java项目依赖包选择具体实现类示例介绍

这篇文章主要为大家介绍了java项目依赖包选择具体实现类示例介绍,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-12-22

MySQL中子查询的具体实现

目录什么是子查询?子查询的分类1. 标量子查询2. 多行子查询3. 表子查询4. 相关子查询5. 非相关子查询子查询的应用场景1. 数据筛选2. 复杂条件查询3. 数据聚合4. 替代 JOIN子查询的优化1. 尽量使用非相关子查询2. 使用
MySQL中子查询的具体实现
2024-09-04

java中动态数组的具体实现

声明:data为数组名。size为数组中最后一个元素的下一个位置。实现动态数组的原因:因为java中的数组是静态的,在new数组时就需要指定数组的大小,如果需要存储的元素为未知的个数,设置空间过大会造成浪费,设置空间过小会无法存入全部数据,我们利用自己写的re
java中动态数组的具体实现
2019-01-23

canvas 中如何实现物体的框选

这篇文章主要为大家介绍了canvas中如何实现物体的框选的示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Opensuse中文设置的具体实现方法

本篇内容主要讲解“Opensuse中文设置的具体实现方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Opensuse中文设置的具体实现方法”吧!Opensuse中文设置的具体实现首先要安装相关
2023-06-16

vue中el-table合并列的具体实现

本文主要介绍了vue中el-table合并列的具体实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-16

教你如何利用.net core实现反向代理中间件的方法

这篇文章主要给大家介绍了关于利用.net core实现反向代理中间件的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用.net core具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧最近在将一些项目的rest api迁
2023-06-03

C#将dll打包到程序中的具体实现

这篇文章介绍了C#将dll打包到程序中的具体实现,有需要的朋友可以参考一下
2022-11-15

echarts中几种渐变方式的具体实现方式

在使用echarts绘制图表时,有的时候需要使用渐变色,下面这篇文章主要给大家介绍了关于echarts中几种渐变方式的具体实现方式,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
2022-11-16

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录