我們在前面對配置模型中默認提供的各種IConfigurationSource實現類型進行了深入詳盡的介紹,如果它們依然不能滿足項目中的需求,我們還可以通過自定義IConfigurationSource實現類型來支持我們希望的配置源。就配置數據的持久化方式來說,將配置存儲在數據庫中應該是一種常見的方式。接下來我們會創建一個針對數據庫的IConfigurationSource實現類型,它采用Entity Framework Core來完成數據庫的存取操作。
我們將這個自定義ConfigurationSource命名為DbConfigurationSource。在正式介紹它的實現之前,我們先來看看它在項目中的應用。我們將配置保存在SQL Server數據庫中的某個數據表中,並采用Entity Framework Core來讀取它。我們將連接字符串作為配置定義在一個名為“appSettings.json”的JSON文件中。
{ "connectionStrings": { "DefaultDb": "Server = ... ; Database=...; Uid = ...; Pwd = ..." } }
在如下所示的演示程序中,我們首先創建了一個ConfigurationBuilder對象,並在它上面注冊了一個指向connectionString.json文件的JsonConfigurationSource對象。針對DbConfigurationSource對象的注冊體現在擴展方法AddDatabase上,這個方法具有兩個參數,分別代表連接字符串的名稱和初始的配置數據。前者正是connectionString.json設置的連接字符串名稱DefaultDb,后者是一個字典對象,它提供的原始配置正好可以構成一個Profile對象。在利用ConfigurationBuilde對象創建出相應的IConfiguration對象之后,我們讀取配置將其綁定為一個Profile對象。
public class Program { static void Main() { var initialSettings = new Dictionary<string, string> { ["Gender"] = "Male", ["Age"] = "18", ["ContactInfo:EmailAddress"] = "foobar@outlook.com", ["ContactInfo:PhoneNo"] = "123456789" }; var profile = new ConfigurationBuilder() .AddJsonFile("appSettings.json") .AddDatabase("DefaultDb", initialSettings) .Build() .Get<Profile>(); Debug.Assert(profile.Gender == Gender.Male); Debug.Assert(profile.Age == 18); Debug.Assert(profile.ContactInfo.EmailAddress == "foobar@outlook.com"); Debug.Assert(profile.ContactInfo.PhoneNo == "123456789"); } }
如上面的代碼片斷所示,針對DbConfigurationSource的應用僅僅體現在我們為IConfigurationBuilder對象定義的AddDatabase擴展方法上,所以使用起來是非常方便的,那么這個擴展方法背后有着怎樣的邏輯實現呢?DbConfigurationSource采用Entity Framework Core並以Code First的方式進行數據操作,如下所示的ApplicationSetting是表示基本配置項的POCO類型,我們將配置項的Key以小寫的方式存儲。另一個ApplicationSettingsContext是對應的DbContext類型。
[Table("ApplicationSettings")] public class ApplicationSetting { private string key; [Key] public string Key { get { return key; } set { key = value.ToLowerInvariant(); } } [Required] [MaxLength(512)] public string Value { get; set; } public ApplicationSetting() { } public ApplicationSetting(string key, string value) { Key = key; Value = value; } } public class ApplicationSettingsContext : DbContext { public ApplicationSettingsContext(DbContextOptions options) : base(options) { } public DbSet<ApplicationSetting> Settings { get; set; } }
如下所示的是DbConfigurationSource類型的定義,它的構造函數具有兩個參數,第一個參數類型為Action<DbContextOptionsBuilder>,我們用這個委托對象來對創建DbContext采用的DbContextOptions進行設置,另一個可選的參數用來指定一些需要自動初始化的配置項。DbConfigurationSource在重寫的Build方法中利用這兩個對象 創建一個DbConfigurationProvider對象。
public class DbConfigurationSource : IConfigurationSource { private Action<DbContextOptionsBuilder> _setup; private IDictionary<string, string> _initialSettings; public DbConfigurationSource(Action<DbContextOptionsBuilder> setup, IDictionary<string, string> initialSettings = null) { _setup = setup; _initialSettings = initialSettings; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new DbConfigurationProvider(_setup, _initialSettings); } }
DbConfigurationProvider派生於抽象類ConfigurationProvider。在重寫的Load方法中,它會根據提供的Action<DbContextOptionsBuilder>創建ApplicationSettingsContext對象,並利用它從數據庫中讀取配置數據並轉換成字典對象並賦值給代表配置字典的Data屬性 。如果數據表中沒有數據,該方法還會利用這個DbContext對象將提供的初始化配置添加到數據庫中。
public class DbConfigurationProvider: ConfigurationProvider { private readonly IDictionary<string, string> _initialSettings; private readonly Action<DbContextOptionsBuilder> _setup; public DbConfigurationProvider(Action<DbContextOptionsBuilder> setup, IDictionary<string, string> initialSettings) { _setup = setup; _initialSettings = initialSettings?? new Dictionary<string, string>() ; } public override void Load() { var builder = new DbContextOptionsBuilder<ApplicationSettingsContext>(); _setup(builder); using (ApplicationSettingsContext dbContext = new ApplicationSettingsContext(builder.Options)) { dbContext.Database.EnsureCreated(); Data = dbContext.Settings.Any() ? dbContext.Settings.ToDictionary(it => it.Key, it => it.Value, StringComparer.OrdinalIgnoreCase) : Initialize(dbContext); } } private IDictionary<string, string> Initialize( ApplicationSettingsContext dbContext) { foreach (var item in _initialSettings) { dbContext.Settings.Add(new ApplicationSetting(item.Key, item.Value)); } return _initialSettings.ToDictionary(it => it.Key, it => it.Value, StringComparer.OrdinalIgnoreCase); } }
實例演示中用來注冊DbConfigurationSource對象的擴展方法AddDatabase具有如下的定義。該方法首先調用IConfigurationBuilder對象的Build方法創建出一個IConfiguration對象,並調用該對象[A5] 的擴展方法GetConnectionString根據指定的連接字符串名稱得到完整的連接字符串。接下來我們調用構造函數創建一個DbConfigurationSource對象並注冊到ConfigurationBuilder對象上。創建DbConfigurationSource對象時指定的Action<DbContextOptionsBuilder>會完成針對連接字符串的設置。
public static class DbConfigurationExtensions { public static IConfigurationBuilder AddDatabase( this IConfigurationBuilder builder, string connectionStringName, IDictionary<string, string> initialSettings = null) { var connectionString = builder.Build() .GetConnectionString(connectionStringName); var source = new DbConfigurationSource( optionsBuilder => optionsBuilder.UseSqlServer(connectionString), initialSettings); builder.Add(source); return builder; } }
[ASP.NET Core 3框架揭秘] 配置[1]:讀取配置數據[上篇]
[ASP.NET Core 3框架揭秘] 配置[2]:讀取配置數據[下篇]
[ASP.NET Core 3框架揭秘] 配置[3]:配置模型總體設計
[ASP.NET Core 3框架揭秘] 配置[4]:將配置綁定為對象
[ASP.NET Core 3框架揭秘] 配置[5]:配置數據與數據源的實時同步
[ASP.NET Core 3框架揭秘] 配置[6]:多樣化的配置源[上篇]
[ASP.NET Core 3框架揭秘] 配置[7]:多樣化的配置源[中篇]
[ASP.NET Core 3框架揭秘] 配置[8]:多樣化的配置源[下篇]
[ASP.NET Core 3框架揭秘] 配置[9]:自定義配置源