Net Core Global Configuration Read Management Method ConfigurationManager

  • 2021-10-25 06:31:00
  • OfStack

Recently, in the process of learning. Net Core, I found that ConfigurationManager, which is commonly used in. Net Framework, was actually killed in Core.

I can understand. The configuration files used in Core are all Json, unlike XML used in Framework, which is understandable if it is not supported for the time being, but after all, the global configuration file is quite important. After reading some articles, there are currently 3 solutions.

1. Introducing extensions System. Configuration. ConfigurationManager

This extension library can be obtained directly in Nuget.

See the web. config configuration file for. NET Core 2.0 migration tips for usage and instructions

Read the file type and method with. Net Framework 1, and only need to introduce the package can, instant very excited have wood!

But! I found that there was something wrong with this extension during my use. I need to modify my app. config file during the running of the project, which has no effect on the output content of my project. Debug found that the obtained value did not change. It's no use restarting the project. Only by recompiling the project will it work.

I don't know if it was because I opened it in the wrong way, but I finally gave up this method.

2. Introducing extensions Microsoft. Extensions. Options. ConfigurationExtensions

This extension library can also be obtained directly in Nuget.

See ASP. NET Core Implementation Class Library Project Reading Configuration File for usage and description

This can read the configuration parameters in application. json, and it can be said that it is close to the design concept of Core without using XML.

Unfortunately, this is also a bit of a fly in the ointment. First of all, like the one above, the contents read by modifying the json file at runtime will not change, but at least restarting the project can modify it, which makes me happy. In addition, this method uses the principle of deserialization, that is, there must be an entity class corresponding to the configuration file. This feeling is chicken ribs, so give up.

3. Customize the extension method

This is my point this time. If the first two methods can meet your readers' needs, there is no need to read them.

Cut the crap and put on the code first:


public class ConfigurationManager
  {
    /// <summary>
    ///  Configuration content 
    /// </summary>
    private static NameValueCollection _configurationCollection = new NameValueCollection();

    /// <summary>
    ///  Configure the listening response chain stack 
    /// </summary>
    private static Stack<KeyValuePair<string, FileSystemWatcher>> FileListeners = new Stack<KeyValuePair<string, FileSystemWatcher>>();

    /// <summary>
    ///  Default path 
    /// </summary>
    private static string _defaultPath = Directory.GetCurrentDirectory() + "\\appsettings.json";

    /// <summary>
    ///  Final configuration file path 
    /// </summary>
    private static string _configPath = null;

    /// <summary>
    ///  Configure node keywords 
    /// </summary>
    private static string _configSection = "AppSettings";

    /// <summary>
    ///  Configure the suffix of the external connection 
    /// </summary>
    private static string _configUrlPostfix = "Url";

    /// <summary>
    ///  Final modification timestamp 
    /// </summary>
    private static long _timeStamp = 0L;

    /// <summary>
    ///  Configure external chain keywords, such as: AppSettings.Url
    /// </summary>
    private static string _configUrlSection { get { return _configSection + "." + _configUrlPostfix; } }


    static ConfigurationManager()
    {
      ConfigFinder(_defaultPath);
    }

    /// <summary>
    ///  Determine the profile path 
    /// </summary>
    private static void ConfigFinder(string Path)
    {
      _configPath = Path;
      JObject config_json = new JObject();
      while (config_json != null)
      {
        config_json = null;
        FileInfo config_info = new FileInfo(_configPath);
        if (!config_info.Exists) break;

        FileListeners.Push(CreateListener(config_info));
        config_json = LoadJsonFile(_configPath);
        if (config_json[_configUrlSection] != null)
          _configPath = config_json[_configUrlSection].ToString();
        else break;
      }

      if (config_json == null || config_json[_configSection] == null) return;

      LoadConfiguration();
    }

    /// <summary>
    ///  Read the contents of the configuration file 
    /// </summary>
    private static void LoadConfiguration()
    {
      FileInfo config = new FileInfo(_configPath);
      var configColltion = new NameValueCollection();
      JObject config_object = LoadJsonFile(_configPath);
      if (config_object == null || !(config_object is JObject)) return;
      
      if (config_object[_configSection]!=null)
      {
        foreach (JProperty prop in config_object[_configSection])
        {
          configColltion[prop.Name] = prop.Value.ToString();
        }
      }
      
      _configurationCollection = configColltion;
    }

    /// <summary>
    ///  Analyse Json Documents 
    /// </summary>
    /// <param name="FilePath"> File path </param>
    /// <returns></returns>
    private static JObject LoadJsonFile(string FilePath)
    {
      JObject config_object = null;
      try
      {
        StreamReader sr = new StreamReader(FilePath, Encoding.Default);
        config_object = JObject.Parse(sr.ReadToEnd());
        sr.Close();
      }
      catch { }
      return config_object;
    }

    /// <summary>
    ///  Add a listening tree node 
    /// </summary>
    /// <param name="info"></param>
    /// <returns></returns>
    private static KeyValuePair<string, FileSystemWatcher> CreateListener(FileInfo info)
    {

      FileSystemWatcher watcher = new FileSystemWatcher();
      watcher.BeginInit();
      watcher.Path = info.DirectoryName;
      watcher.Filter = info.Name;
      watcher.IncludeSubdirectories = false;
      watcher.EnableRaisingEvents = true;
      watcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size;
      watcher.Changed += new FileSystemEventHandler(ConfigChangeListener);
      watcher.EndInit();

      return new KeyValuePair<string, FileSystemWatcher>(info.FullName, watcher);
     
    }

    private static void ConfigChangeListener(object sender, FileSystemEventArgs e)
    {
      long time = TimeStamp();
      lock (FileListeners)
      {
        if (time > _timeStamp)
        {
          _timeStamp = time;
          if (e.FullPath != _configPath || e.FullPath == _defaultPath)
          {
            while (FileListeners.Count > 0)
            {
              var listener = FileListeners.Pop();
              listener.Value.Dispose();
              if (listener.Key == e.FullPath) break;
            }
            ConfigFinder(e.FullPath);
          }
          else
          {
            LoadConfiguration();
          }
        }
      }
    }

    private static long TimeStamp()
    {
      return (long)((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds * 100);
    }

    private static string c_configSection = null;
    public static string ConfigSection
    {
      get { return _configSection; }
      set { c_configSection = value; }
    }


    private static string c_configUrlPostfix = null;
    public static string ConfigUrlPostfix
    {
      get { return _configUrlPostfix; }
      set { c_configUrlPostfix = value; }
    }

    private static string c_defaultPath = null;
    public static string DefaultPath
    {
      get { return _defaultPath; }
      set { c_defaultPath = value; }
    }

    public static NameValueCollection AppSettings
    {
      get { return _configurationCollection; }
    }

    /// <summary>
    ///  Refresh the configuration manually. After modifying the configuration, call this method manually to update the configuration parameters 
    /// </summary>
    public static void RefreshConfiguration()
    {
      lock (FileListeners)
      {
        // Modify configuration 
        if (c_configSection != null) { _configSection = c_configSection; c_configSection = null; }
        if (c_configUrlPostfix != null) { _configUrlPostfix = c_configUrlPostfix; c_configUrlPostfix = null; }
        if (c_defaultPath != null) { _defaultPath = c_defaultPath; c_defaultPath = null; }
        // Release all listening response chains 
        while (FileListeners.Count > 0)
          FileListeners.Pop().Value.Dispose();
        ConfigFinder(_defaultPath);
      }
    }

} 

The initial design is to use cache, and compare the modification time, size and other characteristics of the file every time, and load the configuration again when there is a change. Later, it was found that the pattern Tucson was broken!

C # provides a special way to listen to the file system. Therefore, the listening response chain stack is redesigned to realize it.

Instructions for use:

1. Configure nodes:

It can be written directly to the project's default configuration file appsettings. json in the following format


{
 "AppSettings": {
  "Title": "Test",
  "Version": "1.2.1",
  "AccessToken": "123456@abc.com"
 }
}

Ensure that the configuration node AppSettings exists, and the rest is to write attributes in the form of Key-Value.

2. External Profile

Like 1 in. Net Framework, it can be implemented through an external configuration file. The format is as follows


{
  "AppSettings.Url": "D:\\test\\app1.json"
}

The format is "Configure node name. Outer chain suffix". You can design multiple levels of external configuration files and look down whenever an external configuration node is found and listen for changes in all node files on the chain.

However, it should be noted that: 1 Once an external configuration node exists, the configuration nodes and parameters in this file will no longer participate in parsing

3. Configurable initialization parameters

Many parameters, including the default file path, can be modified. See the code for details.

The modification requires a manual call to the RefreshConfiguration method to make the configuration work, a bit like a transaction. It is recommended to modify the configuration method in the Startup method of the project.

4. Use

Just call ConfigurationManager. Appsettings ["Title"], just like 1 in. Net Framework.


Related articles: