【问题标题】:ChangeToken.OnChange not fire on custom configuration providerChangeToken.OnChange 不会在自定义配置提供程序上触发
【发布时间】:2018-02-13 15:25:26
【问题描述】:

我将尝试创建将从数据库中获取密钥的自定义配置提供程序。正如手册中所写,我创建了这个提供程序,它工作正常。所有键都在启动时加载,一切正常。

但现在我正在尝试使用 IOptionsSnapshot 并在更改时从 db 重新加载密钥。但是什么也没发生。

谁能告诉我出了什么问题?这是我的代码:

public class EFConfigProvider : ConfigurationProvider
{
    private DateTime lastLoaded;
    public EFConfigProvider(Action<DbContextOptionsBuilder> optionsAction)
    {
        OptionsAction = optionsAction;
        lastLoaded = DateTime.Now;
        ChangeToken.OnChange(
            () => Watch(),
            () => {
                Thread.Sleep(250);
                this.Load();
            });
    }

    public new IChangeToken GetReloadToken()
    {
        return Watch();
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    // Load config data from EF DB.
    public override void Load()
    {
        this.Data.Clear();

        var builder = new DbContextOptionsBuilder<ConfigContext>();
        OptionsAction(builder);

        using (var dbContext = new ConfigContext(builder.Options))
        {
            // Save Load Fact
            dbContext.SaveLoadFact();
            // Load Partners Settings
            GetPartners(dbContext);
        }
    }

    private IChangeToken Watch()
    {
        return new DatabaseChangeToken();
    }
}

public class DatabaseChangeToken : IChangeToken
{
    public bool HasChanged
    {
        get
        {
            return true;
        }
    }
    public bool ActiveChangeCallbacks => false;

    public IDisposable RegisterChangeCallback(Action<object> callback, object state) => EmptyDisposable.Instance;

    internal class EmptyDisposable : IDisposable
    {
        public static EmptyDisposable Instance { get; } = new EmptyDisposable();
        private EmptyDisposable() { }
        public void Dispose() { }
    }
}

我做了什么来开始工作:

  1. 我在类中添加了 EFConfigProvider 变量
private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
  1. 我在构造函数中添加
// Start Periodic task to refresh the DB
        PeriodicTask.Run(() =>
        {
            //Refresh();
            OnReload();
        }, TimeSpan.FromSeconds(reload));
  1. 我为 periodTask 添加类
public class PeriodicTask
{
    public static async Task Run(Action action, TimeSpan period, CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            await Task.Delay(period, cancellationToken);

            if (!cancellationToken.IsCancellationRequested)
                action();
        }
    }

    public static Task Run(Action action, TimeSpan period)
    {
        return Run(action, period, CancellationToken.None);
    }
}
  1. 重新加载时添加方法
protected new void OnReload()
        {
            var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
            previousToken.OnReload();
        }
  1. 添加使用更改令牌的更改
  ChangeToken.OnChange(
                () => { return this._reloadToken; },
                () => {
                    Thread.Sleep(250);
                    this.Load();
                });

【问题讨论】:

    标签: c# asp.net-core configuration


    【解决方案1】:

    更新:我发现覆盖OnReload() 破坏了更改通知(我使用的是OptionsMonitor,它没有看到更改)。所以我改变了我的实现不这样做。它调用默认的OnReload() 来处理OptionsMonitor 的更改通知。


    您更新的实现确实帮了我很大的忙,非常感谢!

    但您可以只使用Timer,而不是使用您的自定义PeriodicTask。效果是一样的。

    这是我的实现,每 5 分钟重新加载一次数据:

    using System;
    using System.Linq;
    using System.Threading;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Primitives;
    using Timer = System.Timers.Timer;
    
    namespace MyProject.Classes.Configuration {
        public class MyConfigProvider : ConfigurationProvider {
            private readonly DbContextOptions<MyDbContext> _dbOptions;
            private readonly Timer _reloadTimer = new Timer();
            private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
    
            public MyConfigProvider(Action<DbContextOptionsBuilder> dbOptionsAction) {
                var builder = new DbContextOptionsBuilder<MyDbContext>();
                dbOptionsAction(builder);
                _dbOptions = builder.Options;
    
                _reloadTimer.AutoReset = false;
                _reloadTimer.Interval = TimeSpan.FromMinutes(5).TotalMilliseconds;
                _reloadTimer.Elapsed += (s, e) => { Load(); };
            }
    
            public override void Load() {
                try {
                    using (var db = new MyDbContext(_dbOptions)) {
                        var settings = db.Settings.AsNoTracking().ToList();
                        Data.Clear();
    
                        foreach (var s in settings) {
                            Data.Add(s.Name, s.Value);
                        }
                    }
                    OnReload();
                } finally {
                    _reloadTimer.Start();
                }
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      我有同样的要求,我找到了你的代码,我尝试了同样的事情,但没有工作。

      但是你的代码和其他一些调查让我得到了提示。

      public class CustomConfigurationProvider : ConfigurationProvider
      {
          private readonly string applicationName;
          private readonly bool reloadOnChange;
          private readonly IConfiguration configuration;
      
          public CustomConfigurationProvider(string applicationName, bool reloadOnChange)
          {
              this.applicationName = applicationName;
              this.reloadOnChange = reloadOnChange;
      
              if(reloadOnChange)
              {
              ChangeToken.OnChange(
                  () => GetReloadToken(), // listener to token change
                  () =>
                  {
                      Thread.Sleep(250);
                      this.Load();
                  });
              }
          }
      
          public override async void Load()
          {
              Data.Clear();
      
              Data = read data from database;
      
              if (Condition to check if data in database changed)
              {
                  OnReload(); // This will create new token and trigger change so what is register in OnChange above will be called again which is this.Load()
              }
          }
      }
      

      我还推荐了https://www.mikesdotnetting.com/article/301/loading-asp-net-core-mvc-views-from-a-database-or-other-location

      https://docs.microsoft.com/en-us/aspnet/core/fundamentals/primitives/change-tokens

      希望这会有所帮助。

      【讨论】:

      • 是的,谢谢您的回答。我前段时间解决了它,但没有时间写它。是的,我使用了 Ichangetoken,但以更原始的方式。可以说直接用了。
      猜你喜欢
      • 1970-01-01
      • 2022-08-02
      • 2019-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-06
      • 1970-01-01
      相关资源
      最近更新 更多