對服務來說,一般都會用到數據庫。而今,在微軟的大環境下,使用 EF 的人肯定會越來越多。但是,使用 EF 有個問題,一個是使用缺省的構造函數,缺省從 ConfigurationManager.ConnectionStrings
中獲取數據庫連接;另外一種就是在構造的時候,手工指定數據庫連接字符串。
對開發者來說,最好的辦法就是不去管它,直接用缺省的構造函數就好。但是插件式的開發,系統怎么知道你數據庫的連接字符串放在哪呀?主要的問題就在於其數據庫連接字符串,並沒有添加到 Web.Config 文件中,所以使用缺省構造函數,會出現無法找到配置的錯誤。
有個最簡單的解決辦法:可以選擇把數據庫連接字符串放到 Web.config 中,這樣就能解決所有問題。可這樣做,插件的配置侵入到主站了!但是,話說回來,我相信大部分用 WebApi 框架的人,都是這樣干的。這樣用,日后模塊自己的數據庫連接字符串增刪改升級的時候,還得更改主站的配置。
這是繞不過去的一個坎!把本來一個的配置分散到兩個地方,每次變動就必須修改這兩處地方。日后維護的時候,這就是個坑!在知道的人離職后,后續的人根本就找不到問題原因。
插件管理自己的數據庫連接字符串
理想的情況下,我們可以在第一次系統初始化的時候,給 ConfigurationManager.ConnectionStrings
這個集合中添加我們的數據庫配置,這樣就能在解析的時候找到配置了。順着這個思路繼續想,微軟的反射很強大,可以更改本來不可以更新的數據。所以,就有了下面這段代碼:
public void Configurate(System.Configuration.Configuration[] configurations)
{
var meta = ((TypeX)ConfigurationManager.ConnectionStrings.GetType()).GetField("bReadOnly");
meta.SetValue(ConfigurationManager.ConnectionStrings, false);
configurations.SelectMany(p => p.ConnectionStrings.ConnectionStrings.OfType<ConnectionStringSettings>())
.Where(p => ConfigurationManager.ConnectionStrings.IndexOf(p) < 0)
.ForEach(ConfigurationManager.ConnectionStrings.Add);
meta.SetValue(ConfigurationManager.ConnectionStrings, true);
}
這段代碼的意思是,把各個模塊的數據庫連接字符串文件加載到列表中,然后通過反射開啟賦值,加到 ConfigurationManager.ConnectionStrings
集合中。
要完成這個功能,我們尚需做的就是找到每個模塊的數據庫連接字符串文件,然后加載獲得上面這個函數的參數。考慮到我們為每個模塊定義了一個配置文件,所以這里為其添加一個配置就好了:
<?xml version="1.0" encoding="UTF-8"?>
<configuration enabled="true">
<description>授權支持插件</description>
<assemblies>
<add type="relative">bin/Intime.AuthorizationService.dll</add>
<add type="relative">bin/Intime.AuthorizationService.Services.dll</add>
<add type="relative">bin/Intime.AuthorizationService.Data.dll</add>
<add type="relative">bin/Intime.AuthorizationService.Data.Repository.dll</add>
</assembiles>
<appConfig type="relative">bin/Intime.AuthorizationService.Data.Repository.dll.config</appConfig>
</configuration>
參考 appConfig
配置節,我們可以得到模塊的配置文件絕對路徑,再和 DynamicModules
配合,用下面這段代碼就可以得到 System.Configuration.Configuration[] configurations
這個參數了:
public void Configurate(HttpConfiguration configuration)
{
var items = ServiceLocator.Current.GetAllInstances<IAppConfigHandler>().ToArray();
if (items.Any())
{
var data = DynamicModules.Instance
.Modules
.Where(p => !string.IsNullOrWhiteSpace(p.Configuration.AppConfig))
.Select(p =>
{
var fullFilePath = Path.Combine(p.Path, p.Configuration.AppConfig);
return ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap { ExeConfigFilename = fullFilePath }, ConfigurationUserLevel.None);
})
.ToArray();
items.ForEach(p => p.Configurate(data));
}
}
可以看到,在這里我用了 IAppConfigHandler
接口,這樣就可以擴展其他的配置了,不僅限於 ConnectionStrings
。
另外,上面這段代碼缺點東西,自行腦補吧,很容易就看明白的。