zoukankan      html  css  js  c++  java
  • 实现自己的.NET Core配置Provider之Yaml

    YAML是一种更适合人阅读的文件格式,很多大型的项目像Ruby on Rails都选择YAML作为配置文件的格式。如果项目的配置很少,用JSON或YAML没有多大差别。看看rails项目中的配置文件,如果用JSON写试试什么感受吧。

    《实现自己的.NET Core配置Provider之EF》中已经讲过配置的执行流程,这里不再复述,直接动手。

    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项目:YamlDotNetSharpYaml 。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());
        }
    }
    

    最后

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

    在项目中使用可以执行下面的命令

    Install-Package Cxlt.Extensions.Configuration.Yaml
    

    dotnet add package Cxlt.Extensions.Configuration.Yaml
    

    如果这篇文章对你有帮助或有什么问题,欢迎关注“chengxulvtu"公众号。

  • 相关阅读:
    GetHub下载不成功
    Cache 判断Ip几分钟内攻击次数
    .net 通过Url获取站点json数据
    Linq 读取Xml 数据
    ef Linq 自定义字段列表
    面试的心得
    触发器--单独字段变化另一个字段也变化
    Ajax跨域 取值 Jsonp的定义注意事项
    asp.net里,各种下载方式汇总
    c# 获取硬件信息
  • 原文地址:https://www.cnblogs.com/nianming/p/7097338.html
Copyright © 2011-2022 走看看