为了支持AOP的编程模式,我为.NET Core写了一个轻量级的Interception框架[开源]
ASP.NET Core具有一個以ServiceCollection和ServiceProvider為核心的依賴注入框架,雖然這只是一個很輕量級的框架,但是在大部分情況下能夠滿足我們的需要。不過我覺得它最缺乏的是針對AOP的支持,雖然這個依賴注入框架提供了擴(kuò)展點使我們可以很容易地實現(xiàn)與第三方框架的集成,但是我又不想“節(jié)外生枝”,為此我們趁這個周末寫了一個簡單的Interception框架來解決這個問題。通過這個命名為Dora.Interception的框架,我們可以采用一種非常簡單、直接而優(yōu)雅地(呵呵)在這個原生的DI框架上實現(xiàn)針對AOP的編程。目前這只是一個Beta(Beta1)版本,我將它放到了github上(https://github.com/jiangjinnan/Dora)。我寫這篇文章不是為了說明這個Dora.Interception的設(shè)計和實現(xiàn)原理,而是為了介紹如何利用它在一個ASP.NET Core與原生的DI框架結(jié)合實現(xiàn)AOP的編程模式。兩個實例可以從這里下載。
一、基本原理
和大部分針AOP/Interception的實現(xiàn)一樣,我們同樣采用“代理”的方式實現(xiàn)對方法調(diào)用的攔截和注入。如下圖所示,我們將需要以AOP方法注入的操作定義成一個個的Interceptor,并以某種方式(我采用的是最為直接的標(biāo)注Attribute的形式)應(yīng)用到某個類型或者方法上。在運行的時候我們?yōu)槟繕?biāo)對象創(chuàng)建一個代理,我們針對代理對象的調(diào)用將會自動傳遞到目標(biāo)對象。不過在目標(biāo)對象最終被調(diào)用的時候,注冊的Interceptor會按照順序被先后執(zhí)行。
二、安裝NuGet包
這個框架目前涉及到如下兩個框架,基礎(chǔ)的模型實現(xiàn)在Dora.Interception這個包中,Dora.Interception.Castle則利用Castle.DynamicProxy針對代理的創(chuàng)建提供了一個默認(rèn)實現(xiàn)。
Dora.Interception
Dora.Interception.Castle
這兩個NuGet包已經(jīng)上傳到nuget.org,所以我們可以直接使用它們。假設(shè)我們創(chuàng)建了一個空的ASP.NET Core控制臺應(yīng)用,我們可以通過執(zhí)行如下的命名
三、定義Interceptor
假設(shè)我們創(chuàng)建這樣一個Interceptor,它能夠捕獲后續(xù)執(zhí)行過程中拋出的異常,并將異常消息寫入日志,我們將這個Interceptor命名為ErrorLogger。如下所示的就是這個ErrorLogger的完整定義。
? 1: public?class ErrorLogger ? 2: { ? 3:???? private InterceptDelegate _next; ? 4:???? private ILogger _logger; ? 5:???? public ErrorLogger(InterceptDelegate next, ILoggerFactory loggerFactory, string category) ? 6:???? { ? 7:???????? _next???? = next; ? 8:???????? _logger?? = loggerFactory.CreateLogger(category); ? 9:???? } ?10:? ?11:???? public async Task InvokeAsync(InvocationContext context) ?12:???? { ?13:???????? try ?14:???????? { ?15:???????????? await _next(context); ?16:???????? } ?17:???????? catch (Exception ex) ?18:???????? { ?19:???????????? _logger.LogError(ex.Message); ?20:???????????? throw; ?21:???????? } ?22:???? } ?23: }考慮到依賴注入的使用,我們并沒有為具體的Interceptor類型定義一個接口,用戶僅僅需要按照如下的約定來定義這個Interceptor類型就可以了。對ASP.NET Core的管道設(shè)計比較熟悉的人應(yīng)該可以看出這與中間件的設(shè)計是一致的。
Interceptor具有一個這樣一個公共構(gòu)造函數(shù):它的第一個參數(shù)是一個InterceptDelegate 類型的委托,我們通過它調(diào)用后續(xù)的Interceptor或者目標(biāo)對象。我們并不對后續(xù)的參數(shù)做任何約束,它們可以采用DI的方式進(jìn)行注入(比如上面的loggerFactory參數(shù))。如果不能以DI的形式提供的參數(shù)(比如參數(shù)category),在后面注冊的時候需要顯式指定。
攔截注入的功能虛線實現(xiàn)在一個名為InvokeAsync的方法中,該方法的需要返回一個Task對象,并且要求方法中包含一個類型為InvocationContext 的對象,該對象表示執(zhí)行代理方法的執(zhí)行上下文。如下面的代碼片段所示,我們不僅僅可以得到與當(dāng)前方法調(diào)用相關(guān)的上下文信息,還可以直接利用它設(shè)置參數(shù)的值和最終返回的值。InvokeAsync方法需要自行決定是否繼續(xù)調(diào)用后續(xù)的Interceptor和目標(biāo)對象,這可以直接通過在構(gòu)造函數(shù)中指定的這個InterceptDelegate 來完成。
由于構(gòu)造函數(shù)和InvokeAsync方法都支持依賴注入,所以ErrorLogger也可以定義成如下的形式(ILoggerFactory 在InvokeAsync方法中注入)。
? 1: public?class ErrorLogger ? 2: { ? 3:???? private InterceptDelegate _next; ? 4:???? private?string? _category; ? 5:???? public ErrorLogger(InterceptDelegate next,? string category) ? 6:???? { ? 7:???????? _next = next; ? 8:???????? _category = category; ? 9:???? } ?10:? ?11:???? public async Task InvokeAsync(InvocationContext context, ILoggerFactory loggerFactory) ?12:???? { ?13:???????? try ?14:???????? { ?15:???????????? await _next(context); ?16:???????? } ?17:???????? catch (Exception ex) ?18:???????? { ?19:???????????? loggerFactory.CreateLogger(_category).LogError(ex.Message); ?20:???????????? throw; ?21:???????? } ?22:???? } ?23: }四、定義InterceptorAttribute
由于我們采用標(biāo)注Attribute的方式,我們?yōu)檫@樣的Attribute定義了一個名為InterceptorAttribute的基類。針對ErrorLogger的ErrorLoggerAttribute定義如下,它的核心在與需要實現(xiàn)抽象方法Use并利用作為參數(shù)的IInterceptorChainBuilder 注冊對應(yīng)的ErrorLogger。IInterceptorChainBuilder 中定義了一個泛型的方法使我們很容易地實現(xiàn)針對某個Interceptor類型的注冊。該方法的第一個參數(shù)是整數(shù),它決定注冊的Interceptor在整個Interceptor有序列表中的位置。InterceptorAttribute中定義了對應(yīng)的Order屬性。如果注冊Interceptor類型的構(gòu)造還是具有不能通過依賴注入的參數(shù),我們需要在調(diào)用Use方法的時候顯式指定(比如category)。
? 1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method, AllowMultiple = false)] ? 2: public?class ErrorLoggerAttribute : InterceptorAttribute ? 3: { ? 4:???? private?string _category; ? 5:? ? 6:???? public ErrorLoggerAttribute(string category) ? 7:???? { ? 8:???????? _category = category; ? 9:???? } ?10:???? public?override?void Use(IInterceptorChainBuilder builder) ?11:???? { ?12:???????? builder.Use<ErrorLogger>(this.Order, _category); ?13:???? } ?14: }InterceptorAttribute可以應(yīng)用在類和方法上(我不贊成將它應(yīng)用到接口上),在默認(rèn)情況下它的AllowMultiple 屬性為False。如果我們希望Interceptor鏈中可以包含多個相同類型的Interceptor,我們可以將AllowMultiple 屬性設(shè)置為True。值得一提的是,在AllowMultiple 屬性為False的情況下,如果類型和方法上都應(yīng)用了同一個InterceptorAttribute,那么只會選擇應(yīng)用在方法上的那一個。在如下的代碼中,我們將ErrorLoggerAttribute應(yīng)用到總是會拋出異常的Invoke方法中,并且將日志類型設(shè)置為“App”。
? 1: public?interface IFoobarService ? 2: { ? 3:???? void Invoke(); ? 4: } ? 5:? ? 6: public?class FoobarService : IFoobarService ? 7: { ? 8:??? [ErrorLogger("App")] ? 9:???? public?void Invoke() ?10:???? { ?11:???????? throw?new InvalidOperationException("Manually thrown exception!"); ?12:???? } ?13: }五、以DI的方式注入代理
我們依然會以DI的方式來使用上面定義的服務(wù)IFoobarService,但是毫無疑問,注入的對象必須是目標(biāo)對象(FoobarService)的代理,我們注冊的Interceptor才能生效,為了達(dá)到這個目的,我們需要使用如下這個IInterceptable<T>接口,它的Proxy屬性為我們返回需要的代理對象。
? 1: namespace Dora.Interception ? 2: { ? 3:???? public?interface IInterceptable<T> where T : class ? 4:???? { ? 5:???????? T Proxy { get; } ? 6:???? } ? 7: }比如我們選在在MVC應(yīng)用中將IFoobarService注入到Controller中,我們可以采用如下的定義方式。
? 1: public?class HomeController ? 2: { ? 3:???? private IFoobarService _service; ? 4:???? public HomeController(IInterceptable<IFoobarService> interceptable) ? 5:???? { ? 6:???????? _service = interceptable.Proxy; ? 7:???? } ? 8:???? [HttpGet("/")] ? 9:???? public?string Index() ?10:???? { ?11:???????? _service.Invoke(); ?12:???????? return?"Hello World"; ?13:???? } ?14: }接下來我們來完成這個應(yīng)用余下的部分。如下面的代碼片段所示,我們在作為啟動類Startup的ConfigureServicves方法中調(diào)用IServiceCollection的擴(kuò)展方法AddInterception注冊于Interception相關(guān)的服務(wù)。為了確定ErrorLogger是否將異常信息寫入日志,我們在Main方法中添加了針對ConsoleLoggerProvider的注冊,并選擇只寫入類型為“App”的日志。
? 1: public?class Program ? 2: { ? 3:???? public?static?void Main(string[] args) ? 4:???? { ? 5:???????? new WebHostBuilder() ? 6:???????????? .ConfigureLogging(factory=>factory.AddConsole((category, level)=>category == "App")) ? 7:???????????? .UseKestrel() ? 8:???????????? .UseStartup<Startup>() ? 9:???????????? .Build() ?10:???????????? .Run(); ?11:???? } ?12: } ?13:? ?14: public?class Startup ?15: { ?16:???? public?void ConfigureServices(IServiceCollection services) ?17:???? { ?18:???????? services ?19:???????????? .AddInterception() ?20:???????????? .AddScoped<IFoobarService, FoobarService>() ?21:???????????? .AddMvc(); ?22:???? } ?23:? ?24:???? public?void Configure(IApplicationBuilder app) ?25:???? { ?26:???????? app.UseDeveloperExceptionPage() ?27:???????????? .UseMvc(); ?28:???? } ?29: }運行該應(yīng)用后,如果我們利用瀏覽器訪問該應(yīng)用,由于我們注冊了DeveloperExceptionPageMiddleware中間件,所以會出入如下圖所示的錯誤頁面。而服務(wù)端的控制臺會顯示記錄下的錯誤日志。
六、如果你不喜歡IInterceptable<T>接口
Interception自身的特質(zhì)決定我們只有注入目標(biāo)對象的代理才能讓注冊的Interceptor被執(zhí)行,這個問題我們是利用IInterceptable<T>接口來實現(xiàn)的,可能有人覺得這種方法不是很爽的話,我們還有更好的解決方案。我們先將HomeController寫成正常的形式。
? 1: public?class HomeController ? 2: { ? 3:???? private IFoobarService _service; ? 4:???? public HomeController(IFoobarService service) ? 5:???? { ? 6:???????? _service = service; ? 7:???? } ? 8:???? [HttpGet("/")] ? 9:???? public?string Index() ?10:???? { ?11:???????? _service.Invoke(); ?12:???????? return?"Hello World"; ?13:???? } ?14: }接下來我們需要在Startup的ConfigureServices方法調(diào)用ServiceCollection的ToInterceptable方法即可。
? 1: public?class Startup ? 2: { ? 3:???? public?void ConfigureServices(IServiceCollection services) ? 4:???? { ? 5:???????? services ? 6:???????????? .AddInterception() ? 7:???????????? .AddScoped<IFoobarService, FoobarService>() ? 8:???????????? .AddMvc(); ? 9:???????? services.ToInterceptable(); ?10:???? } ?11:? ?12:???? public?void Configure(IApplicationBuilder app) ?13:???? { ?14:???????? app.UseDeveloperExceptionPage() ?15:???????????? .UseMvc(); ?16:???? } ?17: }目前來說,如果采用這種方法,我們需要讓注入的服務(wù)實現(xiàn)一個空的IInterceptable接口,因為我會利用它來確定某個對象是否需要封裝成代理,將來我會將這個限制移除。
? 1: public?class FoobarService : IFoobarService, IInterceptable ? 2: { ? 3:???? [ErrorLogger("App")] ? 4:???? public?void Invoke() ? 5:???? { ? 6:???????? throw?new InvalidOperationException("Manually thrown exception!"); ? 7:???? } ? 8: }原文地址:http://www.cnblogs.com/artech/p/dora-initerception.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的为了支持AOP的编程模式,我为.NET Core写了一个轻量级的Interception框架[开源]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从事件和DDD入手来构建微服务
- 下一篇: asp.net core 认证及简单集群