前面已經實現了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 }
接下來就是數據庫的配置源類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的源代碼復制而來。
面向雲的.net core開發框架目錄