.NET Core 3.0之深入原始碼理解Configuration(二)

艾心❤發表於2019-05-19
 

檔案型配置基本內容

上一篇文章討論了Configuration的幾個核心物件,本文繼續討論Configuration中關於檔案型配置的相關內容。相比較而言,檔案型配置的使用場景更加廣泛,使用者自定義配置擴充套件也可以基於檔案型配置進行擴充套件。如果需要檢視上一篇文章,可以點選移步

.NET Core檔案型配置中我們提供了三種主要的實現,分別是JSON、XML、INI,請檢視下圖

file2

由圖可知,這三種配置的實現方式是一樣的,當然了其他的配置比如命令列配置、環境變數配置等也是大同小異,理解了改配置型別的實現方式,後面我們再擴充套件基於Consul或者ZK的實現,就非常簡單了。

檔案型配置的抽象擴充套件

檔案型配置的抽象擴充套件位於Microsoft.Extensions.Configuration.FileExtensions元件中,該擴充套件是一個基礎實現。不過其名稱空間是Microsoft.Extensions.Configuration,而Micros oft.Extensions.Configuration擴建本身又是整個.NET Core Configuration的基礎實現。將File擴充套件獨立於外部,體驗了.NET Core的模組化設計。

FileConfigurationSource

Configuration.FileExtensions元件中,FileConfigurationSource是繼承於IConfigurationSource的一個抽象類,包含了一個IConfigurationProvider型別的抽象方法,如下所示

   1:  /// <summary>
   2:  /// Builds the <see cref="IConfigurationProvider"/> for this source.
   3:  /// </summary>
   4:  /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
   5:  /// <returns>A <see cref="IConfigurationProvider"/></returns>
   6:  public abstract IConfigurationProvider Build(IConfigurationBuilder builder);

該抽象類中還包括了幾個比較重要的引數,分別用於配置性行為、檔案內容訪問以及異常處理。

string Path:檔案的路徑

bool Optional:標識載入的檔案是否是可選的

bool ReloadOnChange:如果檔案發生修改,是否重新載入配置源

int ReloadDelay:載入延遲,單位是毫秒,預設是250毫秒

IFileProvider FileProvider:用於獲取檔案內容

Action<FileLoadExceptionContext> OnLoadException:檔案載入異常處理

該類對FileProvider有特殊處理,就是如果沒有提供FileProvider例項,則會基於絕對路徑,在最近的現有目錄中建立物理檔案提供程式。原始碼如下,

   1:  /// <summary>
   2:  /// If no file provider has been set, for absolute Path, this will creates a physical file provider 
   3:  /// for the nearest existing directory.
   4:  /// </summary>
   5:  public void ResolveFileProvider()
   6:  {
   7:      if (FileProvider == null && 
   8:          !string.IsNullOrEmpty(Path) &&
   9:          System.IO.Path.IsPathRooted(Path))
  10:      {
  11:      
var directory = System.IO.Path.GetDirectoryName(Path);
  12:          var pathToFile = System.IO.Path.GetFileName(Path);
  13:          while (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
  14:          {
  15:              pathToFile = System.IO.Path.Combine(System.IO.Path.GetFileName(directory), pathToFile);
  16:              directory = System.IO.Path.GetDirectoryName(directory);
  17:          }
  18:          if (Directory.Exists(directory))
  19:          {
  20:              FileProvider = new PhysicalFileProvider(directory);
  21:              Path = pathToFile;
  22:          }
  23:      }
  24:  }

FileConfigurationProvider

該類是繼承於ConfigurationProvider的抽象類,是從檔案系統載入配置的基類,同時還繼承了IDisposable,其抽象方法是Load方法,用於從當前的Provider中以Stream方式載入資料

   1:  /// <summary>
   2:  /// Loads this provider's data from a stream.
   3:  /// </summary>
   4:  /// <param name="stream">The stream to read.</param>
   5:  public abstract void Load(Stream stream);

該類還重寫了ConfigurationProvider的Load方法,並對檔案載入中的異常做了處理,Data屬性在前文有提到過,此處不再做其他說明。方法原始碼如下所示:

   1:  private void Load(bool reload)
   2:  {
   3:      var file = Source.FileProvider?.GetFileInfo(Source.Path);
   4:      if (file == null || !file.Exists)
   5:      {
   6:          if (Source.Optional || reload) // Always optional on reload
   7:          {
   8:              Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
   9:          }
  10:          else
  11:          {
  12:              var error = new StringBuilder($"The configuration file '{Source.Path}' was not found and is not optional.");
  13:              if (!string.IsNullOrEmpty(file?.PhysicalPath))
  14:              {
  15:                  error.Append($" The physical path is '{file.PhysicalPath}'.");
  16:              }
  17:              HandleException(new FileNotFoundException(error.ToString()));
  18:          }
  19:      }
  20:      else
  21:      {
  22:          // Always create new Data on reload to drop old keys
  23:          if (reload)
  24:          {
  25:              Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  26:          }
  27:          using (var stream = file.CreateReadStream())
  28:          {
  29:              try
  30:              {
  31:                  Load(stream);
  32:              }
  33:              catch (Exception e)
  34:              {
  35:                  HandleException(e);
  36:              }
  37:          }
  38:      }
  39:      // REVIEW: Should we raise this in the base as well / instead?,通過註釋,我們可以知道OnReload()方法可能會在新版中發生變化
  40:      OnReload();
  41:  }
  42:   
  43:  /// <summary>
  44:  /// Loads the contents of the file at <see cref="Path"/>.
  45:  /// </summary>
  46:  /// <exception cref="FileNotFoundException">If Optional is <c>false</c> on the source and a
  47:  /// file does not exist at specified Path.</exception>
  48:  public override void Load()
  49:  {
  50:      Load(reload: false);
  51:  }

另外它還有一個特殊方法,就是引數型別為FileConfigurationSource的建構函式,其主要功能是監控檔案,並在FileConfigurationSource.ReloadDelay設定的時間裡重新載入檔案並返回一個IDisposable型別的值,以下是該建構函式的原始碼:

   1:  /// <summary>
   2:  /// Initializes a new instance with the specified source.
   3:  /// </summary>
   4:  /// <param name="source">The source settings.</param>
   5:  public FileConfigurationProvider(FileConfigurationSource source)
   6:  {
   7:      if (source == null)
   8:      {
   9:          throw new ArgumentNullException(nameof(source));
  10:      }
  11:      Source = source;
  12:   
  13:      if (Source.ReloadOnChange && Source.FileProvider != null)
  14:      {
  15:          _changeTokenRegistration = ChangeToken.OnChange(
  16:              () => Source.FileProvider.Watch(Source.Path),
  17:              () => {
  18:                  Thread.Sleep(Source.ReloadDelay);
  19:                  Load(reload: true);
  20:              });
  21:      }
  22:  }

FileConfigurationExtensions

該類是一個靜態類,其提供了的多個擴充套件方法,主要基於

  • IConfigurationBuilder
  • IFileProvider
  • Action<FileLoadExceptionContext>

包括主要用於設定或獲取IFileProvider物件,前文有介紹過,是儲存於字典之中,需要注意的是,在Get的時候如果字典中並不存在IFileProvider物件,則會例項化一個PhysicalFileProvider物件出來,該類位於Microsoft.Extensions.FileProviders.PhysicalFileProvider

   1:  /// <summary>
   2:  /// Sets the default <see cref="IFileProvider"/> to be used for file-based providers.
   3:  /// </summary>
   4:  /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
   5:  /// <param name="fileProvider">The default file provider instance.</param>
   6:  /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
   7:  public static IConfigurationBuilder SetFileProvider(this IConfigurationBuilder builder, IFileProvider fileProvider)
   8:  {
   9:      if (builder == null)
  10:      {
  11:          throw new ArgumentNullException(nameof(builder));
  12:      }
  13:   
  14:      builder.Properties[FileProviderKey] = fileProvider ?? throw new ArgumentNullException(nameof(fileProvider));
  15:      return builder;
  16:  }
  17:   
  18:  /// <summary>
  19:  /// Gets the default <see cref="IFileProvider"/> to be used for file-based providers.
  20:  /// </summary>
  21:  /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
  22:  /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
  23:  public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
  24:  {
  25:      if (builder == null)
  26:      {
  27:          throw new ArgumentNullException(nameof(builder));
  28:      }
  29:   
  30:      if (builder.Properties.TryGetValue(FileProviderKey, out object provider))
  31:      {
  32:          return provider as IFileProvider;
  33:      }
  34:   
  35:      return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
  36:  }

為指定路徑的物理檔案設定檔案型Provider,該方法同樣基於PhysicalFileProvider,並返回IConfigurationBuilder物件

   1:  /// <summary>
   2:  /// Sets the FileProvider for file-based providers to a PhysicalFileProvider with the base path.
   3:  /// </summary>
   4:  /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
   5:  /// <param name="basePath">The absolute path of file-based providers.</param>
   6:  /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
   7:  public static IConfigurationBuilder SetBasePath(this IConfigurationBuilder builder, string basePath)
   8:  {
   9:      if (builder == null)
  10:      {
  11:          throw new ArgumentNullException(nameof(builder));
  12:      }
  13:   
  14:      if (basePath == null)
  15:      {
  16:          throw new ArgumentNullException(nameof(basePath));
  17:      }
  18:   
  19:      return builder.SetFileProvider(new PhysicalFileProvider(basePath));
  20:  }

以及異常處理,可以看到其異常處理也會存放於字典中,如果字典中找不到,就會返回空,這個地方如果直接使用,需要注意空指標問題。

   1:  /// <summary>
   2:  /// Sets a default action to be invoked for file-based providers when an error occurs.
   3:  /// </summary>
   4:  /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
   5:  /// <param name="handler">The Action to be invoked on a file load exception.</param>
   6:  /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
   7:  public static IConfigurationBuilder SetFileLoadExceptionHandler(this IConfigurationBuilder builder, Action<FileLoadExceptionContext> handler)
   8:  {
   9:      if (builder == null)
  10:      {
  11:          throw new ArgumentNullException(nameof(builder));
  12:      }
  13:   
  14:      builder.Properties[FileLoadExceptionHandlerKey] = handler;
  15:      return builder;
  16:  }
  17:   
  18:  /// <summary>
  19:  /// Gets the default <see cref="IFileProvider"/> to be used for file-based providers.
  20:  /// </summary>
  21:  /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
  22:  /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
  23:  public static Action<FileLoadExceptionContext> GetFileLoadExceptionHandler(this IConfigurationBuilder builder)
  24:  {
  25:      if (builder == null)
  26:      {
  27:          throw new ArgumentNullException(nameof(builder));
  28:      }
  29:   
  30:      if (builder.Properties.TryGetValue(FileLoadExceptionHandlerKey, out object handler))
  31:      {
  32:          return handler as Action<FileLoadExceptionContext>;
  33:      }
  34:      
return null;
  35:  }

該類還有兩個靜態私有變數,指定了檔案Provider的Key以及檔案載入異常處理Key。

   1:  private static string FileProviderKey = "FileProvider";
   2:  private static string FileLoadExceptionHandlerKey = "FileLoadExceptionHandler";

總結

檔案型配置還依賴於.NET Core的其他元件Microsoft.Extensions.FileProviders和Microsoft.Extensions.Primitives。

FileProviders元件提供了檔案處理的一般方法,Primitives元件提供了監控機制,同時還包括兩個比較重要的結構體StringValues和StringSegment,本文暫時不做討論,有興趣的朋友,可以自行檢視該元件原始碼。

相關文章