ASP.NET Core 选项模式源码学习Options IOptionsMonitor(三)

2020-02-24 16:01:01 浏览数 (1)

前言

IOptionsMonitor 是一种单一示例服务,可随时检索当前选项值,这在单一实例依赖项中尤其有用。IOptionsMonitor用于检索选项并管理TOption实例的选项通知, IOptionsMonitor<TOptions> 支持以下方案:

  • 更改通知
  • 命名选项
  • 可重载配置
  • 选择性选项失效 (IOptionsMonitorCache<TOptions>)

IOptionsMonitor

代码语言:javascript复制
    public interface IOptionsMonitor<out TOptions>
    {
        /// <summary>
        ///    返回具有 DefaultName 的当前 TOptions 实例。
        /// </summary>
        TOptions CurrentValue { get; }

        /// <summary>
        ///    返回具有给定名称的已配置的 TOptions 实例。
        /// </summary>
        TOptions Get(string name);

        /// <summary>
        ///     注册一个要在命名 TOptions 更改时调用的侦听器。
        /// </summary>
        IDisposable OnChange(Action<TOptions, string> listener);
    }

OptionsMonitor

OptionsMonitor通过IOptionsChangeTokenSource实现监听事件

代码语言:javascript复制
    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;
        }
    }

IOptionsChangeTokenSource 的代码片段:

代码语言:javascript复制
    public interface IOptionsChangeTokenSource<out TOptions>
    {

        IChangeToken GetChangeToken();


        string Name { get; }
    }

在OptionsMonitor的构造函数中,通过调用其GetChangeToken方法,获取到 ChangeToken ,在 InvokeChanged 完成 _onChange 事件的调用:

代码语言:javascript复制
        private void InvokeChanged(string name)
        {
            name = name ?? Options.DefaultName;
            _cache.TryRemove(name);
            var options = Get(name);
            if (_onChange != null)
            {
                _onChange.Invoke(options, name);
            }
        }

对外暴露OnChange方法,方便我们添加自己的业务逻辑

代码语言:javascript复制
        public IDisposable OnChange(Action<TOptions, string> listener)
        {
            var disposable = new ChangeTrackerDisposable(this, listener);
            _onChange  = disposable.OnChange;
            return disposable;
        }

通过ChangeTrackerDisposable进行事件的注销

代码语言:javascript复制
  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;
        }

ConfigurationChangeTokenSource

ConfigurationChangeTokenSource实现IOptionsChangeTokenSource接口

代码语言:javascript复制
    public class ConfigurationChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions>
    {
        private IConfiguration _config;


        public ConfigurationChangeTokenSource(IConfiguration config) : this(Options.DefaultName, config)
        { }


        public ConfigurationChangeTokenSource(string name, IConfiguration config)
        {
            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }
            _config = config;
            Name = name ?? Options.DefaultName;
        }


        public string Name { get; }


        public IChangeToken GetChangeToken()
        {
            return _config.GetReloadToken();
        }
    }

示例

代码语言:javascript复制
    public class WeatherForecastController : ControllerBase
    {
        private readonly ILogger<WeatherForecastController> _logger;

        private readonly IOptionsMonitor<MyOptions> _options;
        public WeatherForecastController(IOptionsMonitor<MyOptions> options, ILogger<WeatherForecastController> logger)
        {
            _options = options;
            _logger = logger;
        }

        [HttpGet]
        public OkObjectResult Get() {
            _options.OnChange(_=>_logger.LogWarning(_options.CurrentValue.Name));
            return Ok(string.Format("Name:{0},Url:{1}", _options.CurrentValue.Name,_options.CurrentValue.Url));
        }
    }

现在我们每次修改配置文件,便会触发OnChange事件

0 人点赞