天天看點

為了支援AOP的程式設計模式,我為.NET Core寫了一個輕量級的Interception架構[開源]一、基本原理二、安裝NuGet包三、定義Interceptor四、定義InterceptorAttribute五、以DI的方式注入代理六、如果你不喜歡IInterceptable<T>接口

ASP.NET

目錄 一、基本原理 二、安裝NuGet包 三、定義Interceptor 四、定義InterceptorAttribute 五、以DI的方式注入代理 六、如果你不喜歡IInterceptable<T>接口

和大部分針AOP/Interception的實作一樣,我們同樣采用“代理”的方式實作對方法調用的攔截和注入。如下圖所示,我們将需要以AOP方法注入的操作定義成一個個的Interceptor,并以某種方式(我采用的是最為直接的标注Attribute的形式)應用到某個類型或者方法上。在運作的時候我們為目标對象建立一個代理,我們針對代理對象的調用将會自動傳遞到目标對象。不過在目标對象最終被調用的時候,注冊的Interceptor會按照順序被先後執行。

為了支援AOP的程式設計模式,我為.NET Core寫了一個輕量級的Interception架構[開源]一、基本原理二、安裝NuGet包三、定義Interceptor四、定義InterceptorAttribute五、以DI的方式注入代理六、如果你不喜歡IInterceptable<T>接口

這個架構目前涉及到如下兩個架構,基礎的模型實作在Dora.Interception這個包中,Dora.Interception.Castle則利用Castle.DynamicProxy針對代理的建立提供了一個預設實作。

Dora.Interception

Dora.Interception.Castle

這兩個NuGet包已經上傳到nuget.org,是以我們可以直接使用它們。假設我們建立了一個空的ASP.NET Core控制台應用,我們可以通過執行如下的命名

為了支援AOP的程式設計模式,我為.NET Core寫了一個輕量級的Interception架構[開源]一、基本原理二、安裝NuGet包三、定義Interceptor四、定義InterceptorAttribute五、以DI的方式注入代理六、如果你不喜歡IInterceptable<T>接口

假設我們建立這樣一個Interceptor,它能夠捕獲後續執行過程中抛出的異常,并将異常消息寫入日志,我們将這個Interceptor命名為ErrorLogger。如下所示的就是這個ErrorLogger的完整定義。

考慮到依賴注入的使用,我們并沒有為具體的Interceptor類型定義一個接口,使用者僅僅需要按照如下的約定來定義這個Interceptor類型就可以了。對ASP.NET Core的管道設計比較熟悉的人應該可以看出這與中間件的設計是一緻的。

Interceptor具有一個這樣一個公共構造函數:它的第一個參數是一個InterceptDelegate

類型的委托,我們通過它調用後續的Interceptor或者目标對象。我們并不對後續的參數做任何限制,它們可以采用DI的方式進行注入(比如上面的loggerFactory參數)。如果不能以DI的形式提供的參數(比如參數category),在後面注冊的時候需要顯式指定。

攔截注入的功能虛線實作在一個名為InvokeAsync的方法中,該方法的需要傳回一個Task對象,并且要求方法中包含一個類型為InvocationContext

的對象,該對象表示執行代理方法的執行上下文。如下面的代碼片段所示,我們不僅僅可以得到與目前方法調用相關的上下文資訊,還可以直接利用它設定參數的值和最終傳回的值。InvokeAsync方法需要自行決定是否繼續調用後續的Interceptor和目标對象,這可以直接通過在構造函數中指定的這個InterceptDelegate

來完成。

由于構造函數和InvokeAsync方法都支援依賴注入,是以ErrorLogger也可以定義成如下的形式(ILoggerFactory 在InvokeAsync方法中注入)。

由于我們采用标注Attribute的方式,我們為這樣的Attribute定義了一個名為InterceptorAttribute的基類。針對ErrorLogger的ErrorLoggerAttribute定義如下,它的核心在與需要實作抽象方法Use并利用作為參數的IInterceptorChainBuilder

注冊對應的ErrorLogger。IInterceptorChainBuilder

中定義了一個泛型的方法使我們很容易地實作針對某個Interceptor類型的注冊。該方法的第一個參數是整數,它決定注冊的Interceptor在整個Interceptor有序清單中的位置。InterceptorAttribute中定義了對應的Order屬性。如果注冊Interceptor類型的構造還是具有不能通過依賴注入的參數,我們需要在調用Use方法的時候顯式指定(比如category)。

InterceptorAttribute可以應用在類和方法上(我不贊成将它應用到接口上),在預設情況下它的AllowMultiple

屬性為False。如果我們希望Interceptor鍊中可以包含多個相同類型的Interceptor,我們可以将AllowMultiple

屬性設定為True。值得一提的是,在AllowMultiple

屬性為False的情況下,如果類型和方法上都應用了同一個InterceptorAttribute,那麼隻會選擇應用在方法上的那一個。在如下的代碼中,我們将ErrorLoggerAttribute應用到總是會抛出異常的Invoke方法中,并且将日志類型設定為“App”。

我們依然會以DI的方式來使用上面定義的服務IFoobarService,但是毫無疑問,注入的對象必須是目标對象(FoobarService)的代理,我們注冊的Interceptor才能生效,為了達到這個目的,我們需要使用如下這個IInterceptable<T>接口,它的Proxy屬性為我們傳回需要的代理對象。

比如我們選在在MVC應用中将IFoobarService注入到Controller中,我們可以采用如下的定義方式。

接下來我們來完成這個應用餘下的部分。如下面的代碼片段所示,我們在作為啟動類Startup的ConfigureServicves方法中調用IServiceCollection的擴充方法AddInterception注冊于Interception相關的服務。為了确定ErrorLogger是否将異常資訊寫入日志,我們在Main方法中添加了針對ConsoleLoggerProvider的注冊,并選擇隻寫入類型為“App”的日志。

運作該應用後,如果我們利用浏覽器通路該應用,由于我們注冊了DeveloperExceptionPageMiddleware中間件,是以會出入如下圖所示的錯誤頁面。而服務端的控制台會顯示記錄下的錯誤日志。

為了支援AOP的程式設計模式,我為.NET Core寫了一個輕量級的Interception架構[開源]一、基本原理二、安裝NuGet包三、定義Interceptor四、定義InterceptorAttribute五、以DI的方式注入代理六、如果你不喜歡IInterceptable<T>接口

Interception自身的特質決定我們隻有注入目标對象的代理才能讓注冊的Interceptor被執行,這個問題我們是利用IInterceptable<T>接口來實作的,可能有人覺得這種方法不是很爽的話,我們還有更好的解決方案。我們先将HomeController寫成正常的形式。

接下來我們需要在Startup的ConfigureServices方法調用ServiceCollection的ToInterceptable方法即可。

目前來說,如果采用這種方法,我們需要讓注入的服務實作一個空的IInterceptable接口,因為我會利用它來确定某個對象是否需要封裝成代理,将來我會将這個限制移除。

作者:蔣金楠

微信公衆賬号:大内老A

如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識别二維碼)關注個人公衆号(原來公衆帳号蔣金楠的自媒體将會停用)。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

<a href="http://www.cnblogs.com/artech/p/dora-initerception.html" target="_blank">原文連結</a>

繼續閱讀