前言

前文讲述了,服务和配置直接的配合,这一节写一下,当配置文件修改了,每个服务如何感知自己的配置。

正文

服务感知到自己的配置发生变化,这就牵扯出两个东西:

IoptionsMonitor<out TOptions>

IoptionSnapshot<out TOptions>

在作用域范围使用IoptionSnapshot,在单例中使用IoptionsMonitor 。

IoptionsMonitor

先来演示作用域范围的使用。

配置:

{
"SelfService": {
"name" : "zhangsan"
}
}

SelfServiceOption:

public class SelfServiceOption
{
public string Name { get; set; }
}

服务:

public class SelfService : ISelfService
{
IOptionsSnapshot<SelfServiceOption> _options;
public SelfService(IOptionsSnapshot<SelfServiceOption> options)
{
this._options = options;
}
public string ShowOptionName()
{
return _options.Value.Name;
}
}

注册:

services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"), BinderOptions =>
{
BinderOptions.BindNonPublicProperties = true;
});
services.AddScoped<ISelfService, SelfService>();

测试:

[HttpGet]
public int GetService([FromServices]ISelfService selfService)
{
Console.WriteLine(selfService.ShowOptionName());
return 1;
}

结果:

第一次访问后修改为zhangsan_change,再次访问接口,会呈现上述效果。

那么为什么使用IoptionsMonitor,而为什么Ioptions 没有用呢。

前一篇写过Ioptions 的实现类OptionsManager,这个是有缓存的_cache,如下:

public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{
private readonly IOptionsFactory<TOptions> _factory;
private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>(); // Note: this is a private cache /// <summary>
/// Initializes a new instance with the specified options configurations.
/// </summary>
/// <param name="factory">The factory to use to create options.</param>
public OptionsManager(IOptionsFactory<TOptions> factory)
{
_factory = factory;
} /// <summary>
/// The default configured <typeparamref name="TOptions"/> instance, equivalent to Get(Options.DefaultName).
/// </summary>
public TOptions Value
{
get
{
return Get(Options.DefaultName);
}
} /// <summary>
/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
/// </summary>
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName; // Store the options in our instance cache
return _cache.GetOrAdd(name, () => _factory.Create(name));
}
}

IoptionsMonitor的实现类也是OptionsManager,但是人家是作用域模式。

在Addoptions中:

services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));

也就是说每创建一个SelfService,就会创建一个OptionsManager。缓存自然只在作用域内有效。

好的,那么来看下单例。

IoptionsMonitor

服务:

public class SelfService : ISelfService
{
IOptionsMonitor<SelfServiceOption> _options;
public SelfService(IOptionsMonitor<SelfServiceOption> options)
{
this._options = options; _options.OnChange((selftServiceOptions) =>
{
Console.WriteLine("alter change:"+selftServiceOptions.Name);
});
}
public string ShowOptionName()
{
return _options.CurrentValue.Name;
}
}

注册:

services.Configure<SelfServiceOption>(Configuration.GetSection("SelfService"), BinderOptions =>
{
BinderOptions.BindNonPublicProperties = true;
});
services.AddSingleton<ISelfService, SelfService>();

测试接口:

[HttpGet]
public int GetService([FromServices]ISelfService selfService)
{
Console.WriteLine(selfService.ShowOptionName());
return 1;
}

同意是修改钱访问一次,修改后访问一次。

结果如下:

那么看下IOptionMonitor的实现类OptionsMonitor:

public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions>, IDisposable where TOptions : class, new()
{
private readonly IOptionsMonitorCache<TOptions> _cache;
private readonly IOptionsFactory<TOptions> _factory;
private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;
private readonly List<IDisposable> _registrations = new List<IDisposable>();
internal event Action<TOptions, string> _onChange; /// <summary>
/// Constructor.
/// </summary>
/// <param name="factory">The factory to use to create options.</param>
/// <param name="sources">The sources used to listen for changes to the options instance.</param>
/// <param name="cache">The cache used to store options.</param>
public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
_factory = factory;
_sources = sources;
_cache = cache; foreach (var source in _sources)
{
var registration = ChangeToken.OnChange(
() => source.GetChangeToken(),
(name) => InvokeChanged(name),
source.Name); _registrations.Add(registration);
}
} private void InvokeChanged(string name)
{
name = name ?? Options.DefaultName;
_cache.TryRemove(name);
var options = Get(name);
if (_onChange != null)
{
_onChange.Invoke(options, name);
}
} /// <summary>
/// The present value of the options.
/// </summary>
public TOptions CurrentValue
{
get => Get(Options.DefaultName);
} /// <summary>
/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
/// </summary>
public virtual TOptions Get(string name)
{
name = name ?? Options.DefaultName;
return _cache.GetOrAdd(name, () => _factory.Create(name));
} /// <summary>
/// Registers a listener to be called whenever <typeparamref name="TOptions"/> changes.
/// </summary>
/// <param name="listener">The action to be invoked when <typeparamref name="TOptions"/> has changed.</param>
/// <returns>An <see cref="IDisposable"/> which should be disposed to stop listening for changes.</returns>
public IDisposable OnChange(Action<TOptions, string> listener)
{
var disposable = new ChangeTrackerDisposable(this, listener);
_onChange += disposable.OnChange;
return disposable;
} /// <summary>
/// Removes all change registration subscriptions.
/// </summary>
public void Dispose()
{
// Remove all subscriptions to the change tokens
foreach (var registration in _registrations)
{
registration.Dispose();
} _registrations.Clear();
} internal class ChangeTrackerDisposable : IDisposable
{
private readonly Action<TOptions, string> _listener;
private readonly OptionsMonitor<TOptions> _monitor; public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
{
_listener = listener;
_monitor = monitor;
} public void OnChange(TOptions options, string name) => _listener.Invoke(options, name); public void Dispose() => _monitor._onChange -= OnChange;
}
}

给每个给做了监听哈:

foreach (var source in _sources)
{
var registration = ChangeToken.OnChange(
() => source.GetChangeToken(),
(name) => InvokeChanged(name),
source.Name); _registrations.Add(registration);
}

这个IOptionsChangeTokenSource怎么来的呢?是在我们的configure配置方法中:

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

看到这一段:services.AddSingleton<IOptionsChangeTokenSource>(new ConfigurationChangeTokenSource(name, config));。

当有修改后,那么会调用:

private void InvokeChanged(string name)
{
name = name ?? Options.DefaultName;
_cache.TryRemove(name);
var options = Get(name);
if (_onChange != null)
{
_onChange.Invoke(options, name);
}
}

这里面会移除缓存_cache.TryRemove(name);,然后重新新调用: Get(name);也就会再绑定一次。

这里面有一个值得注意的是,如果有回调,不一定是本身这个服务的配置修改,可能是其他服务的配置修改了,也会被通知,因为这个是文件发生变化就会被通知。

原理如下:

GetSession会返回一个 ConfigurationSection。那么它里面的GetReloadToken是这样的:

 public IChangeToken GetReloadToken() => _root.GetReloadToken();

这返回了ConfigurationRoot的GetReloadToken。

实验一下:

{
"SelfService": {
"name": "zhangsan"
},
"SelfService2": {
"name" : "good one"
}
}

改成:

{
"SelfService": {
"name": "zhangsan"
},
"SelfService2": {
"name" : "good one1"
}
}

结果:

索引我们可以在服务里面配置增加一个version版本号,如果版本修改了,然后才做相应的操作。

以上只是个人整理,如有错误,望请指点。

下一节:配置验证。

重新整理 .net core 实践篇—————服务的配置更新[十三]的更多相关文章

  1. 重新整理 .net core 实践篇—————服务与配置之间[十一二]

    前言 前面基本介绍了,官方对于asp .net core 设计配置和设计服务的框架的一些思路.看下服务和配置之间是如何联系的吧. 正文 服务: public interface ISelfServic ...

  2. 重新整理 .net core 实践篇—————3种配置验证[十四]

    前言 简单整理一些配置的验证. 正文 配置的验证大概分为3类: 直接注册验证函数 实现IValidteOptions 使用Microsoft.Extensions.Options.DataAnnota ...

  3. 重新整理 .net core 实践篇—————路由和终结点[二十三]

    前言 简单整理一下路由和终节点. 正文 路由方式主要有两种: 1.路由模板方式 2.RouteAttribute 方式 路由约束: 1.类型约束 2.范围约束 3.正则表达式 4.是否必选 5.自定义 ...

  4. 重新整理 .net core 实践篇————配置应用[一]

    前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...

  5. 携程框架Apollo实现.NET Core微服务统一配置(测试环境-单机)

    Apollo实现.NET Core微服务统一配置(测试环境-单机) https://www.cnblogs.com/guolianyu/p/10065999.html 一.前言 注:此篇只是为测试环境 ...

  6. 重新整理 .net core 实践篇—————配置系统之军令状[七](配置文件)

    前言 介绍一下配置系统中的配置文件,很多服务的配置都写在配置文件中,也是配置系统的大头. 正文 在asp .net core 提供了下面几种配置文件格式的读取方式. Microsoft.extensi ...

  7. 重新整理 .net core 实践篇—————配置系统之强类型配置[十]

    前言 前文中我们去获取value值的时候,都是通过configurationRoot 来获取的,如configurationRoot["key"],这种形式. 这种形式有一个不好的 ...

  8. 重新整理 .net core 实践篇—————配置系统之简单配置中心[十一]

    前言 市面上已经有很多配置中心集成工具了,故此不会去实践某个框架. 下面链接是apollo 官网的教程,实在太详细了,本文介绍一下扩展数据源,和简单翻翻阅一下apollo 关键部分. apollo 服 ...

  9. 重新整理 .net core 实践篇—————grpc[三十三]

    前言 简单整理一下grpc. 正文 什么是grpc? 一个远程过程调用框架,可以像类一样调用远程方法. 这种模式一般来说就是代理模式,然后都是框架自我生成的. 由google 公司发起并开源,故而前面 ...

随机推荐

  1. Spring Security极简入门三部曲(上篇)

    目录 Spring Security极简入门三部曲(上篇) 写在前面 为什么要用Spring Security 数据库设计 demo时刻 核心代码讲解 小结 Spring Security极简入门三部 ...

  2. APP的编译和反编译

    目录 Apktool的准备 Apktool解压APP Apktool打包APP 对APP进行签名

  3. Portswigger web security academy:OS command injection

    Portswigger web security academy:OS command injection 目录 Portswigger web security academy:OS command ...

  4. SpringBoot配置切换

    切换需求 有时候在本地测试是使用8080端口,可是上线使用的又是80端口. 此时就可以通过多配置文件实现多配置支持与灵活切换. 多配置文件 3个配置文件: 核心配置文件:application.pro ...

  5. restful 与 webapi 详解

    restful 什么是API API全称Aplication Programming Itererface即应用程序编程接口, 我们在开发应用程序时经常用到.API作为接口,用来"连接&qu ...

  6. Docker 部署net5程序

    1.前言 本文讲述使用docker容器部署.net5项目.实现跨平台. 部署环境:docker 容器.liunx系统.这里采用VmWare虚拟机.并采用MobaXterm工具实现终端连接linux系统 ...

  7. 封装了一些sqlsugar的常用方法 用来动态切换数据库和依赖注入 支持泛型

    接口: /// <summary> /// 数据库操作 /// </summary> public interface IDAL_Services { /// <summ ...

  8. UVA OJ 623 500!

    500!  In these days you can more and more often happen to see programs which perform some useful cal ...

  9. Let's go!

    第一次开通博客 心情还是很激动的,而且做出了这么好看的页面虽然都是用的别人的组件,自己不是很知道原理但是也很开心,以后会将自己学习的东西写成笔记发在上面

  10. [tools] 工具

    代码编辑 notepad++ 文档对比 Beyond Compare 代码阅读 source insight 代码分析 Scitools 下载 http://www.cr173.com/soft/29 ...