ASP.NET Core

2023年 8月 12日 83.5k 0

自定义配置提供程序

在 .NET Core 配置系统中封装一个配置提供程序关键在于提供相应的 IconfigurationSource 实现和 IConfigurationProvider 接口实现,这两个接口在上一章 ASP.NET Core - 配置系统之配置提供程序 中也有提到了。

IConfigurationSource

IConfigurationSource负责创建IConfigurationProvider实现的实例。它的定义很简单,就一个Build方法,返回IConfigurationProvider实例:

public interface IConfigurationSource
{
IConfigurationProvider Build(IConfigurationBuilder builder);
}

IConfigurationProvider

IConfigurationProvider 负责实现配置的设置、读取、重载等功能,并以键值对形式提供配置。

public interface IConfigurationProvider
{
// 获取指定父路径下的直接子节点Key,然后 Concat(earlierKeys) 一同返回
IEnumerable GetChildKeys(IEnumerable earlierKeys, string parentPath);

// 当该配置提供程序支持更改追踪(change tracking)时,会返回 change token
// 否则,返回 null
IChangeToken GetReloadToken();

// 加载配置
void Load();

// 设置 key:value
void Set(string key, string value);

// 尝试获取指定 key 的 value
bool TryGet(string key, out string value);
}

像工作中常用的配置中心客户端,例如 nacos、consul,都是实现了对应的配置提供程序,从而将配置中心中的配置无缝地接入到 .NET Core 的配置系统中进行使用,和本地配置文件的使用没有分别。

如果我们需要封装自己的配置提供程序,推荐直接继承抽象类 ConfigurationProvider,该类实现了 IConfigurationProvider 接口,继承自该类只要实现Load方法即可,Load方法用于从配置来源加载解析配置信息,将终的键值对配置信息存储到Data中。这个过程中可参考一下其他已有的配置提供程序的源码,模仿着去写自己的东西。

在我们日常的系统平台中,总少不了数据字典这样一个功能,用于维护平台中一些业务配置,因为是随业务动态扩展和变动的,很多时候不会写在配置文件,而是维护在数据库中。以下以这样一个场景实现一个配置提供程序。

因为是以数据库作为载体来存储配置信息,所以步就是定义实体类

public class DataDictioaryDO
{
public int Id { get; set
public int? ParentId { get; set
public string Key { get; set
public string Value { get; set; }
}

数据字典支持多级级联,通过ParentId关联上一级,ParentId为空的即为根节点,如存在下级节点则Value值可以为空,就算填写了也无*效,终呈现出来的就是一个树结构。

然后就是定义相应的数据库访问上下问 DataDictionaryDbContext

public class DataDictionaryDbContext : DbContext
{
public DbSet DataDictioaries { get; set; }

public DataDictionaryDbContext(DbContextOptions options) : base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity().HasKey(e => e.Id);
modelBuilder.Entity().Property(e => e.Value).IsRequired(false);
}
}

通过 DbContextOptions 交由外部去配置具体的数据库类型和连接字符串。

之后创建 IConfigurationSource 实现类,主要就是构造函数中需要传入数据库配置委托,并且在 Build 实例化EFDataDictionaryConfigurationProvider 对象。

public class EFDataDictionaryConfigurationSource : IConfigurationSource
{
private readonly Action _action;

public EFDataDictionaryConfigurationSource(Action action)
{
_action= action;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new EFDataDictionaryConfigurationProvider(_action);
}
}

之后通过继承 ConfigurationProvider 实现 EFDataDictionaryConfigurationProvider,主要逻辑就是从数据库获取对应的数据表,如果表中没有数据则插入默认数据,再通过相应的解析器解析数据表数据生成一个 Dictionary 对象。

public class EFDataDictionaryConfigurationProvider : ConfigurationProvider
{
Action OptionsAction { get; }

public EFDataDictionaryConfigurationProvider(Action action)
{
OptionsAction = action;
}
public override void Load()
{
var builder = new DbContextOptionsBuilder();

OptionsAction(builde);
using var dbContext = new DataDictionaryDbContext(builder.Options);
if(dbContext == null)
{
throw new Exception("Null DB Context !");
}

dbContext.Database.EnsureCreated();

if (!dbContext.DataDictioaries.Any())
{
CreateAndSaveDefaultValues(dbContext);
}

Data = EFDataDictionaryParser.Parse(dbContext.DataDictioaries);
}

private void CreateAndSaveDefaultValues(DataDictionaryDbContext context)
{
var datas = new List
{
new DataDictioaryDO
{
Id = 1,
Key = "Settings",
},
new DataDictioaryDO
{
Id = 2,
ParentId = 1,
Key = "Provider",
Value = nameof(EFDataDictionaryConfigurationProvider)
},
new DataDictioaryDO
{
Id = 3,
ParentId = 1,
Key = "Version",
Value = "v1.0.0"
}
};

context.DataDictioaries.AddRange(datas);
context.SaveChanges();
}
}

其中,解析器 EFDataDictionaryParser 的代码如下,主要就是通过递归的方式,通过树形数据的 key 构建构建完整的 key,并将其存入 Dictionary对象中。

internal class EFDataDictionaryParser
{
private readonly IDictionary _data = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
private readonly Stack _context = new();
private string _currentPath;

private EFDataDictionaryParser() { }

public static IDictionary Parse(IEnumerable datas) =>
new EFDataDictionaryParser().ParseDataDictionaryConfiguration(datas);

private IDictionary ParseDataDictionaryConfiguration(IEnumerable datas)
{
_data.Clear();

if(datas?.Any() != true)
{
return _data;
}

var roots = datas.Where(d => !d.ParentId.HasValue);
foreach (var root in roots)
{
EnterContext(root.Key);
VisitElement(datas, root);
ExitContext();
}
return _data;
}

private void VisitElement(IEnumerable datas, DataDictioaryDO parent)
{
var children = datas.Where(d => d.ParentId == parent.Id);
if (children.Any())
{
foreach (var section in children)
{
EnterContext(section.Key);
VisitElement(datas, section);
ExitContext();
}
}
else
{
var key = _currentPath;
if (_data.ContainsKey(key))
throw new FormatException($"A duplicate key '{key}' was found.");

_data[key] = parent.Value;
}
}

private void EnterContext(string context)
{
_context.Push(context);
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}

private void ExitContext()
{
_context.Pop();
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}
}

之后为这个配置提供程序提供一个扩展方法,方便之后的使用,如下:

public static class EFDataDictionaryConfigurationExtensions
{
public static IConfigurationBuilder AddEFDataDictionaryConfiguration(this IConfigurationBuilder builder,
Action optionAction)
{
builder.Add(new EFDataDictionaryConfigurationSource(optionAction));
return builder;
}
}

之后在入口文件中将我们的配置扩展程序添加到配置系统中,并指定使用内存数据库进行测试

using ConfigurationSampleConsole.ConfigProvider;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
// 清除原有的配置提供程序
config.Sources.Clear();

config.AddEFDataDictionaryConfiguration(builder =>
{
builder.UseInMemoryDatabase("DataDictionary");
});
})
.Build();

var configuration = host.Services.GetService();

Console.WriteLine($"Settings:Provider: {configuration.GetValue("Settings:Provider")}");
Console.WriteLine($"Settings:Version: {configuration.GetValue("Settings:version")}");

host.Run();

后的控制台输出结果如下:

以上就是 .NET Core 框架下配置系统的一部分知识点,更加详尽的介绍大家可以再看看官方文档。配置系统很多时候是结合选项系统一起使用的,下一篇将介绍一下 .NET Core 框架下的选项系统。

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论