经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
ASP.NET Core - 配置系统之自定义配置提供程序
来源:cnblogs  作者:啊晚  时间:2023/3/14 8:48:20  对本文有异议

4. 自定义配置提供程序

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

IConfigurationSource

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

  1. public interface IConfigurationSource
  2. {
  3. IConfigurationProvider Build(IConfigurationBuilder builder);
  4. }

IConfigurationProvider

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

  1. public interface IConfigurationProvider
  2. {
  3. // 获取指定父路径下的直接子节点Key,然后 Concat(earlierKeys) 一同返回
  4. IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
  5. // 当该配置提供程序支持更改追踪(change tracking)时,会返回 change token
  6. // 否则,返回 null
  7. IChangeToken GetReloadToken();
  8. // 加载配置
  9. void Load();
  10. // 设置 key:value
  11. void Set(string key, string value);
  12. // 尝试获取指定 key 的 value
  13. bool TryGet(string key, out string value);
  14. }

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

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

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

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

  1. public class DataDictioaryDO
  2. {
  3. public int Id { get; set
  4. public int? ParentId { get; set
  5. public string Key { get; set
  6. public string Value { get; set; }
  7. }

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

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

  1. public class DataDictionaryDbContext : DbContext
  2. {
  3. public DbSet<DataDictioaryDO> DataDictioaries { get; set; }
  4. public DataDictionaryDbContext(DbContextOptions<DataDictionaryDbContext> options) : base(options)
  5. {
  6. }
  7. protected override void OnModelCreating(ModelBuilder modelBuilder)
  8. {
  9. base.OnModelCreating(modelBuilder);
  10. modelBuilder.Entity<DataDictioaryDO>().HasKey(e => e.Id);
  11. modelBuilder.Entity<DataDictioaryDO>().Property(e => e.Value).IsRequired(false);
  12. }
  13. }

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

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

  1. public class EFDataDictionaryConfigurationSource : IConfigurationSource
  2. {
  3. private readonly Action<DbContextOptionsBuilder> _action;
  4. public EFDataDictionaryConfigurationSource(Action<DbContextOptionsBuilder> action)
  5. {
  6. _action= action;
  7. }
  8. public IConfigurationProvider Build(IConfigurationBuilder builder)
  9. {
  10. return new EFDataDictionaryConfigurationProvider(_action);
  11. }
  12. }

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

  1. public class EFDataDictionaryConfigurationProvider : ConfigurationProvider
  2. {
  3. Action<DbContextOptionsBuilder> OptionsAction { get; }
  4. public EFDataDictionaryConfigurationProvider(Action<DbContextOptionsBuilder> action)
  5. {
  6. OptionsAction = action;
  7. }
  8. public override void Load()
  9. {
  10. var builder = new DbContextOptionsBuilder<DataDictionaryDbContext>();
  11. OptionsAction(builde);
  12. using var dbContext = new DataDictionaryDbContext(builder.Options);
  13. if(dbContext == null)
  14. {
  15. throw new Exception("Null DB Context !");
  16. }
  17. dbContext.Database.EnsureCreated();
  18. if (!dbContext.DataDictioaries.Any())
  19. {
  20. CreateAndSaveDefaultValues(dbContext);
  21. }
  22. Data = EFDataDictionaryParser.Parse(dbContext.DataDictioaries);
  23. }
  24. private void CreateAndSaveDefaultValues(DataDictionaryDbContext context)
  25. {
  26. var datas = new List<DataDictioaryDO>
  27. {
  28. new DataDictioaryDO
  29. {
  30. Id = 1,
  31. Key = "Settings",
  32. },
  33. new DataDictioaryDO
  34. {
  35. Id = 2,
  36. ParentId = 1,
  37. Key = "Provider",
  38. Value = nameof(EFDataDictionaryConfigurationProvider)
  39. },
  40. new DataDictioaryDO
  41. {
  42. Id = 3,
  43. ParentId = 1,
  44. Key = "Version",
  45. Value = "v1.0.0"
  46. }
  47. };
  48. context.DataDictioaries.AddRange(datas);
  49. context.SaveChanges();
  50. }
  51. }

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

  1. internal class EFDataDictionaryParser
  2. {
  3. private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  4. private readonly Stack<string> _context = new();
  5. private string _currentPath;
  6. private EFDataDictionaryParser() { }
  7. public static IDictionary<string, string> Parse(IEnumerable<DataDictioaryDO> datas) =>
  8. new EFDataDictionaryParser().ParseDataDictionaryConfiguration(datas);
  9. private IDictionary<string, string> ParseDataDictionaryConfiguration(IEnumerable<DataDictioaryDO> datas)
  10. {
  11. _data.Clear();
  12. if(datas?.Any() != true)
  13. {
  14. return _data;
  15. }
  16. var roots = datas.Where(d => !d.ParentId.HasValue);
  17. foreach (var root in roots)
  18. {
  19. EnterContext(root.Key);
  20. VisitElement(datas, root);
  21. ExitContext();
  22. }
  23. return _data;
  24. }
  25. private void VisitElement(IEnumerable<DataDictioaryDO> datas, DataDictioaryDO parent)
  26. {
  27. var children = datas.Where(d => d.ParentId == parent.Id);
  28. if (children.Any())
  29. {
  30. foreach (var section in children)
  31. {
  32. EnterContext(section.Key);
  33. VisitElement(datas, section);
  34. ExitContext();
  35. }
  36. }
  37. else
  38. {
  39. var key = _currentPath;
  40. if (_data.ContainsKey(key))
  41. throw new FormatException($"A duplicate key '{key}' was found.");
  42. _data[key] = parent.Value;
  43. }
  44. }
  45. private void EnterContext(string context)
  46. {
  47. _context.Push(context);
  48. _currentPath = ConfigurationPath.Combine(_context.Reverse());
  49. }
  50. private void ExitContext()
  51. {
  52. _context.Pop();
  53. _currentPath = ConfigurationPath.Combine(_context.Reverse());
  54. }
  55. }

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

  1. public static class EFDataDictionaryConfigurationExtensions
  2. {
  3. public static IConfigurationBuilder AddEFDataDictionaryConfiguration(this IConfigurationBuilder builder,
  4. Action<DbContextOptionsBuilder> optionAction)
  5. {
  6. builder.Add(new EFDataDictionaryConfigurationSource(optionAction));
  7. return builder;
  8. }
  9. }

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

  1. using ConfigurationSampleConsole.ConfigProvider;
  2. using Microsoft.EntityFrameworkCore;
  3. using Microsoft.Extensions.Configuration;
  4. using Microsoft.Extensions.DependencyInjection;
  5. using Microsoft.Extensions.Hosting;
  6. using var host = Host.CreateDefaultBuilder(args)
  7. .ConfigureAppConfiguration((context, config) =>
  8. {
  9. // 清除原有的配置提供程序
  10. config.Sources.Clear();
  11. config.AddEFDataDictionaryConfiguration(builder =>
  12. {
  13. builder.UseInMemoryDatabase("DataDictionary");
  14. });
  15. })
  16. .Build();
  17. var configuration = host.Services.GetService<IConfiguration>();
  18. Console.WriteLine($"Settings:Provider: {configuration.GetValue<string>("Settings:Provider")}");
  19. Console.WriteLine($"Settings:Version: {configuration.GetValue<string>("Settings:version")}");
  20. host.Run();

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

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



参考文章:

ASP.NET Core 中的配置 | Microsoft Learn
配置 - .NET | Microsoft Learn
理解ASP.NET Core - 配置(Configuration)



ASP.NET Core 系列:
目录:ASP.NET Core 系列总结
上一篇:ASP.NET Core - 配置系统之配置提供程序

原文链接:https://www.cnblogs.com/wewant/p/17110770.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号