實現本身的.NET Core配置Provider之Yaml

YAML是一種更適合人閱讀的文件格式,不少大型的項目像Ruby on Rails都選擇YAML做爲配置文件的格式。若是項目的配置不多,用JSON或YAML沒有多大差異。看看rails項目中的配置文件,若是用JSON寫試試什麼感覺吧。html

《實現本身的.NET Core配置Provider之EF》中已經講過配置的執行流程,這裏再也不復述,直接動手。node

YamlConfigurationProvider

Yaml是基於文件的,能夠直接從FileConfigurationProvider繼承,在FileConfigurationProvider實現了監控文件變化並自動從新加載的功能。git

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文件的核心,後面會介紹。github

YamlConfigurationSource

internal class YamlConfigurationSource : FileConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        EnsureDefaults(builder);
        return new YamlConfigurationProvider(this);
    }
}

YamlConfigurationSource實現父類的Build方法,返回YamlConfigurationProvider數組

AddYamlFile擴展方法

爲添加Yaml配置源增長擴展方法。bash

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項目:YamlDotNetSharpYaml 。SharpYaml Fork自YamlDotNet,但作了很多改進並支持Yaml1.2,不過須要netstandard1.6+。YamlDotNet支持Yaml1.1,須要netstandard1.3+。我選擇的YamlSharp。app

Yaml可表示三種類型的數據:Scalar(標量,如字符串、布爾值、整數等)、Sequence(序列,如數組)和Mapping(映射,如字典,鍵值對等)。ide

關於Yaml能夠參考阮一峯老師的《YAML 語言教程》ui

SharpYaml會把Yaml文件轉換爲樹形結構,而後咱們只須要把全部的葉子節點的路徑做爲字典的鍵,將葉子節點的值做爲字典的值存儲起來就能夠了。this

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());
    }
}

最後

本項目已在github上開源,地址:https://github.com/chengxulvtu/Cxlt.Extensions.Configuration

在項目中使用能夠執行下面的命令

Install-Package Cxlt.Extensions.Configuration.Yaml

dotnet add package Cxlt.Extensions.Configuration.Yaml

若是這篇文章對你有幫助或有什麼問題,歡迎關注「chengxulvtu"公衆號。

相關文章
相關標籤/搜索