02020305 .NET Core核心基础组件05-开发自己的配置提供者

02020305 .NET Core核心基础组件05-开发自己的配置提供者

1. 开发自己的配置提供者(视频2-35)

1.1 开发自定义配置提供者的步骤
  • step1 → 开发一个直接或者间接实现IConfigurationProvider接口的类XXXConfigurationProvider,一般继承自ConfigurationProvider。如果是从文件读取,可以继承自FileConfigurationProvider。重写Load方法,把“扁平化数据”设置到Data属性即可。
  • step2 → 再开发一个实现了IConfigurationSource接口的类XXXConfigurationSource。如果是从文件读取,可以继承自FileConfigurationSource。在Build方法中返回上面的ConfigurationProvider对象。
  • step3 → 然后使用即可,configurationBuilder.Add(new ConfigurationSource())即可。为了简化使用,一般提供一个IConfigurationBuilder的扩展方法。
  • 整体流程简述:编写ConfigurationProvider类实际读取配置;编写ConfigurationSource在Build中返回ConfigurationProvider对象;把ConfigurationSource对象加入IConfigurationBuilder。
1.2 开发web.config提供者
  • .NET Core中不建议使用.NET Framework里的web.config,不过仍然继续提供了ConfigurationManager了,不过没有官方支持通过新的Configuration框架读取的方式。我们来实现一个,能够读取web.config里的connectionStrings和appSettings的内容。没用过web.config也没关系。
  • 这个项目主要意义还是展示如何编写自定义配置提供者,实用意义不大。主要为下一个更实用的配置提供者做知识准备。
1.3 web.config格式
// web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="connstr1" connectionString="Data Source=.;Initial Catalog=DemoDB;User ID=sa;Password=123456" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <appSettings>
    <add key="Smtp:Server" value="smtp.test.com" />
    <add key="Smtp.Port" value="25" />
    <add key="RedisServer" value="127.0.0.1" />
    <add key="RedisPassword" value="abc123" />
  </appSettings>
</configuration>
  • configuration → 根节点
  • connectionStrings → 配置的是连接字符串
  • appSettings → 普通配置,用于非连接字符串
1.4 解析xml文件的类并映射为扁平化结果
  • 不再讲解xml以及XmlDocument的基础知识。继承自FileConfigurationProvider
//Key是大小写不敏感的
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(stream);
var nodeConnStrs = xmlDoc.SelectNodes("/configuration/connectionStrings/add");
foreach(var nodeConnStr in nodeConnStrs.Cast<XmlNode>())
{
    string name = nodeConnStr.Attributes["name"].Value;
    string connectionString = nodeConnStr.Attributes["connectionString"].Value;
    data[$"{name}:connectionString"] = connectionString;
    var providerNameProp = nodeConnStr.Attributes["providerName"];
    if(providerNameProp!=null)//providerName is optional
    {
        string providerName = providerNameProp.Value;
        data[$"{name}:providerName"] = providerName;
    }
} 

说明:
1. XmlDocument → 用来解析xml文件。自行在网上搜索XmlDocument的使用和xml相关知识。
2. Configuration框架约定大小写是不敏感的。OrdinalignoreCase:大小写不敏感。

2. 项目示例

2.1 项目目录
MyConfigProviderDemo → 项目名称,.NET Core 5.0控制台项目。
├── MyConfigProvider.cs
├── MyConfigSource.cs
├── TestWebConfig.cs
├── WebConfig.cs
├── MyConfigExtensions.cs
├── web.config
└── Program.cs

需要安装如下5个包:
microsoft.extensions.configuration
microsoft.extensions.configuration.binder
microsoft.extensions.configuration.json
microsoft.extensions.options
microsoft.extensions.dependencyinjection
2.2 源代码
// MyConfigExtensions.cs
using Microsoft.Extensions.Configuration;

namespace MyConfigProviderDemo
{
    static class MyConfigExtensions
    {
        public static IConfigurationBuilder AddMyConfig(this IConfigurationBuilder cb, string path=null)
        {
            if(path == null)
            {
                path = "web.config";
            }
            cb.Add(new MyConfigSource() { Path = path });
            return cb;
        }

    }
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyConfigProvider.cs
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;

namespace MyConfigProviderDemo
{
    class MyConfigProvider : FileConfigurationProvider
    {
        public MyConfigProvider(MyConfigSource src) : base(src) // 调用父类的构造函数
        {

        }
        public override void Load(Stream stream) // 读取的配置文件流
        {
            var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // StringComparer.OrdinalIgnoreCase表示忽略大小写
            XmlDocument xmlDoc = new XmlDocument(); // 创建一个xml对象
            xmlDoc.Load(stream); // 加载xml文件
            /*
             * web.config文档
                <configuration>
                  <connectionStrings>
                    <add name="connstring" connectionString="abc" providerName="mysql"/>
                    <add name="conntext" connectionString="123" providerName="sqlserver"/>
                  </connectionStrings>
                  <appSettings>
                    <add key="AppStts:name" value="smtp.test.com" />
                    <add key="AppStts:age" value="25" />
                    <add key="AppStts:proxy.address" value="127.0.0.1" />
                    <add key="AppStts:proxy:ids:0" value="666" />
                    <add key="AppStts:proxy:ids:1" value="777" />
                  </appSettings>
                </configuration>
             */

            // 将connectionStrings中的节点扁平化映射为:[cstr:{connectionString:"abc",providerName:"mysql"}, ctext:{connectionString:"123",providerName:"sqlserver"}]
            XmlNodeList connStringsNode = xmlDoc.SelectNodes("/configuration/connectionStrings/add"); // 拿到connectionStrings下所有(2个)节点:connstring和conntext
            foreach (XmlNode xmlNode in connStringsNode.Cast<XmlNode>()) // Cast<XmlNode>()转换节点集合,遍历connStringsNode(即connstring和conntext集合)转换为XmlNode
            {
                string name = xmlNode.Attributes["name"].Value;
                string connectionString = xmlNode.Attributes["connectionString"].Value;
                data[$"{name}:connectionString"] = connectionString; // 解析xml文件,将结果扁平化映射到data中
                var attProviderName = xmlNode.Attributes["providerName"];
                if (attProviderName != null)
                {
                    data[$"{name}:providerName"] = attProviderName.Value; // 解析xml文件,将结果扁平化映射到data中
                }
            }

            // 将appSettings中的节点扁平化映射为:[key:value形式]
            XmlNodeList appSettings = xmlDoc.SelectNodes("/configuration/appSettings/add"); // 拿到connectionStrings下所有(2个)节点:connstring和conntext
            foreach (XmlNode xmlNode in appSettings.Cast<XmlNode>()) // Cast<XmlNode>()转换节点集合,遍历connStringsNode(即connstring和conntext集合)转换为XmlNode
            {
                string key = xmlNode.Attributes["key"].Value;
                key = key.Replace(".", ":"); // 将.替换为:
                string value = xmlNode.Attributes["value"].Value;
                data[key] = value; // 解析xml文件,将结果扁平化映射到data中
            }
            this.Data = data; // 将扁平化的数据赋值到Data中
        }
    }
}

—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyConfigSource.cs
using Microsoft.Extensions.Configuration;

namespace MyConfigProviderDemo
{
    /// <summary>
    /// MyConfigSource用来提供参数。
    /// </summary>
    class MyConfigSource : FileConfigurationSource
    {
        public override IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            EnsureDefaults(builder); // 这句代码老师没有具体说明,大意是对相对或者绝对路径进行转换的意思
            return new MyConfigProvider(this);
        }
    }
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// TestWebConfig.cs
using Microsoft.Extensions.Options;
using System;

namespace MyConfigProviderDemo
{
    class TestWebConfig
    {
        private IOptionsSnapshot<WebConfig> optWebConfig;

        public TestWebConfig(IOptionsSnapshot<WebConfig> optWebConfig)
        {
            this.optWebConfig = optWebConfig;
        }

        public void Test()
        {
            var wc = optWebConfig.Value;
            Console.WriteLine(wc.Connstring.ConnectionString);
            Console.WriteLine(wc.Connstring.ProviderName);
            Console.WriteLine(wc.Conntext.ConnectionString);
            Console.WriteLine(wc.Conntext.ProviderName);
            Console.WriteLine(wc.AppStts.Name);
            Console.WriteLine(wc.AppStts.Age);
            Console.WriteLine(wc.AppStts.Proxy.Address);
            Console.WriteLine(wc.AppStts.Proxy.Ids);
        }
    }
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// WebConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyConfigProviderDemo
{
    /*
     * web.config文档
        <configuration>
          <connectionStrings>
            <add name="connstring" connectionString="abc" providerName="mysql"/>
            <add name="conntext" connectionString="123" providerName="sqlserver"/>
          </connectionStrings>
          <appSettings>
            <add key="AppStts:name" value="smtp.test.com" />
            <add key="AppStts:age" value="25" />
            <add key="AppStts:proxy.address" value="127.0.0.1" />
            <add key="AppStts:proxy:ids:0" value="666" />
            <add key="AppStts:proxy:ids:1" value="777" />
          </appSettings>
        </configuration>
     *
     * 1. 将connectionStrings中的节点扁平化映射为:[cstr:{connectionString:"abc",providerName:"mysql"}, ctext:{connectionString:"123",providerName:"sqlserver"}]
     */
    class ConnStrsAttr // 与connectionStrings节点中的connectionString和providerName对应
    {
        public string ConnectionString { get; set; } // 与扁平结构中connectionString对应
        public string ProviderName { get; set; } // 与扁平结构中providerName对应
    }

    class AppSttsProxy // 与appSettings中proxy对应
    {
        public string Address { get; set; } // 与proxy:adde
        public int[] Ids { get; set; } // 与proxy:ids:0和proxy:ids:1对应
    }

    class AppStts // 与appSettings节点对应
    {
        public string Name { get; set; } // 与<add key="name" value="smtp.test.com" />对应
        public int Age { get; set; } // 与<add key="age" value="25" />对应
        public AppSttsProxy Proxy { get; set; } // 与<add key = "proxy.../>对应
    }

    class WebConfig // 与web.config中所有节点对应
    {
        public ConnStrsAttr Connstring { get; set; } // 与扁平结构cstr对应
        public ConnStrsAttr Conntext { get; set; } // 与扁平结构ctext对应
        public AppStts AppStts { get; set; } // 与扁平结构appSettings对应
    }
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// web.config
<configuration>
  <connectionStrings>
    <add name="connstring" connectionString="abc" providerName="mysql"/>
    <add name="conntext" connectionString="123" providerName="sqlserver"/>
  </connectionStrings>
  <appSettings>
    <add key="AppStts:name" value="smtp.test.com" />
    <add key="AppStts:age" value="25" />
    <add key="AppStts:proxy.address" value="127.0.0.1" />
    <add key="AppStts:proxy:ids:0" value="666" />
    <add key="AppStts:proxy:ids:1" value="777" />
  </appSettings>
</configuration>
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Program.cs
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace MyConfigProviderDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<TestWebConfig>();
            ConfigurationBuilder configBuilder = new ConfigurationBuilder();
            // configBuilder.Add(new MyConfigSource() { Path = "web.config" }); // 不加扩展方法用这种
            configBuilder.AddMyConfig(); // 加了扩展方法可以直接.出来
            IConfiguration configRoot = configBuilder.Build();
            services.AddOptions().Configure<WebConfig>(e => configRoot.Bind(e)); // 根节点和WebConfig绑定

            using (var sp = services.BuildServiceProvider())
            {
                var c = sp.GetRequiredService<TestWebConfig>();
                c.Test();
            }
        }
    }
}

控制台输出:
abc
mysql
123
sqlserver
smtp.test.com
25
127.0.0.1
System.Int32[]

2.3 项目总结

  • 把DI和配置系统整个复习了一遍,查漏补缺之后再看这个项目感觉也还好。战术后仰。

结尾

书籍:ASP.NET Core技术内幕与项目实战

视频:https://www.bilibili.com/video/BV1pK41137He

著:杨中科

ISBN:978-7-115-58657-5

版次:第1版

发行:人民邮电出版社

※敬请购买正版书籍,侵删请联系85863947@qq.com※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

posted @ 2025-09-12 20:45  qinway  阅读(8)  评论(0)    收藏  举报