4.2 多级可换源的配置(下)
前面已经实现了Json配置源的方式,以及在Startup中注册使用我们的配置源。下面我们进入重点,就是如何实现数据库方式的配置。数据表对应的实体类和DbContext代码如下,就不写数据表的结构了:)
1 public class ConfigurationSectionInfo 2 { 3 public string AppCode { get; set; } 4 public string SectionCode { get; set; } 5 public string SectionName { get; set; } 6 public string SectionString { get; set; } 7 } 8 9 public class ConfigurationContext : DbContext 10 { 11 public ConfigurationContext(DbContextOptions options) : base(options) 12 { 13 } 14 15 public DbSet<ConfigurationSectionInfo> ConfigurationSections { get; set; } 16 17 protected override void OnModelCreating(ModelBuilder modelBuilder) 18 { 19 EntityTypeBuilder<ConfigurationSectionInfo> builder = modelBuilder.Entity<ConfigurationSectionInfo>(); 20 21 builder.ToTable("CONFIGURATION_SECTION_INFO"); 22 builder.Property(c => c.AppCode).HasColumnName("APP_CODE").IsRequired(); 23 builder.Property(c => c.SectionCode).HasColumnName("SECTION_CODE").IsRequired(); 24 builder.Property(c => c.SectionName).HasColumnName("SECTION_NAME").IsRequired(); 25 builder.Property(c => c.SectionString).HasColumnName("SECTION_STRING").IsRequired(); 26 27 builder.HasKey(c => new { c.AppCode, c.SectionCode }); 28 } 29 }
在.net core的文档中,也写了一个数据库方式。但是该方式,是每一个键值对都存放到数据库中,例如一个缓存配置就包含MicroStrutLibrary:Caching:DefaultSlidingTime、MicroStrutLibrary:Caching:Type等多条记录,配置分散不说,写起来非常麻烦,也不如像上图中看起来那么有条理。
接下来就是数据库的配置源类DatabaseConfigSource,继承我们自己的基类ConfigSource,并实现GetConfigurationRoot方法。
1 [TypeName("Database", "数据库配置")] 2 public class DatabaseConfigSource : ConfigSource 3 { 4 public DatabaseConfigSource(string parameter) : base(parameter) 5 { 6 } 7 8 public override IConfigurationRoot GetConfigurationRoot() 9 { 10 AppConfigInfo config = AppConfigInfo.GetConfig(); 11 12 ConfigurationBuilder builder = new ConfigurationBuilder(); 13 builder.Add(new DbConfigurationSource(options => options.UseSqlServer(_Parameter), config.AppCode)); 14 15 return builder.Build(); 16 } 17 }
需要注意的是AppConfigInfo类,这个类我们用到的是AppCode属性,AppCode是指应用程序代码。因为我们的公共配置可以给多个应用使用,因此数据库方式获取配置时必须传入AppCode。在这里的意思是获取与应用程序(AppCode)相关的配置项。因为配置数据表中可能存在许多个应用的配置信息,我们这里只获取当前应用的配置信息。Parameter参数就是数据库链接串,可以在前面一节ConfigSource类的介绍中明显的看到。
创建ConfigurationBuilder,添加IConfigurationSource的数据库实现--DbConfigurationSource,其核心是DbConfigurationProvider。DbConfigurationSource和DbConfigurationProvider的实现如下:
1 public class DbConfigurationSource : IConfigurationSource 2 { 3 private readonly Action<DbContextOptionsBuilder> _optionsAction; 4 private readonly string appCode; 5 6 public DbConfigurationSource(Action<DbContextOptionsBuilder> optionsAction, string appCode) 7 { 8 _optionsAction = optionsAction; 9 this.appCode = appCode; 10 } 11 12 public IConfigurationProvider Build(IConfigurationBuilder builder) 13 { 14 return new DbConfigurationProvider(_optionsAction, appCode); 15 } 16 } 17 18 public class DbConfigurationProvider : ConfigurationProvider 19 { 20 private Action<DbContextOptionsBuilder> optionsAction; 21 private string appCode; 22 23 public DbConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction, string appCode) 24 { 25 this.optionsAction = optionsAction; 26 this.appCode = appCode; 27 } 28 29 public override void Load() 30 { 31 var builder = new DbContextOptionsBuilder<ConfigurationContext>(); 32 optionsAction(builder); 33 34 using (var dbContext = new ConfigurationContext(builder.Options)) 35 { 36 dbContext.Database.EnsureCreated(); 37 Data = GetConfigData(dbContext); 38 } 39 } 40 41 private IDictionary<string, string> GetConfigData(ConfigurationContext dbContext) 42 { 43 List<string> configSections = new List<string>(); 44 45 var appConfigs = dbContext.ConfigurationSections.Where(a => a.AppCode == this.appCode); 46 foreach (ConfigurationSectionInfo info in appConfigs) 47 { 48 configSections.Add("\"" + info.SectionCode + "\":{" + info.SectionString + "}"); 49 } 50 51 var defConfigs = dbContext.ConfigurationSections.Where(d => string.IsNullOrEmpty(d.AppCode) && appConfigs.Any(a => a.SectionCode == d.SectionCode)); 52 foreach (ConfigurationSectionInfo info in defConfigs) 53 { 54 configSections.Add("\"" + info.SectionCode + "\":{" + info.SectionString + "}"); 55 } 56 57 string configs = "{\"MicroStrutLibrary\":{" + string.Join(",", configSections) + "}}"; 58 59 return JsonConfigurationParser.Parse(configs); 60 } 61 }
DbConfigurationProvider程序的大体流程是:从数据库中读取与本应用(AppCode)相关的配置节,再读取所有应用为空的配置节(缺省配置节),然后所有配置节合并成为一个总的配置字符串,最后调用解析方法生成配置的Key/Value。解析的代码:
1 public static class JsonConfigurationParser 2 { 3 private static IDictionary<string, string> _data; 4 private static Stack<string> _context; 5 private static string _currentPath; 6 7 static JsonConfigurationParser() 8 { 9 _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase); 10 _context = new Stack<string>(); 11 } 12 13 public static IDictionary<string, string> Parse(string configs) 14 { 15 _data.Clear(); 16 17 var jsonConfig = JObject.Parse(configs); 18 19 VisitJObject(jsonConfig); 20 21 return _data; 22 } 23 24 private static void VisitJObject(JObject jObject) 25 { 26 foreach (var property in jObject.Properties()) 27 { 28 EnterContext(property.Name); 29 VisitProperty(property); 30 ExitContext(); 31 } 32 } 33 34 private static void VisitProperty(JProperty property) 35 { 36 VisitToken(property.Value); 37 } 38 39 private static void VisitToken(JToken token) 40 { 41 switch (token.Type) 42 { 43 case JTokenType.Object: 44 VisitJObject(token.Value<JObject>()); 45 break; 46 47 case JTokenType.Array: 48 VisitArray(token.Value<JArray>()); 49 break; 50 51 case JTokenType.Integer: 52 case JTokenType.Float: 53 case JTokenType.String: 54 case JTokenType.Boolean: 55 case JTokenType.Bytes: 56 case JTokenType.Raw: 57 case JTokenType.Null: 58 VisitPrimitive(token); 59 break; 60 default: 61 MicroStrutLibraryExceptionHelper.Throw(typeof(JsonConfigurationParser).FullName, LogLevel.Error, "类型不正确!"); 62 break; 63 } 64 } 65 66 private static void VisitArray(JArray array) 67 { 68 for (int index = 0; index < array.Count; index++) 69 { 70 EnterContext(index.ToString()); 71 VisitToken(array[index]); 72 ExitContext(); 73 } 74 } 75 76 private static void VisitPrimitive(JToken data) 77 { 78 var key = _currentPath; 79 80 MicroStrutLibraryExceptionHelper.TrueThrow(_data.ContainsKey(key), typeof(JsonConfigurationParser).FullName, LogLevel.Error, $"键值{key}重复"); 81 82 _data[key] = data.ToString(); 83 } 84 85 private static void EnterContext(string context) 86 { 87 _context.Push(context); 88 _currentPath = ConfigurationPath.Combine(_context.Reverse()); 89 } 90 91 private static void ExitContext() 92 { 93 _context.Pop(); 94 _currentPath = ConfigurationPath.Combine(_context.Reverse()); 95 } 96 }
这个解析类的代码基本照.net core的源代码复制而来。