ABP基礎(chǔ)架構(gòu)深入探索
前言
我們將從 ASP.NET Core 的 Startup
類開始了解為什么我們需要模塊化系統(tǒng),以及 ABP 如何提供模塊化方式來(lái)配置和初始化應(yīng)用程序。然后我們將探索 ASP.NET Core 的依賴注入,以及ABP是如何使用預(yù)定義規(guī)則(predefined rules)自動(dòng)進(jìn)行依賴注入。最后,我們將了解 ASP.NET Core 的配置和選項(xiàng)框架,以及其他類庫(kù)。
以下是本文的所有主題:
- 了解模塊化
- 使用依賴注入系統(tǒng)
- 配置應(yīng)用程序
- 實(shí)現(xiàn)選項(xiàng)模式
- 日志系統(tǒng)
一、了解模塊化
模塊化是一種將大型軟件按功能分解為更小的部分,并允許每個(gè)部分通過(guò)標(biāo)準(zhǔn)化接口進(jìn)行通信。模塊化有以下主要好處:
- 模塊按規(guī)則進(jìn)行隔離后,大大降低了系統(tǒng)復(fù)雜性。
- 模塊之間松散耦合,提供了更大的靈活性。因?yàn)槟K是可組裝、可替換的。
- 因?yàn)槟K是獨(dú)立的,所以它允許跨應(yīng)用被重用。
大多數(shù)企業(yè)的軟件被設(shè)計(jì)成模塊化,但是,實(shí)現(xiàn)模塊化并不容易。ABP 框架的主要目標(biāo)之一是為模塊化提供基礎(chǔ)設(shè)施和工具。我們將在后面詳細(xì)介紹模塊化開發(fā),本節(jié)只介紹 ABP 模塊的基礎(chǔ)知識(shí)。
Startup 類
在定義ABP的模塊之前,建議先熟悉 ASP.NET Core 中的StartUp
類,我們看下ASP.NET Core 的Startup
類:
public class Startup { ????public void ConfigureServices(IServiceCollection services) ????{ ????????services.AddMvc(); ????????services.AddTransient<MyService>(); ????} ????public void Configure(IApplicationBuilder app, IWebHostEnvironment env) ????{ ????????app.UseRouting(); ????????if (env.IsDevelopment()) ????????{ ????????????app.UseDeveloperExceptionPage(); ????????} ????????app.UseEndpoints(endpoints => ????????{ ????????????endpoints.MapControllers(); ????????}); ????} }
ConfigureServices
方法用于配置服務(wù)并將新服務(wù)注冊(cè)到依賴注入系統(tǒng)。另一方面,Configure
方法用于配置 ASP.NET Core 管道中間件,用于處理 HTTP 請(qǐng)求。在應(yīng)用程序啟動(dòng)之前,我們需要在Program.cs
中配置Startup
類:
public class Program { ????public static void Main(string[] args) ????{ ????????CreateHostBuilder(args).Build().Run(); ????} ????public static IHostBuilder CreateHostBuilder(string[] args) => ????????Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => ????????????{ ????????????????webBuilder.UseStartup<Startup>(); ????????????}); }
這個(gè)Startup
類是獨(dú)一無(wú)二的,我們只有一個(gè)點(diǎn)來(lái)配置和初始化所有的服務(wù)。但是,在模塊化應(yīng)用程序中,我們希望每個(gè)模塊都能獨(dú)立配置和初始化與該模塊相關(guān)的服務(wù)。此外,一個(gè)模塊通常需要使用或依賴于其他模塊,因此模塊配置順序和初始化就非常重要了。我們來(lái)看下 ABP 的模塊是如何定義的
模塊定義
ABP 模塊是一組類型(比如類或接口),它們一同開發(fā)一同交付的。它是一個(gè)程序集(一般來(lái)說(shuō)是Visual Studio 中的一個(gè)項(xiàng)目),派生自AbpModule
,模塊類負(fù)責(zé)配置和初始化,并在必要時(shí)配置依賴模塊。
下面是一個(gè)短信發(fā)送模塊的簡(jiǎn)單定義:
using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; namespace SmsSending { ????public class SmsSendingModule : AbpModule ????{ ????????public override void ConfigureServices( ServiceConfigurationContext context) ????????{ ????????????context.Services.AddTransient<SmsService>(); ????????} ????} }
每個(gè)模塊都可以重寫ConfigureServices
方法,以便將其服務(wù)注冊(cè)到依賴注入系統(tǒng)。此示例中的SmsService
服務(wù)被注冊(cè)為瞬態(tài)生命周期。該示例和上面Startup類似。但是,大多時(shí)候,您不需要手動(dòng)注冊(cè)服務(wù),這要?dú)w功ABP 框架的按約定注冊(cè)系統(tǒng)。
OnApplicationInitialization
方法用在服務(wù)注冊(cè)完成后,并且在應(yīng)用準(zhǔn)備就緒后執(zhí)行。使用此方法,您可以在應(yīng)用啟動(dòng)時(shí)執(zhí)行任何操作。例如,您可以初始化一個(gè)服務(wù):
public class SmsSendingModule : AbpModule { ????//... ????public override void OnApplicationInitialization(ApplicationInitializationContext context) ????{ ????????var service = context.ServiceProvider.GetRequiredService<SmsService>(); ????????service.Initialize(); ????} }
這里,我們使用context.ServiceProvider
從依賴注入系統(tǒng)請(qǐng)求并初始化服務(wù)。可見(jiàn),此時(shí)服務(wù)已經(jīng)完成注冊(cè)。
您也可以將OnApplicationInitialization
方法等同于Startup
類的Configure
方法。
您可以在此處構(gòu)建 ASP.NET Core 請(qǐng)求管道。但是,通常我們會(huì)在啟動(dòng)模塊中配置請(qǐng)求管道,如下一節(jié)所述。
模塊依賴和啟動(dòng)模塊
一個(gè)業(yè)務(wù)應(yīng)用通常由多個(gè)模塊組成,ABP 框架允許您聲明模塊之間的依賴關(guān)系。一個(gè)應(yīng)用必須要有一個(gè)啟動(dòng)模塊。啟動(dòng)模塊可以依賴于其他模塊,其他模塊可以再依賴于其他模塊,以此類推。
下圖是一個(gè)簡(jiǎn)單的模塊依賴關(guān)系圖:
如果所示,如果模塊 A 依賴于模塊 B,則模塊 B 總是在模塊 A 之前初始化。這允許模塊 A 使用、設(shè)置、更改或覆蓋模塊 B 定義的配置和服務(wù)。
對(duì)于示例圖,模塊初始化的順序應(yīng)該是:G、F、E、D、B、C、A。
您不必知道確切的初始化順序;只需要知道如果你的模塊依賴于模塊xx,那么模塊xx在你的模塊之前被初始化。
ABP使用[DependsOn]
(屬性聲明)方式來(lái)定義模塊依賴:
[DependsOn(typeof(ModuleB), typeof(ModuleC))] public class ModuleA : AbpModule {???? }
這里,ModuleA
通過(guò)[DependsOn]
依賴于ModuleB
和ModuleC
。本例中,啟動(dòng)模塊ModuleA
負(fù)責(zé)設(shè)置ASP.NET Core 的請(qǐng)求管道:
[DependsOn(typeof(ModuleB), typeof(ModuleC))] public class ModuleA : AbpModule { ????//... ????public override void OnApplicationInitialization(ApplicationInitializationContext context) ????{ ????????var app = context.GetApplicationBuilder(); ????????var env = context.GetEnvironment(); ???????? ????????app.UseRouting(); ????????if (env.IsDevelopment()) ????????{ ????????????app.UseDeveloperExceptionPage(); ????????} ????????app.UseEndpoints(endpoints => ????????{ ????????????endpoints.MapControllers(); ????????}); ????} }
代碼塊和之前ASP.NET Core的 Startup類 創(chuàng)建請(qǐng)求管道相同。
context.GetApplicationBuilder()
和context.GetEnvironment()
用于從依賴注入中獲IApplicationBuilder
和IWebHostEnvironment
服務(wù)。
最后,我們?cè)?code>Startup里將ASP.NET Core 和 ABP 框架進(jìn)行集成:
public class Startup { ????public void ConfigureServices(IServiceCollection?services) ????{ ????????services.AddApplication<ModuleA>(); ????} ????public void Configure(IApplicationBuilder app) ????{ ????????app.InitializeApplication(); ????} }
services.AddApplication()
方法由 ABP 框架定義,用于ABP的模塊配置。它按順序執(zhí)行了所有模塊的ConfigureServices
方法。而app.InitializeApplication()
方法也是由 ABP 框架定義,它也是按照模塊依賴的順序來(lái)執(zhí)行所有模塊的OnApplicationInitialization
方法。
ConfigureServices
和OnApplicationInitialization
方法是模塊類中最常用的方法。
模塊生命周期
AbpModule
中定義的生命周期方法,除了上面看到的ConfigureServices和OnApplicationInitialization,下面羅列其他生命周期相關(guān)方法:
PreConfigureServices
: 這個(gè)方法在ConfigureServices方法之前被調(diào)用。它允許您配置服務(wù)之前執(zhí)行的代碼。
ConfigureServices
:這是配置模塊和注冊(cè)服務(wù)的主要方法。
PostConfigureServices
: 該方法在ConfigureServices之后調(diào)用(包括依賴于您模塊的模塊),這里可以配置服務(wù)后執(zhí)行的代碼。
OnPreApplicationInitialization
: 這個(gè)方法在OnApplicationInitialization之前被調(diào)用。在這個(gè)階段,您可以從依賴注入中解析服務(wù),因?yàn)榉?wù)已經(jīng)被初始化。
OnApplicationInitialization
:此方法用來(lái)配置 ASP.NET Core 請(qǐng)求管道并初始化您的服務(wù)。
OnPostApplicationInitialization
: 這個(gè)方法在初始化階段后被調(diào)用。
OnApplicationShutdown
:您可以根據(jù)需要自己實(shí)現(xiàn)模塊的關(guān)閉邏輯。帶Pre…
和Post…
前綴的方法與原始方法具有相同的目的。它們提供了一種在模塊之前或之后執(zhí)行的一些配置/初始化代碼,一般情況下我們很少使用到。
異步生命周期方法
本節(jié)介紹的生命周期方法是同步的。在編寫本書時(shí),ABP 框架團(tuán)隊(duì)正努力在 框架 5.1 版本引入異步生命周期方法。
如前所述,模塊類主要包含注冊(cè)和配置與該模塊相關(guān)的服務(wù)的代碼。在下一節(jié)中,我們將介紹如何使用 ABP 框架注冊(cè)服務(wù)。
二、使用依賴注入系統(tǒng)
.NET 原生依賴注入
依賴注入是一種獲取類的依賴的技術(shù),它將創(chuàng)建類與使用該類分開。
假設(shè)我們有一個(gè)UserRegistrationService
類,它調(diào)用SmsService
類來(lái)發(fā)送驗(yàn)證短信,如下:
public class UserRegistrationService { ????private readonly SmsService _smsService; ????public UserRegistrationService(SmsService smsService) ????{ ????????_smsService = smsService; ????} ????public async Task RegisterAsync( ????????string username, ????????string password, ????????string phoneNumber) ????{ ????????//...save user in the database ????????await _smsService.SendAsync( ????????????phoneNumber, ????????????"Your verification code: 1234" ????????); ????} }
這里的SmsService
使用構(gòu)造函數(shù)注入來(lái)獲取實(shí)例。也就是說(shuō),依賴注入系統(tǒng)會(huì)自動(dòng)幫我們實(shí)例化類的依賴項(xiàng),并將它們賦值給我們的_smsService。
注意:ABP采用的是ASP.NET Core原生的依賴注入框架,他自己并沒(méi)有發(fā)明依賴注入框架。
在設(shè)計(jì)服務(wù)時(shí),我們還要考慮另外一件重要的事情:服務(wù)生命周期。ASP.NET Core 為服務(wù)注冊(cè)提供了三個(gè)生命周期選項(xiàng):
- Transient(瞬態(tài)):每次您請(qǐng)求/注入服務(wù)時(shí),都會(huì)創(chuàng)建一個(gè)新實(shí)例。
- Scoped(范圍): 通常這由請(qǐng)求生命周期來(lái)評(píng)估,您只有在同一范圍內(nèi)才能共享相同的實(shí)例。
- Singleton(單例):在應(yīng)用內(nèi)有且僅有一個(gè)實(shí)例。所有請(qǐng)求都使用相同的實(shí)例。該對(duì)象在第一次請(qǐng)求創(chuàng)建。以下模塊注冊(cè)了兩個(gè)服務(wù),一個(gè)是瞬態(tài)的,另一個(gè)是單例的:
public class MyModule : AbpModule { ????public override void ConfigureServices(ServiceConfigurationContext context) ????{ ????????context.Services.AddTransient<ISmsService,?SmsService>(); ????????context.Services.AddSingleton<OtherService>(); ????} }
context.Services
的類型是IServiceCollection
,它是一個(gè)擴(kuò)展方法。
在第一個(gè)示例中使用接口注冊(cè),第二個(gè)示例使用引用類注冊(cè)為單例。
ABP的依賴注入
使用 ABP 框架時(shí),您不必考慮服務(wù)注冊(cè),這要?dú)w功于 ABP 框架獨(dú)特的服務(wù)注冊(cè)系統(tǒng)。
1.約定式注冊(cè)
在 ASP.NET Core 中,所有服務(wù)需要顯式注冊(cè)到IServiceCollection
,如上一節(jié)所示。這些注冊(cè)大多重復(fù),完全可以自動(dòng)化操作。
ABP 對(duì)于以下類型采用自動(dòng)注冊(cè):
- MVC controllers
- Razor page models
- View components
- Razor components
- SignalR hubs
- Application services
- Domain services
- Repositories以上類型均使用瞬態(tài)生命周期自動(dòng)注冊(cè)。如果您還有別的類型,可以考慮接口注冊(cè)。
2.接口注冊(cè)
您可以實(shí)現(xiàn)以下三種接口來(lái)注冊(cè):
ITransientDependency
IScopedDependency
ISingletonDependency
例如,在下面代碼塊中,我們將服務(wù)注冊(cè)為單例:
public class UserPermissionCache : ISingletonDependency { }
接口注冊(cè)很容易并且是推薦的方式,但與下面的屬性注冊(cè)相比,它有一定的局限性。
3.屬性注冊(cè)
屬性注冊(cè)更精細(xì),下面是和屬性注冊(cè)相關(guān)的配置參數(shù)
Lifetime
(enum
): 服務(wù)的生命周期,包括Singleton
,Transient
和Scoped
TryRegister
(bool
):僅當(dāng)服務(wù)尚未注冊(cè)時(shí)才注冊(cè)
ReplaceServices
(bool
):如果服務(wù)已經(jīng)注冊(cè),則替換之前的注冊(cè)
示例代碼:
using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; namespace UserManagement { ????[Dependency(ServiceLifetime.Transient, TryRegister =?true)] ????public class UserPermissionCache ????{ } }
4.接口屬性混合注冊(cè)
屬性接口一起使用。如果屬性定義了屬性,屬性比接口優(yōu)先級(jí)更高。
如果一個(gè)類可能被注入不同的類或接口,具體取決于暴露的類型。
暴露服務(wù)
當(dāng)一個(gè)類沒(méi)有實(shí)現(xiàn)接口時(shí),只能通過(guò)類引用注入。上一節(jié)中的UserPermissionCache
類就是通過(guò)注入類引用來(lái)使用的。
假設(shè)我們有一個(gè)抽象 SMS 發(fā)送的接口:
public interface ISmsService { ????Task SendAsync(string phoneNumber, string message); }
假設(shè)您要ISmsService
實(shí)現(xiàn) Azure 服務(wù):
public class AzureSmsService : ISmsService, ITransientDependency { ????public async Task SendAsync(string phoneNumber, string message) ????{ ????????//TODO: ... ????} }
這里的AzureSmsService
實(shí)現(xiàn)了ISmsService
和ITransientDependency
兩個(gè)接口。而ITransientDependency
接口才是用于自動(dòng)注冊(cè)到依賴注入中的。這里的注入主要通過(guò)命名約定來(lái)實(shí)現(xiàn),因?yàn)?code>AzureSmsService以SmsService
作為后綴結(jié)尾。我們?cè)倥e一個(gè)通過(guò)命名約定的例子,假設(shè)我們有一個(gè)實(shí)現(xiàn)多個(gè)接口的類:
public class PdfExporter: IExporter, IPdfExporter, ICanExport, ITransientDependency { }
PdfExporter
服務(wù)可以通過(guò)注入IPdfExporter
和IExporter
接口來(lái)使用,也可以直接注入PdfExporter
類引用來(lái)使用。但是,您不能使用ICanExport
接口注入它,因?yàn)槊QPdfExporter
不以CanExport
為后綴。
一旦您使用該ExposeServices
屬性來(lái)暴露服務(wù),如以下代碼塊所示:
[ExposeServices(typeof(IPdfExporter))] public class PdfExporter: IExporter, IPdfExporter, ICanExport, ITransientDependency { }
現(xiàn)在,您只能通過(guò)注入IPdfExporter
接口來(lái)使用PdfExporter
類。
我應(yīng)該為每個(gè)服務(wù)定義接口嗎?
ABP 不會(huì)強(qiáng)迫你這么做,但是通用接口來(lái)定義是最佳實(shí)踐:如果你想松散地耦合你的服務(wù)。比如,在單元測(cè)試中可以輕松模擬測(cè)試數(shù)據(jù)。
這就是為什么我們將接口與實(shí)現(xiàn)物理分離(例如,我們?cè)陧?xiàng)目中定義Application.Contracts
接口,并在Application
項(xiàng)目中實(shí)現(xiàn)它們,或者在領(lǐng)域?qū)又卸x存儲(chǔ)庫(kù)接口,在基礎(chǔ)設(shè)施層中實(shí)現(xiàn)它們)。
我們已經(jīng)了解了如何注冊(cè)和消費(fèi)服務(wù)。另外,某些服務(wù)具有選項(xiàng)配置,您需要在使用它們之前對(duì)其進(jìn)行配置。接下來(lái)的兩節(jié)將展開介紹,更多關(guān)于ABP基礎(chǔ)架構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
.Net創(chuàng)建型設(shè)計(jì)模式之工廠方法模式(Factory?Method)
這篇文章介紹了.Net設(shè)計(jì)模式之工廠方法模式(Factory?Method),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05ASP.NET在VS2022中使用Dispose釋放資源實(shí)例
這篇文章介紹了ASP.NET在VS2022中使用Dispose釋放資源實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11.Net結(jié)構(gòu)型設(shè)計(jì)模式之裝飾模式(Decorator)
這篇文章介紹了.Net結(jié)構(gòu)型設(shè)計(jì)模式之裝飾模式(Decorator),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05ASP.NET MVC把數(shù)據(jù)庫(kù)中枚舉項(xiàng)的數(shù)字轉(zhuǎn)換成文字
這篇文章介紹了ASP.NET MVC把數(shù)據(jù)庫(kù)中枚舉項(xiàng)的數(shù)字轉(zhuǎn)換成文字的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10asp.net平臺(tái)下C#實(shí)現(xiàn)Socket通信
這篇文章介紹了asp.net平臺(tái)下C#實(shí)現(xiàn)Socket通信的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01詳解ABP框架中的數(shù)據(jù)過(guò)濾器與數(shù)據(jù)傳輸對(duì)象的使用
ABP框架是一個(gè)基于ASP.NET的Web開發(fā)框架,這里我們來(lái)詳解ABP框架中的數(shù)據(jù)過(guò)濾器與數(shù)據(jù)傳輸對(duì)象的使用,需要的朋友可以參考下2016-06-06