前面已經實現了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開發框架目錄