.net framework config provider
- 参照课程和JsonFileConfiguration制作自己的配置库
- 能跑成功,但是先放着,等到需要时再自习分析
项目结构
- 其中test项目依赖工程项目
nuget库
DotnetFrameworkConfigurationProvider
DotnetFrameworkConfigurationGlobalConfig
namespace Cattail.ConfigurationProvider.DotnetFrameworkConfigurationProvider;
/// <summary>
/// [usage] 为DotnetFrameworkConfiguration项目提供全局配置
/// </summary>
public static class DotnetFrameworkConfigurationGlobalConfig
{
/// <summary>
/// [usage] webconfig.xml文件的位置
/// </summary>
public static string ConfigPath = Environment.CurrentDirectory + "\\Configuration\\webconfig.xml";
// public static string ConfigPath = "/Configuration/webconfig.xml";
}
Configuration
namespace Cattail.ConfigurationProvider.DotnetFrameworkConfigurationProvider;
// [file] xml文件实例化类型
public class Configuration
{
public ConnectionStringClass? Connstr1 { get; set; }
public ConnectionStringClass? Contest1 { get; set; }
public Config? Config { get; set; }
public override string ToString()
{
// tostring
return $"{nameof(Connstr1)}: {Connstr1}, {nameof(Contest1)}: {Contest1}, {nameof(Config)}: {Config}";
}
}
public class ConnectionStringClass
{
public string? ConnectionString { get; set; }
public string? ProviderName { get; set; }
public override string ToString()
{
// tostring
return $"{nameof(ConnectionString)}: {ConnectionString}, {nameof(ProviderName)}: {ProviderName}";
}
}
public class Config
{
public string? RedisServer { get; set; }
public string? RedisPassword { get; set; }
public Smtp? Smtp { get; set; }
public override string ToString()
{
// tostring
return $"{nameof(RedisServer)}: {RedisServer}, {nameof(RedisPassword)}: {RedisPassword}, {nameof(Smtp)}: {Smtp}";
}
}
public class Smtp
{
public string? Server { get; set; }
public string? Port { get; set; }
public override string ToString()
{
// tostring
return $"{nameof(Server)}: {Server}, {nameof(Port)}: {Port}";
}
}
webconfig.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 配置 -->
<configuration>
<!-- 链接信息 -->
<connectionStrings>
<add name="connstr1" connectionString="DataSource=.;InitialCatalog=DemoDB;UserId=sa;Password=123456"/>
<add name="contest1" connectionString="test text" providerName="MySql"/>
</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>
FxFileConfigurationSource
using Microsoft.Extensions.Configuration;
namespace Cattail.ConfigurationProvider.DotnetFrameworkConfigurationProvider;
public class FxFileConfigurationSource : FileConfigurationSource
{
public override IConfigurationProvider Build(IConfigurationBuilder builder)
{
// [notice] 该语句用于处理path等默认值问题
EnsureDefaults(builder);
return new FxFileConfigurationProvider(this);
}
}
FxFileConfigurationProvider
using System.Xml;
using Microsoft.Extensions.Configuration;
namespace Cattail.ConfigurationProvider.DotnetFrameworkConfigurationProvider;
/// <summary>
/// <para>
/// [功能] .net framework 配置文件读取 配置提供者
/// </para>
/// </summary>
public class FxFileConfigurationProvider : FileConfigurationProvider
{
public FxFileConfigurationProvider(FxFileConfigurationSource source) : base(source)
{
}
/// <summary>
/// [功能] 解析配置文件中的connectionStrings节点
/// </summary>
/// <param name="xmlDocument"></param>
/// <param name="data"></param>
private static void ConnectionStringsXmlParser(XmlDocument xmlDocument, Dictionary<string, string?> data)
{
// [步骤] 解析xml目标节点
using (XmlNodeList? nodeConnectStrings = xmlDocument.SelectNodes("/configuration/connectionStrings/add"))
{
if (nodeConnectStrings is null)
return;
// [步骤] 遍历节点
foreach (XmlNode nodeConnectString in nodeConnectStrings.Cast<XmlNode>())
{
// [步骤] 提取add中的name和相应的connectionString
string? name = nodeConnectString.Attributes?["name"]?.Value;
string? connectionString = nodeConnectString.Attributes?["connectionString"]?.Value;
// [步骤] 记录name、connectionString和它的值value
if (connectionString is not null)
data[$"{name}:connectionString"] = connectionString;
// [步骤] 提取add节点中的providerName参数
var providerNameProperty = nodeConnectString.Attributes?["providerName"];
// 记录name、providerName和它的值value
string? providerName = providerNameProperty?.Value;
data[$"{name}:providerName"] = providerName;
}
}
}
/// <summary>
/// [功能] 解析配置文件中的appSettings节点
/// </summary>
/// <param name="xmlDocument"></param>
/// <param name="data"></param>
private static void AppSettingsXmlParser(XmlDocument xmlDocument, Dictionary<string, string?> data)
{
using (var asNodes = xmlDocument.SelectNodes("/configuration/appSettings/add"))
{
if (asNodes is null)
return;
foreach (var xmlNode in asNodes.Cast<XmlNode>())
{
if (xmlNode is null)
continue;
// [process] key
string? key = xmlNode.Attributes?["key"]?.Value;
key = key?.Replace(".", ";");
// [process] value
string? value = xmlNode.Attributes?["value"]?.Value;
data[$"{nameof(Config)}:" + key] = value;
}
}
}
/// <summary>
/// [功能] 加载资源
/// </summary>
/// <remarks>
/// [实现步骤] 创建大小写不敏感的Dictionary;加载xml文件;解析xml;找到目标节点下的多个资源;解析资源并保存到dictionary
/// </remarks>
/// <param name="stream">配置数据流</param>
public override void Load(Stream stream)
{
// [步骤] 创建大小写不敏感的Dictionary,用于存储config参数
// [解释] 参数StringComparer.OrdinalIgnoreCase表示该dictionary是大小写不敏感的
var data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
// [步骤] 加载xml文件
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(stream);
// [步骤] 解析配置文件中的connectionStrings节点
ConnectionStringsXmlParser(xmlDocument, data);
// [步骤] 解析配置文件中的appSettings节点
AppSettingsXmlParser(xmlDocument, data);
// [步骤] 配置data
this.Data = data;
}
}
FxFileConfigurationExtensions
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
namespace Cattail.ConfigurationProvider.DotnetFrameworkConfigurationProvider;
public static class FxFileConfigurationExtensions
{
// [class message]
// [refer to] JsonConfigurationExtensions
/// <summary>
/// Adds a Fx configuration source to <paramref name="builder".
/// (fx configuration is a xml .net framework config file);
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="configureSource">Configures the source.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
// [idea] 使用原生builder.Add(Action<FxFileConfigurationSource>?)方法
// [notice] builder.Add 传输action,表示Configuration Source配置的方式,
// [notice] 但是Configuration Source创建对象的时机还在官方代码
public static IConfigurationBuilder AddFxFile(this IConfigurationBuilder builder,
Action<FxFileConfigurationSource>? configureSource) => builder.Add(configureSource);
/// <summary>
/// Adds a Fx configuration source to <paramref name="builder"/>.
/// <para>(fx configuration is a xml .net framework config file)</para>
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="provider">The <see cref="IFileProvider"/> to use to access the file.</param>
/// <param name="path">Path relative to the base path stored in
/// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>
/// <param name="optional">Whether the file is optional.</param>
/// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddFxFile(this IConfigurationBuilder builder, IFileProvider? provider,
string path, bool optional, bool reloadOnChange)
{
// [process begin] 参数校验
if (builder is null)
{
throw new ArgumentNullException($"{nameof(builder)}参数不能为null!");
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException($"{nameof(path)}参数不能为null!");
}
// [process] 路径验证
// [propose] 为了让路径验证掌握在自己编写的代码中
if (!File.Exists(path))
{
throw new FileNotFoundException($"文件{path}不存在!请检查{nameof(path)}的路径!");
}
// [process end] 参数校验
// [process] configuration builder 参数添加 configuration source
return builder.AddFxFile(s =>
{
s.FileProvider = provider;
s.Path = path;
s.Optional = optional;
s.ReloadOnChange = reloadOnChange;
s.ResolveFileProvider();
});
}
/// <summary>
/// Adds a Fx configuration source to <paramref name="builder"/>.
/// <para>(fx configuration is a xml .net framework config file)</para>
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="path">Path relative to the base path stored in
/// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>
/// <param name="optional">Whether the file is optional.</param>
/// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddFxFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
{
return AddFxFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange);
}
/// <summary>
/// Adds a Fx configuration source to <paramref name="builder"/>.
/// <para>(fx configuration is a xml .net framework config file)</para>
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="path">Path relative to the base path stored in
/// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>
/// <param name="optional">Whether the file is optional.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddFxFile(this IConfigurationBuilder builder, string path, bool optional)
{
return AddFxFile(builder, provider: null, path: path, optional: optional, reloadOnChange: false);
}
/// <summary>
/// Adds a Fx configuration source to <paramref name="builder"/>.
/// <para>(fx configuration is a xml .net framework config file)</para>
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/> to add to.</param>
/// <param name="path">Path relative to the base path stored in
/// <see cref="IConfigurationBuilder.Properties"/> of <paramref name="builder"/>.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
public static IConfigurationBuilder AddFxFile(this IConfigurationBuilder builder, string path)
{
return AddFxFile(builder, provider: null, path: path, optional: false, reloadOnChange: false);
}
}
DotnetFrameworkConfigurationProviderTest
WebConfigServiceTest
using Cattail.ConfigurationProvider.DotnetFrameworkConfigurationProvider;
using Microsoft.Extensions.Options;
namespace DotnetFrameworkConfigurationProviderTest;
public class WebConfigServiceTest(IOptionsSnapshot<Configuration> optionsSnapshot)
{
private IOptionsSnapshot<Configuration> _optionsSnapshot = optionsSnapshot;
public override string ToString()
{
// tostring
return "WebConfigServiceTest._optionsSnapshot: " + _optionsSnapshot.Value.ToString();
}
}
DotnetFrameworkConfigurationProviderTest
using Cattail.ConfigurationProvider.DotnetFrameworkConfigurationProvider;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DotnetFrameworkConfigurationProviderTest;
/// <summary>
/// 测试自己编写的配置提供器
/// </summary>
[TestClass]
public class DotnetFrameworkConfigurationProviderTest
{
// [test method] 测试自己编写的配置提供器
[TestMethod]
public void TestDotnetFrameworkConfigurationProvider()
{
// [process] 验证文件是否存在
if (!File.Exists(DotnetFrameworkConfigurationGlobalConfig.ConfigPath))
{
Console.WriteLine("文件不存在");
throw new FileNotFoundException();
}
// // [process] 装配config source
// FxFileConfigurationSource fxFileConfigurationSource = new FxFileConfigurationSource();
// fxFileConfigurationSource.Path = DotnetFrameworkConfigurationGlobalConfig.ConfigPath;
// // [debug] 防止绝对路径引起的 “The configuration file was not found and is not optional”报错
// fxFileConfigurationSource.ResolveFileProvider();
// [process] 创建config builder
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
// [process] 添加config source
// configurationBuilder.Add(fxFileConfigurationSource);
configurationBuilder.AddFxFile(DotnetFrameworkConfigurationGlobalConfig.ConfigPath);
// [process] 生成config root
IConfigurationRoot configurationRoot = configurationBuilder.Build();
// [process] 配置依赖注入
ServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddOptions()
.Configure<Configuration>(e => configurationRoot.Bind(e));
// [process] 配置需求类依赖注入
serviceCollection.AddScoped<WebConfigServiceTest>();
// [process] 生成service provider
using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider())
{
var test = serviceProvider.GetService<WebConfigServiceTest>();
Console.WriteLine(test);
}
}
}
.NET 6教程,.Net Core 2022视频教程,杨中科主讲 - p43 - 编写自己的配置类