實現自己的.NET Core配置Provider之Yaml
阿新 • • 發佈:2018-12-30
YAML是一種更適合人閱讀的檔案格式,很多大型的專案像Ruby on Rails都選擇YAML作為配置檔案的格式。如果專案的配置很少,用JSON或YAML沒有多大差別。看看rails專案中的配置檔案,如果用JSON寫試試什麼感受吧。
YamlConfigurationProvider
Yaml是基於檔案的,可以直接從FileConfigurationProvider
繼承,在FileConfigurationProvider實現了監控檔案變化並自動重新載入的功能。
internal class YamlConfigurationProvider : FileConfigurationProvider { public YamlConfigurationProvider(FileConfigurationSource source) : base(source) { } public override void Load(Stream stream) { var parser = new YamlConfigurationFileParser(); Data = parser.Parse(stream); } }
YamlConfigurationParser
是解析Yaml檔案的核心,後面會介紹。
YamlConfigurationSource
internal class YamlConfigurationSource : FileConfigurationSource { public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new YamlConfigurationProvider(this); } }
YamlConfigurationSource
實現父類的Build方法,返回YamlConfigurationProvider
。
AddYamlFile擴充套件方法
為新增Yaml配置源增加擴充套件方法。
public static class YamlConfigurationExtensions { public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, string path) { return AddYamlFile(builder, provider: null, path: path, optional: false, reloadOnChange: false); } public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, string path, bool optional) { return AddYamlFile(builder, provider: null, path: path, optional: optional, reloadOnChange: false); } public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange) { return AddYamlFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange); } public static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (string.IsNullOrEmpty(path)) { throw new ArgumentException(Resources.Error_InvalidFilePath, nameof(path)); } return builder.AddYamlFile(s => { s.FileProvider = provider; s.Path = path; s.Optional = optional; s.ReloadOnChange = reloadOnChange; s.ResolveFileProvider(); }); } internal static IConfigurationBuilder AddYamlFile(this IConfigurationBuilder builder, Action<YamlConfigurationSource> configureSource) { var source = new YamlConfigurationSource(); configureSource(source); return builder.Add(source); } }
YamlConfigurationFileParser
解析Yaml是核心的功能,目前github有開源的C# Yaml專案:YamlDotNet和SharpYaml 。SharpYaml Fork自YamlDotNet,但做了不少改進並支援Yaml1.2,不過需要netstandard1.6+。YamlDotNet支援Yaml1.1,需要netstandard1.3+。我選擇的YamlSharp。
Yaml可表示三種類型的資料:Scalar(標量,如字串、布林值、整數等)、Sequence(序列,如陣列)和Mapping(對映,如字典,鍵值對等)。
關於Yaml可以參考阮一峰老師的《YAML 語言教程》。
SharpYaml會把Yaml檔案轉換為樹形結構,然後我們只需要把所有的葉子節點的路徑作為字典的鍵,將葉子節點的值作為字典的值儲存起來就可以了。
internal class YamlConfigurationFileParser
{
private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.Ordinal);
private readonly Stack<string> _context = new Stack<string>();
private string _currentPath;
public IDictionary<string, string> Parse(Stream input)
{
_data.Clear();
_context.Clear();
var yaml = new YamlStream();
yaml.Load(new StreamReader(input));
if (yaml.Documents.Count > 0)
{
var rootNode = yaml.Documents[0].RootNode;
VisitYamlNode("", rootNode);
}
return _data;
}
private void VisitYamlNode(string context, YamlNode node)
{
if (node is YamlScalarNode)
{
VisitYamlScalarNode(context, (YamlScalarNode)node);
}
else if (node is YamlMappingNode)
{
VisitYamlMappingNode(context, (YamlMappingNode)node);
}
else if (node is YamlSequenceNode)
{
VisitYamlSequenceNode(context, (YamlSequenceNode)node);
}
}
private void VisitYamlScalarNode(string context, YamlScalarNode node)
{
EnterContext(context);
if (_data.ContainsKey(_currentPath))
{
throw new FormatException(string.Format(Resources.Error_KeyIsDuplicated, _currentPath));
}
_data[_currentPath] = node.Value;
ExitContext();
}
private void VisitYamlMappingNode(string context, YamlMappingNode node)
{
EnterContext(context);
foreach (var yamlNode in node.Children)
{
context = ((YamlScalarNode)yamlNode.Key).Value;
VisitYamlNode(context, yamlNode.Value);
}
ExitContext();
}
private void VisitYamlSequenceNode(string context, YamlSequenceNode node)
{
EnterContext(context);
for (int i = 0; i < node.Children.Count; i++)
{
VisitYamlNode(i.ToString(), node.Children[i]);
}
ExitContext();
}
private void EnterContext(string context)
{
if (!string.IsNullOrEmpty(context))
{
_context.Push(context);
}
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}
private void ExitContext()
{
if (_context.Any())
{
_context.Pop();
}
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}
}
最後
在專案中使用可以執行下面的命令
Install-Package Cxlt.Extensions.Configuration.Yaml
或
dotnet add package Cxlt.Extensions.Configuration.Yaml
如果這篇文章對你有幫助或有什麼問題,歡迎關注“chengxulvtu"公眾號。