SoFunction
Updated on 2025-03-04

Net Core global configuration read management method ConfigurationManager

Recently, while learning .Net Core, I found that the ConfigurationManager commonly used in the .Net Framework was killed in Core.

It can also be understood. The configuration files used in Core are all Json, unlike the XML used by Framework, which is understandable if you do not support them for the time being, but after all, global configuration files are quite important. After reading some articles, there are currently 3 solutions.

1. Introduce and expand

This extension library can be obtained directly in Nuget.

See how to use and instructions.NET Core 2.0 migration tips configuration file

The file types and methods to be read are the same as those in the .Net Framework, and you only need to introduce a package. I was very excited in an instant!

but! I found that there was a problem with this extension during use. My file needs to be modified during the project operation, which has no effect on the output content in my project. Debug found that the obtained value did not change. Restarting the project is useless. Only by recompiling the project can it work.

I don't know if it was because my opening method was wrong, but I finally gave up this method.

2. Introduce and expand

This extension library can also be obtained directly in Nuget.

See how to use and instructionsCore implementation class library project reading configuration file

This can read the configuration parameters and no longer use XML, it can be said that it is very close to the design concept of Core.

Unfortunately, this is a bit of a disadvantage. First of all, the content read by modifying the json file during runtime will not change, but at least the restart project can be modified, which makes me feel much more relieved. In addition, this method adopts the principle of deserialization, that is, there must be an entity class corresponding to the configuration file. This feels quite useless, so I give up.

3. Custom extension method

This is the focus of what I said this time. If the first two methods can meet your needs, there is no need to continue reading.

Less nonsense, start with the code:

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 = () + "\\";

    /// <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 for external connections    /// </summary>
    private static string _configUrlPostfix = "Url";

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

    /// <summary>
    /// Configure external link keywords, for example:    /// </summary>
    private static string _configUrlSection { get { return _configSection + "." + _configUrlPostfix; } }


    static ConfigurationManager()
    {
      ConfigFinder(_defaultPath);
    }

    /// <summary>
    /// Determine the configuration file 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;

        (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 configuration file content    /// </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[] = ();
        }
      }
      
      _configurationCollection = configColltion;
    }

    /// <summary>
    /// parse Json file    /// </summary>
    /// <param name="FilePath">FilePath</param>    /// &lt;returns&gt;&lt;/returns&gt;
    private static JObject LoadJsonFile(string FilePath)
    {
      JObject config_object = null;
      try
      {
        StreamReader sr = new StreamReader(FilePath, );
        config_object = (());
        ();
      }
      catch { }
      return config_object;
    }

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

      FileSystemWatcher watcher = new FileSystemWatcher();
      ();
       = ;
       = ;
       = false;
       = true;
       =  |  |  |  |  |  | ;
       += new FileSystemEventHandler(ConfigChangeListener);
      ();

      return new KeyValuePair&lt;string, FileSystemWatcher&gt;(, watcher);
     
    }

    private static void ConfigChangeListener(object sender, FileSystemEventArgs e)
    {
      long time = TimeStamp();
      lock (FileListeners)
      {
        if (time &gt; _timeStamp)
        {
          _timeStamp = time;
          if ( != _configPath ||  == _defaultPath)
          {
            while ( &gt; 0)
            {
              var listener = ();
              ();
              if ( == ) break;
            }
            ConfigFinder();
          }
          else
          {
            LoadConfiguration();
          }
        }
      }
    }

    private static long TimeStamp()
    {
      return (long)(( - new DateTime(1970, 1, 1, 0, 0, 0, )).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; }
    }

    /// &lt;summary&gt;
    /// Manually refresh the configuration. After modifying the configuration, please call this method manually to update the configuration parameters    /// &lt;/summary&gt;
    public static void RefreshConfiguration()
    {
      lock (FileListeners)
      {
        //Modify the 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 ( &gt; 0)
          ().();
        ConfigFinder(_defaultPath);
      }
    }

} 

The initial design was to use cache, and each call compared the file's modification time, size and other characteristics, and changes occurred from the newly loaded configuration. Later I found out that the pattern was broken!

C# provides a method to specifically listen to file systems. Therefore, the listening response chain stack is newly designed to implement it.

Instructions for use:

1. Configure nodes:

It can be written directly in the default configuration file of the project. The format is as follows

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

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

2. External configuration files

Like in the .Net Framework, it can be implemented through external configuration files. The format is as follows

{
  "": "D:\\test\\"
}

The format is in the form of "configure node name.external link suffix". Multi-level external configuration files can be designed. As long as there are external configuration nodes, they will look down and listen for changes in all node files on the chain.

But it is important to note:Once an external configuration node exists, the configuration nodes and parameters in this file will no longer participate in the parsing.

3. Configurable initialization parameters

Multiple parameters including the default file path can be modified, see the code for details.

After modification, the RefreshConfiguration method needs to be called manually to make the configuration content take effect, which is a bit like transaction processing. It is recommended to modify the configuration method in the Startup method of the project.

4. Use

Just like in the .Net Framework, just call ["Title"].

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.