Analysis of Automatic Update of Json Configuration in Net Core

Time:2019-8-3

Pre

I learned from Jesse’s ASP.net Core Quick Start course that in Asp.net core, if the added Json configuration is changed, it supports automatic overload configuration. As a programmer with a serious “wheeled” scenario, he recently tried to create a blog system that could update automatically. With Mysql as the data source of ConfigureSource, we clicked on the source code of AddJsonFile, which is an extension function. It’s interesting to find out that there is something else. This article simply talks about how Json config’s Reload OnChange is implemented. In the process of learning Reload OnChange, we will also take Configuration by the way:grin:, hope: Help your little buddies.


public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
  WebHost.CreateDefaultBuilder(args)
  .ConfigureAppConfiguration(option =>
   {
   option.AddJsonFile("appsettings.json",optional:true,reloadOnChange:true);
   })
  .UseStartup<Startup>();

If you configure the JSON data source in Asp.net core and set the reloadOnChange property to true, you can automatically update the configuration when the file changes. First of all, let’s take a brief look at the source code of this blog. After reading it, you may still feel a little confused. Don’t panic. I will simplify these codes and give a simple example. Hope It can help you.

A glimpse of the source code

AddJson

First of all, of course, we start with this well-known extension function, which undergoes the following evolution process.


 public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,string path,bool optional,bool reloadOnChange)
 {
 return builder.AddJsonFile((IFileProvider) null, path, optional, reloadOnChange);
 }

Pass a null FileProvider to another overloaded Addjson function.

Knock on the blackboard, Null’s FileProvider is very important.


 public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,IFileProvider provider,string path,bool optional,bool reloadOnChange)
 {
  return builder.AddJsonFile((Action<JsonConfigurationSource>) (s =>
  {
  s.FileProvider = provider;
  s.Path = path;
  s.Optional = optional;
  s.ReloadOnChange = reloadOnChange;
  s.ResolveFileProvider();
  }));
 }

Evolving the incoming parameters into an Action delegateJsonConfigurationSourceAttribute assignment.


 public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
 {
  return builder.Add<JsonConfigurationSource>(configureSource);
 }

Builder. add for the final call(action) method.


 public static IConfigurationBuilder Add<TSource>(this IConfigurationBuilder builder,Action<TSource> configureSource)where TSource : IConfigurationSource, new()
 {
  TSource source = new TSource();
  if (configureSource != null)
  configureSource(source);
  return builder.Add((IConfigurationSource) source);
 }

In the Add method, we create a Source instance, which is the Json Configuration Source instance, and then pass this instance to the delegate just now, so that we pass in the outermost"appsettings.json",optional:true,reloadOnChange:trueThe parameters work for this example.

Ultimately, this instance is added to builder. So what is builder? What can it do?

ConfigurationBuild

The builder mentioned earlier defaults to beConfigurationBuilderI simplified it. The key code is as follows.


public class ConfigurationBuilder : IConfigurationBuilder
 {
  public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();

  public IConfigurationBuilder Add(IConfigurationSource source)
  {
   Sources.Add(source);
   return this;
  }

  public IConfigurationRoot Build()
  {
   var providers = new List<IConfigurationProvider>();
   foreach (var source in Sources)
   {
    var provider = source.Build(this);
    providers.Add(provider);
   }
   return new ConfigurationRoot(providers);
  }
 }

As you can see, this builder has a collection type of Sources that can save any implementationIConfigurationSourceSource, what I talked about earlierJsonConfigurationSourceIt’s the interface that’s implemented, and it’s often used.MemoryConfigurationSource , XmlConfigureSource , CommandLineConfigurationSourceAnd so on.

In addition, it has a very important build method, which is in theWebHostBuilderMethod executionbuildWhen it’s called, don’t ask meWebHostBuilder.builderWhat does the method perform: joy:.


public static void Main(string[] args)
  {
   CreateWebHostBuilder(args).Build().Run();
  }

In the ConfigureBuilder method, each Source’s Builder method is called, and the one we just passed in isJsonConfigurationSourceSo we need to see what JsonSource builder does.

Are you crying around these builders? Don’t panic. In the next article, I’ll explain how to customize a ConfigureSoure. I’ll sort out the UML class diagrams of Congigure series, which should be much clearer.

JsonConfigurationSource


public class JsonConfigurationSource : FileConfigurationSource
 {
  public override IConfigurationProvider Build(IConfigurationBuilder builder)
  {
   EnsureDefaults(builder);
   return new JsonConfigurationProvider(this);
  }
 }

This is it.JsonConfigurationSourceAll the code is not streamlined, it only implements a Build method. In Build, EnsureDefaults is called. Don’t underestimate it. The empty FileProvider was assigned here.


 public void EnsureDefaults(IConfigurationBuilder builder)
  {
   FileProvider = FileProvider ?? builder.GetFileProvider();
  }
  public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
  {
   return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
  }

You can see that the FileProvider defaults toPhysicalFileProviderWhy?FileProviderSo lucky that I spent so much foreshadowing to emphasize it? Look down.

JsonConfigurationProvider && FileConfigurationProvider

In the build method of Json Configuration Source, an instance of Json Configuration Provider is returned, so my intuition tells me that there must be a nightmare in its constructor: confused:.


 public class JsonConfigurationProvider : FileConfigurationProvider
 {
  
  public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }

  
  public override void Load(Stream stream)
  {
   try {
    Data = JsonConfigurationFileParser.Parse(stream);
   } catch (JsonReaderException e)
   {
    throw new FormatException(Resources.Error_JSONParseError, e);
   }
  }
 }

If something goes wrong, there must be a demon.~~

Look at the constructor of base.


 public FileConfigurationProvider(FileConfigurationSource source)
  {
   Source = source;

   if (Source.ReloadOnChange && Source.FileProvider != null)
   {
    _changeTokenRegistration = ChangeToken.OnChange(
     () => Source.FileProvider.Watch(Source.Path),
     () => {
      Thread.Sleep(Source.ReloadDelay);
      Load(reload: true);
     });
   }
  }

What a genius, the problem is that in this constructor, it calls oneChangeToken.OnChangeMethod, that’s the key to Reload OnChange. If you don’t turn it off at this point, congratulations, the fun begins.

ReloadOnChange

Talk is cheap. Show me the code.codeCome here).


 public static class ChangeToken
 {
  public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
  {
   return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
  }
 }

In OnChange method, no matter what func, action, look at the names of these two parameters, producer, consumer, producer, consumer. I don’t know what I think of when I see this key word. Anyway, I think of snake: and rat: when I study food chain in elementary school.

So let’s take a look at this: snake: what is it, rat: what is it, and we have to go back.FileConfigurationProviderConstructor.

You can see the producer: rat: Yes:


() => Source.FileProvider.Watch(Source.Path)

Consumer: snake: Yes:


() => {
 Thread.Sleep(Source.ReloadDelay);
 Load(reload: true);
}

Let’s think about this: once rat: runs out, it’s snake: eaten.

So we do here. Once FileProvider. Watch returns something, a Load () event occurs to reload the data.

Snake: and: rat: easy to understand, but the code is not so easy to understand, we passedOnChangeFirst parameterFunc<IChangeToken> changeTokenProducerThe way you know, here’s rat:, actually.IChangeToken .

IChangeToken


 public interface IChangeToken
 {
  bool HasChanged { get; }

  bool ActiveChangeCallbacks { get; }

  IDisposable RegisterChangeCallback(Action<object> callback, object state);
 }

IChangeToken’s focus is on the RegisterChangeCallback method, snake: eat: rat: This happens in this callback method.

Let’s do a snake: eat: rat: experiment.

Experiment 1

static void Main()
  {
   // Define a FileProvider for the directory C: Users liuzh MyBox TestSpace
   var phyFileProvider = new PhysicalFileProvider("C:\Users\liuzh\MyBox\TestSpace");

   // Let the Provider start listening for all files in this directory
   var changeToken = phyFileProvider.Watch("*.*");

   // Registration (Eat) (This matter to callback function
   ChangeeToken. RegisterChangeCallback (=> {Console. WriteLine ("Rats are eaten by snakes"); new object ()};

   // Add a file to the directory
   AddFileToPath();

   Console.ReadKey();

  }

  static void AddFileToPath()
  {
   Console. WriteLine.
   File. Create ("C: Users liuzh MyBox TestSpace TestSpace Rats out of the hole. txt").
  }

This is the result of operation.

As you can see, once a file is created in the monitored directory, the callback function is triggered immediately, but if we continue to manually change (copy) the files in the monitored directory, the callback function will no longer execute.

This is because after changeToken listens to file changes and triggers the callback function, the changeToken’s mission is accomplished. To keep listening, we retrieve token from the callback function and register common events for the new token callback function, so that we can keep listening all the time.

That’s what ChangeToken. Onchange does. Let’s look at the source code.


 public static class ChangeToken
 {
  public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
  {
   return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
  }
 }
 public class ChangeTokenRegistration<TAction>
 {
  private readonly Func<IChangeToken> _changeTokenProducer;
  private readonly Action<TAction> _changeTokenConsumer;
  private readonly TAction _state;

  public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TAction> changeTokenConsumer, TAction state)
  {
   _changeTokenProducer = changeTokenProducer;
   _changeTokenConsumer = changeTokenConsumer;
   _state = state;

   var token = changeTokenProducer();

   RegisterChangeTokenCallback(token);
  }

  private void RegisterChangeTokenCallback(IChangeToken token)
  {
   token.RegisterChangeCallback(_ => OnChangeTokenFired(), this);
  }

  private void OnChangeTokenFired()
  {
   var token = _changeTokenProducer();

   try
   {
    _changeTokenConsumer(_state);
   }
   finally
   {
    // We always want to ensure the callback is registered
    RegisterChangeTokenCallback(token);
   }
  }
 }

Simply put, it registers a token.OnChangeTokenFiredCallback function, look carefullyOnChangeTokenFiredWhat has been done here, in general, is three steps.

1. Get a new token.
2. Call consumers to consume.
3. Register an OnChangeTokenFired callback function for the newly acquired token.

So round and round~~

Experiment 2

Now that we know how OnChange works, let’s modify the code of Experiment 1.

static void Main()
  {
   var phyFileProvider = new PhysicalFileProvider("C:\Users\liuzh\MyBox\TestSpace");
   ChangeToken.OnChange(() => phyFileProvider.Watch("*.*"),
    () => {Console.WriteLine ("Rats are eaten by snakes"); and {Console.WriteLine ("Rats are eaten by snakes");
   Console.ReadKey();
  }

Take a look at the execution effect

As you can see, whenever the monitored directory changes a file, whether it is a new file or changes the contents of the file, a callback function will be triggered. In fact, in JsonConfig, this callback function is Load (), which is responsible for reloading data. That is why in Asp. net core, if ReloadOnchange is set to true, Json Config will trigger a callback function. Once the configuration is updated, the configuration is automatically overloaded.

PhysicalFilesWatcher

So why does a file trigger a callback function for ChangeToken once it changes?PhysicalFileProviderCalled inPhysicalFilesWatcherMonitoring the file system and observing the constructor of Physical Files Watcher, you can see thatPhysicalFilesWatcherNeed to pass inFileSystemWatcher , FileSystemWatcheryessystem.ioThe underlying IO class, in the constructor, registers EventHandler events for the Watcher’s Created, Changed, Renamed, Deleted. EventHandler eventually calls ChangToken’s callback function in these EventHandlers, so the callback function will be triggered whenever the file system changes.


 public PhysicalFilesWatcher(string root,FileSystemWatcher fileSystemWatcher,bool pollForChanges,ExclusionFilters filters)
 {
  this._root = root;
  this._fileWatcher = fileSystemWatcher;
  this._fileWatcher.IncludeSubdirectories = true;
  this._fileWatcher.Created += new FileSystemEventHandler(this.OnChanged);
  this._fileWatcher.Changed += new FileSystemEventHandler(this.OnChanged);
  this._fileWatcher.Renamed += new RenamedEventHandler(this.OnRenamed);
  this._fileWatcher.Deleted += new FileSystemEventHandler(this.OnChanged);
  this._fileWatcher.Error += new ErrorEventHandler(this.OnError);
  this.PollForChanges = pollForChanges;
  this._filters = filters;
  this.PollingChangeTokens = new ConcurrentDictionary<IPollingChangeToken, IPollingChangeToken>();
  this._timerFactory = (Func<Timer>) (() => NonCapturingTimer.Create(new TimerCallback(PhysicalFilesWatcher.RaiseChangeEvents), (object) this.PollingChangeTokens, TimeSpan.Zero, PhysicalFilesWatcher.DefaultPollingInterval));
 }

If you are as interested in source code as I am, you can get it from the official source code.aspnet/ExtensionsResearch on Download Source: https://github.com/aspnet/Extensions

In the next article, I will explain how to customize a ConfigureSoure with Mysql as its data source and implement automatic updates. I will also organize UML class diagrams of Configurerelated classes. If you are interested, you can pay attention to me and receive the next article as soon as possible.

The code address covered in this article: https://github.com/liuzhenyulive/MiniConfiguration

The above is the whole content of this article. I hope it will be helpful to everyone’s study, and I hope you will support developpaer more.