寫在前面
上一篇文章討論了檔案型配置的基本內容,本篇內容討論JSON型配置的實現方式,理解了這一種配置型別的實現方式,那麼其他型別的配置實現方式基本可以觸類旁通。看過了上一篇文章的朋友,應該看得出來似曾相識。此圖主要表達了檔案型配置的實現,當然其他配置,包括自定義配置,都會按照這樣的方式去實現。
JSON配置元件的相關內容
該元件有四個類
- JsonConfigurationExtensions
- JsonConfigurationSource
- JsonConfigurationFileParser
- JsonConfigurationProvider
這四個類相互合作,職責明確,共同將JSON型別的配置載入到記憶體中,供相應的系統使用。
JsonConfigurationFileParser
該類是一個內部類,擁有一個私有的構造方法,意味著該類無法在其他地方進行例項化,只能在自己內部使用。它只有一個公共方法,並且是靜態的,如下所示
1: public static IDictionary<string, string> Parse(Stream input) => new JsonConfigurationFileParser().ParseStream(input);
該方法通過讀取輸入資料流,將其轉化為字典型別的配置資料,該字典型別是SortedDictionary型別的,並且不區分大小寫,此處需要注意。這也呼應了之前所說的.NET Core Configuration對外使用的時候,都是以字典方式去提供給外界使用的。
那麼,這個類是如何將資料流轉化為JSON的呢,我們繼續閱讀原始碼
1: private IDictionary<string, string> ParseStream(Stream input)
2: {
3: _data.Clear();
4:
5: using (var reader = new StreamReader(input))
6: using (JsonDocument doc = JsonDocument.Parse(reader.ReadToEnd(), new JsonReaderOptions { CommentHandling = JsonCommentHandling.Skip }))
7: {
8: if (doc.RootElement.Type != JsonValueType.Object)
9: {
10: throw new FormatException(Resources.FormatError_UnsupportedJSONToken(doc.RootElement.Type));
11: }
12: VisitElement(doc.RootElement);
13: }
14:
15: return _data;
16: }
通過原始碼,我們知道,此處使用了JsonDocument處理StreamReader資料,JsonDocument又是什麼呢,通過名稱空間我們知道,它位於System.Text.Json中,這是.NET Core原生的處理JSON的元件,有興趣的朋友可以去翻翻MSDN或者GitHub查詢相關資料。此處不做過多說明。
VisitElement方法主要是遍歷JsonElement.EnumerateObject()方法中的物件集合,此處採用Stack<string>例項進行資料安全方面的控制。其中VisitValue是一個在處理json時相當全面的方法,說它全面是因為它考慮到了JSON值的幾乎所有型別:
- JsonValueType.Object
- JsonValueType.Array
- JsonValueType.Number
- JsonValueType.String
- JsonValueType.True
- JsonValueType.False
- JsonValueType.Null
當然,該方法,並不會很傻的處理每一種型別,主要是針對Object和Array型別進行了遞迴遍歷,以便在諸如Number、String等的簡單型別時跳出遞迴,並存放到字典中,需要再次強調的是,存放在字典中的值是以String型別儲存的。
至此,JsonConfigurationFileParser完成了從檔案讀取內容並轉化為鍵值對的工作。
JsonConfigurationSource
這個類比較簡單,因為繼承自FileConfigurationSource,如前文所說,FileConfigurationSource類已經做了初步的實現,只提供了一個Build方法交給子類去重寫。其返回值是JsonConfigurationProvider例項。
1: /// <summary>
2: /// Represents a JSON file as an <see cref="IConfigurationSource"/>.
3: /// </summary>
4: public class JsonConfigurationSource : FileConfigurationSource
5: {
6: /// <summary>
7: /// Builds the <see cref="JsonConfigurationProvider"/> for this source.
8: /// </summary>
9: /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
10: /// <returns>A <see cref="JsonConfigurationProvider"/></returns>
11: public override IConfigurationProvider Build(IConfigurationBuilder builder)
12: {
13: EnsureDefaults(builder);
14: return new JsonConfigurationProvider(this);
15: }
16: }
此處的EnsureDefaults()方法,主要是設定FileProvider例項以及指定載入型別的異常處理方式。
JsonConfigurationProvider
這個類也很簡單,它繼承於FileConfigurationProvider,FileConfigurationProvider本身也已經通用功能進行了抽象實現,先看一下這個類的原始碼
1: /// <summary>
2: /// A JSON file based <see cref="FileConfigurationProvider"/>.
3: /// </summary>
4: public class JsonConfigurationProvider : FileConfigurationProvider
5: {
6: /// <summary>
7: /// Initializes a new instance with the specified source.
8: /// </summary>
9: /// <param name="source">The source settings.</param>
10: public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }
11:
12: /// <summary>
13: /// Loads the JSON data from a stream.
14: /// </summary>
15: /// <param name="stream">The stream to read.</param>
16: public override void Load(Stream stream)
17: {
18: try {
19: Data = JsonConfigurationFileParser.Parse(stream);
20: } catch (JsonReaderException e)
21: {
22: throw new FormatException(Resources.Error_JSONParseError, e);
23: }
24: }
25: }
其建構函式的傳入引數型別是JsonConfigurationSource,這和JsonConfigurationSource.Build()方法中的return new JsonConfigurationProvider(this)程式碼片段相呼應。
JsonConfigurationProvider所重寫的方法,呼叫的是JsonConfigurationFileParser.Parse(stream)方法,所以該類顯得非常的輕量。
JsonConfigurationExtensions
這個方法,大家就更熟悉了,我們平時所使用的AddJsonFile()方法,就是在這個擴充套件類中進行擴充套件的,其返回值是IConfigurationBuilder型別,其核心方法原始碼如下所示
1: /// <summary>
2: /// Adds a JSON configuration source to <paramref name="builder"/>.
3: /// </summary>
4: /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
5: /// <param name="provider">The <see cref="IFileProvider"/> to use to access the file.</param>
6: /// <param name="path">Path relative to the base path stored in
7: /// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>
8: /// <param name="optional">Whether the file is optional.</param>
9: /// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>
10: /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
11: public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange)
12: {
13: if (builder == null)
14: {
15: throw new ArgumentNullException(nameof(builder));
16: }
17: if (string.IsNullOrEmpty(path))
18: {
19: throw new ArgumentException(Resources.Error_InvalidFilePath, nameof(path));
20: }
21:
22: return builder.AddJsonFile(s =>
23: {
24: s.FileProvider = provider;
25: s.Path = path;
26: s.Optional = optional;
27: s.ReloadOnChange = reloadOnChange;
28: s.ResolveFileProvider();
29: });
30: }
不過,大家不要看這個方法的程式碼行數很多,就認為,其他方法都過載與該方法,其實該方法過載自
1: /// <summary>
2: /// Adds a JSON configuration source to <paramref name="builder"/>.
3: /// </summary>
4: /// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
5: /// <param name="configureSource">Configures the source.</param>
6: /// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
7: public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
8: => builder.Add(configureSource);
這個方法最終呼叫的還是IConfigurationBuilder.Add()方法
總結
通過介紹以上JSON Configuration元件的四個類,我們知道了,該元件針對JSON格式的檔案的處理方式,不過由於其實檔案型配置,其抽象實現已經在檔案型配置擴充套件實現。
從這裡,我們可以學習一下,如果有一天我們需要擴充套件遠端配置,比如Consul、ZK等,我們也可以考慮並採用這種架構的設計方式。另外在JSON Configuration元件中,.NET Core將專有型功能方法的處理進行了聚合,並聚焦關注點的方式也值得我們學習。
最後JsonConfigurationFileParser中給了我們一種關於Stream轉換成JSON的實現,我們完全可以把這個類當成工具類去使用。