0.簡要介紹
在 Abp 框架當中通過各種 Configuration 來實現模塊的配置,Abp 本身提供的很多基礎設施功能的一些在運行時的行為是通過很多不同的 Configuration 來開放給用戶進行一些自定義配置的。
比如說緩存模塊,我要配置緩存的過期時間,Abp 默認是 1 個小時,但是我也可以自己來定義,直接賦值或者從配置項來讀取都是由具體使用者來控制的,所以 Abp 通過各種 Configuration 類來控制一些運行時參數。
這些 Abp 本身基礎設施的配置類都是存放在 \Abp\src\Abp\Configuration\Startup\
這個文件夾內部的,我們來看一下他們的依賴關系。
1.啟動流程
從上圖可以看到在 IAbpStartupConfiguration
內部擁有諸多引用(可能沒有列舉完成,可以在其定義看到),基本上 Abp 自己的基礎設施配置都在這里面。
那么 IAbpStartupConfiguration
自己內部的這些屬性是在哪兒初始化的呢,其實就是在之前講過的 AbpBootstrapper
的 Initialize()
內部初始化的。再看下代碼:
public virtual void Initialize()
{
try
{
// 其他代碼
IocManager.IocContainer.Install(new AbpCoreInstaller());
IocManager.Resolve<AbpStartupConfiguration>().Initialize();
// 其他代碼
}
catch (Exception ex)
{
_logger.Fatal(ex.ToString(), ex);
throw;
}
}
在 AbpCoreInstaller
類內部之前也說過,在這里面統一注入了這些 Configuration 的單例,同時解析出 AbpStartupConfiguration
,調用其 Initialzie()
方法來對自己的那些 xxxConfiguration 接口賦值,代碼如下:
public void Initialize()
{
Localization = IocManager.Resolve<ILocalizationConfiguration>();
Modules = IocManager.Resolve<IModuleConfigurations>();
Features = IocManager.Resolve<IFeatureConfiguration>();
Navigation = IocManager.Resolve<INavigationConfiguration>();
Authorization = IocManager.Resolve<IAuthorizationConfiguration>();
Validation = IocManager.Resolve<IValidationConfiguration>();
Settings = IocManager.Resolve<ISettingsConfiguration>();
UnitOfWork = IocManager.Resolve<IUnitOfWorkDefaultOptions>();
EventBus = IocManager.Resolve<IEventBusConfiguration>();
MultiTenancy = IocManager.Resolve<IMultiTenancyConfig>();
Auditing = IocManager.Resolve<IAuditingConfiguration>();
Caching = IocManager.Resolve<ICachingConfiguration>();
BackgroundJobs = IocManager.Resolve<IBackgroundJobConfiguration>();
Notifications = IocManager.Resolve<INotificationConfiguration>();
EmbeddedResources = IocManager.Resolve<IEmbeddedResourcesConfiguration>();
EntityHistory = IocManager.Resolve<IEntityHistoryConfiguration>();
CustomConfigProviders = new List<ICustomConfigProvider>();
ServiceReplaceActions = new Dictionary<Type, Action>();
}
所以,在模塊定義的基類 AbpModule
當中,早就注入了 IAbpStartupConfiguration
接口,讓你很方便的就可以在模塊的預加載的時候配置各種基礎設施的參數。舉個栗子:
public override void PreInitialize()
{
Configuration.Caching.ConfigureAll(z=>z.DefaultSlidingExpireTime = TimeSpan.FromHours(1));
}
可以看到這里我們的 Configuration 屬性其實就是 IAbpStartupConfiguration
接口。
2.代碼分析
2.1自定義模塊配置
我們可以看到 IAbpStartupConfiguration
除了自己擁有大量基礎設施的配置類,同時他還繼承一個基類叫做 DictionaryBasedConfig
,那么 Abp 框架為什么要這么寫呢?
其實這個基類的作用就是存放用戶自定義的 Configuration 類型的,細心觀察的話會發現在 AbpStartupConfiguration
的內部有一個 Get
方法,該方法就是用來獲取存儲的配置類型。
public T Get<T>()
{
// 調用基類的 GetOrCreate 方法,不存在的話直接從 IocContainer 中解析
return GetOrCreate(typeof(T).FullName, () => IocManager.Resolve<T>());
}
在 DictionaryBasedConfig
中維護了一個字典 CustomSettings
,其 Key/Value 類型為 string/object ,因為在 Abp 框架當中是不知道你自定義模塊配置類的類型的,所以存了一個 object 對象。
然后就有以下用法,首先在模塊 PreInitialize()
方法當中注入你需要注入的配置類:
public override void PreInitialize()
{
// 注入配置類
IocManager.Register<IAbpAspNetCoreConfiguration, AbpAspNetCoreConfiguration>();
// 替換服務,后面講解
Configuration.ReplaceService<IPrincipalAccessor, AspNetCorePrincipalAccessor>(DependencyLifeStyle.Transient);
Configuration.ReplaceService<IAbpAntiForgeryManager, AbpAspNetCoreAntiForgeryManager>(DependencyLifeStyle.Transient);
Configuration.ReplaceService<IClientInfoProvider, HttpContextClientInfoProvider>(DependencyLifeStyle.Transient);
}
然后針對 IModuleConfigurations
寫一個擴展方法,因為在 IModuleConfigurations
內部就有一個 IAbpAspNetCoreConfiguration
的實例,IModuleConfigurations
的注釋就說該接口是用於配置模塊的,模塊可以通過編寫擴展方法來添加自己的 Configuration 類:
public static class AbpAspNetCoreConfigurationExtensions
{
/// <summary>
/// Used to configure ABP ASP.NET Core module.
/// </summary>
public static IAbpAspNetCoreConfiguration AbpAspNetCore(this IModuleConfigurations configurations)
{
// 兩種寫法都差不多
return configurations.AbpConfiguration.GetOrCreate("AbpModule", () => IocManager.Resolve<IAbpAspNetCoreConfiguration>());
return configurations.AbpConfiguration.Get<IAbpAspNetCoreConfiguration>();
}
}
2.2 服務實現替換
在 Abp 當中允許我們替換一些他本身的一些實現,只要你是在模塊進行預加載的時候替換的話,都是可以的。而 Abp 他本身在 IAbpStartupConfiguration
當中提供了一個方法叫做 ReplaceService()
方法專門來讓你替換服務。
我們來看一下他的定義:
void ReplaceService(Type type, Action replaceAction);
emmmm,傳入一個 Type
和 Action
,咋跟我看到的不一樣呢,Ctrl + N 搜索了一下,發現在模塊里面使用的 ReplaceService()
方法是存放在 AbpStartupConfigurationExtensions
里面編寫的一個靜態方法,其定義如下:
public static void ReplaceService<TType, TImpl>(this IAbpStartupConfiguration configuration, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
where TType : class
where TImpl : class, TType
{
configuration.ReplaceService(typeof(TType), () =>
{
configuration.IocManager.Register<TType, TImpl>(lifeStyle);
});
}
我來看看,傳入一個 Type
和 一個 Action
,Type
用來調用 IAbpStartupConfiguration
的同名方法,Action
則是用來注冊組件的。
原來如此,我們再來到 IAbpStartupConfiguration.ReplaceService(Type type, Action replaceAction)
的具體實現:
public Dictionary<Type, Action> ServiceReplaceActions { get; private set; }
public void ReplaceService(Type type, Action replaceAction)
{
ServiceReplaceActions[type] = replaceAction;
}
唔,就是一個字典嘛,我們來看看在什么地方用到過它。
public override void Initialize()
{
foreach (var replaceAction in ((AbpStartupConfiguration)Configuration).ServiceReplaceActions.Values)
{
replaceAction();
}
// 其他代碼
}
最后我們看到在 AbpKernelModule
的 Initialize()
方法里面就會遍歷這個字典,來調用之前存入的 Action
。
因為 Abp 所有組件的注冊都是在模塊 Initialize()
內部來進行注冊的,而這串代碼剛好又放在 AbpKernelModule
的初始化方法的第一行就開始執行,所以確保你替換的組件能夠在 Abp 內部組件注冊前執行。
所以當你要替換 Abp 內置組件服務的時候一定要記住在模塊的 PreInitialize()
里面執行哦~
3. 擴展:Abp 支持多數據庫
如果你的 Abp 項目有多個數據庫上下文實體的時候怎么辦呢?
在 Abp 官方 Demo 當中就有說明,你可以通過替換默認的 IConnectionStringResolver
來實現不同數據庫的解析哦~,我們繼承 DefaultConnectionStringResolver
實現一個 MulitDbContextConnectionStringResolver
。
public class MulitDbContextConnectionStringResolver : DefaultConnectionStringResolver
{
public HKERPConnectionStringResolver(IAbpStartupConfiguration configuration)
: base(configuration)
{
}
public override string GetNameOrConnectionString(ConnectionStringResolveArgs args)
{
if (args["DbContextConcreteType"] as Type == typeof(ADbContext))
{
var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());
// 返回 ADbContext 的 ConnectionString
return configuration.GetConnectionString(AllConsts.ADbConnectionStringName);
}
if (args["DbContextConcreteType"] as Type == typeof(BDbContext))
{
var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());
// 返回 BDbContext 的 ConnectionString
return configuration.GetConnectionString(HKERPCRMConsts.BDbConnectionStringName);
}
// 都不是則使用默認的數據庫連接字符串
return base.GetNameOrConnectionString(args);
}
}
然后在我們的 EFCore 模塊的預加載方法當中加入以下代碼:
Configuration.ReplaceService(typeof(IConnectionStringResolver), () =>
{
IocManager.IocContainer.Register(
Component.For<IConnectionStringResolver, IDbPerTenantConnectionStringResolver>()
.ImplementedBy<MulitDbContextConnectionStringResolver>()
.LifestyleTransient()
);
});
當然你也不要忘記在后面通過 AddDbContext()
方法來把你的數據庫上下文添加到 Abp 里面去哦。
Configuration.Modules.AbpEfCore().AddDbContext<ADbContext>(options=>{ /*配置代碼*/});
Configuration.Modules.AbpEfCore().AddDbContext<BDbContext>(options=>{ /*配置代碼*/});