1. 程式人生 > >[ASP.NET Core 3框架揭祕] 配置[9]:自定義配置源

[ASP.NET Core 3框架揭祕] 配置[9]:自定義配置源

我們在前面對配置模型中預設提供的各種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"] = "[email protected]",
            ["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 == "[email protected]");
        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]:自定義配置