1. 程式人生 > >[譯]如何在ASP.NET Core中實現面向切面程式設計(AOP)

[譯]如何在ASP.NET Core中實現面向切面程式設計(AOP)

> 原文地址:[ASPECT ORIENTED PROGRAMMING USING PROXIES IN ASP.NET CORE](https://blog.zhaytam.com/2020/08/18/aspnetcore-dynamic-proxies-for-aop/) > 原文作者:ZANID HAYTAM > 譯文地址:[如何在ASP.NET Core中實現面向切面程式設計(AOP)](https://www.cnblogs.com/lwqlun/p/aop_in_asp_net_core.html) > 譯文作者:Lamond Lu ![https://blog.zhaytam.com/img/default.jpg](https://blog.zhaytam.com/img/default.jpg) # 前言 在使用了Spring Boot數月之後,  我發覺ASP.NET Core中缺失了對面向切面程式設計(AOP)的預設支援。 > 維基百科中針對AOP的定義: > > 面向切面程式設計(AOP)是一種程式設計範例,其旨在通過允許跨領域關注點的分離來提高模組化。它通過“切入點”規範指定要修改的程式碼,不修改原始碼本身的情況下,向現有程式碼提供額外行為,例如使用日誌的方式記錄為所有以"set"開頭的方法呼叫記錄。使用該方式,你可以向核心業務邏輯中追加一些不太重要的功能(例如日誌),而不會使程式碼混亂。AOP為面向切換的軟體開發奠定了基礎。 以下是AOP的一些常用場景 - 日誌審計 - 事務管理 - 安全 > 代理模式(Proxy Pattern)也常用於Mocking(例如Moq, NSubstitute等)和延時載入(Lazy Loading)(例如EF Core, NHierante等) # C#中實現AOP C#中其實已經支援AOP了,你可以快速Google搜尋一下,AOP的實現方式有2種`RealProxy`真實代理和`MarshalByRefObject`.技術上講,他們都可以在本地和遠端使用,它看起來非常的美好,直到你明白的你的所有目標物件都必須繼承`MarshalByRefObject`。僅此一點,就讓大部分人不會考慮這種實現方式。 # 更好的實現方式 幸運的是,我們在C#中可以使用一種更好的方式建立代理物件,即使用`Castle.DynamicProxy`庫。 > `Castle.DynamicProxy`是一個用於在執行時生成輕量級.NET代理的庫。生成代理物件允許你在不修改原始程式碼的情況下攔截對物件成員的呼叫,只有virtual物件成員才能被攔截。 - Castle Project 使用Castle提供的動態代理,你可以為抽象類、介面(同時提供實現)以及帶有virtual方法/屬性的普通類建立代理物件。 以下是一個例子,這裡我們假設建立了一個處理部落格文章的服務應用。 ```c# public class BlogPost { public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } public bool Disabled { get; set; } public DateTime Created { get; set; } } public interface IBlogService { void DisablePost(BlogPost post); BlogPost GetPost(int id); } public class BlogService : IBlogService { public BlogPost GetPost(int id) { return new BlogPost { Id = id, Title = "Test", Description = "Test", Disabled = false, Created = DateTime.UtcNow }; } public void DisablePost(BlogPost post) { post.Disabled = true; } } ``` 通常,你會將`BlogService`類註冊為`IBlogService`介面的實現,一切都運轉的非常正常。但是現在,你希望代理這個介面,當介面中任何方法被呼叫的時候,做點什麼事情。 這裡,我們首先建立一個攔截器物件以便攔截方法呼叫,就像`RealProxy`一樣 ```c# public class LoggingInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { Console.WriteLine($"正在呼叫方法 {invocation.TargetType}.{invocation.Method.Name}."); invocation.Proceed(); // 執行當前被攔截的方法 } } ``` 然後,我們將使用一個代理生成器生成代理物件。 ```c# var generator = new ProxyGenerator(); var actual = new BlogService(); var proxiedService = (IBlogService)proxyGenerator.CreateInterfaceProxyWithTarget(typeof(IBlogService), actual, new LoggingInterceptor()); // 使用proxiedService物件和你平常使用IBlogService物件是一樣的 ``` 現在我們就創建出了一個實現了`IBlogService`介面的代理物件,其中包含了內部實現`BlogService`。當任何一個介面方法被呼叫的時候,`LoggingInterceptor.Intercept`方法就會被呼叫,當攔截器呼叫`invocation.Proceed()`方法時,它在`BlogService`類中的具體實現方法就會被呼叫。 # 如何在ASP.NET Core中使用Castle實現AOP 在ASP.NET Core中使用Castle實現AOP的實現思路是, 始終使用ASP.NET Core的IOC容器來建立代理服務。雖然Castle專案中包含它自己的IOC容器`Castle Windor` , 使得注入代理更加的容易,但是我們暫時不使用它。 這裡,我們首先為我們的`LoggingInterceptor`新增一個簡單的依賴以展示我們如何使用ASP.NET Core自帶的DI來處理依賴問題。因為現實中,你的大部分攔截器都是需要一個或多個依賴項的。 ```c# public class LoggingInterceptor : IInterceptor { private readonly ILogger _logger; public LoggingInterceptor(ILogger logger) { _logger = logger; } public void Intercept(IInvocation invocation) { _logger.LogDebug($"Calling method {invocation.TargetType}.{invocation.Method.Name}."); invocation.Proceed(); } } ``` 第二步,我們在依賴注入容器中註冊一個單例的`ProxyGenerator`物件,以及我們即將使用的所有的攔截器物件 ```c# services.AddSingleton(new ProxyGenerator()); services.AddScoped(); ``` 最後,我們建立一個擴充套件方法`AddProxiedScoped`, 並使用它註冊其他所有服務。 ```c# public static class ServicesExtensions { public static void AddProxiedScoped(this IServiceCollection services) where TInterface : class where TImplementation : class, TInterface { services.AddScoped(); services.AddScoped(typeof(TInterface), serviceProvider => { var proxyGenerator = serviceProvider.GetRequiredService(); var actual = serviceProvider.GetRequiredService(); var interceptors = serviceProvider.GetServices().ToArray(); return proxyGenerator.CreateInterfaceProxyWithTarget(typeof(TInterface), actual, interceptors); }); } } // In ConfigureServices services.AddProxiedScoped(); ``` 這裡,讓我們看看它是如何工作的 1. 我們註冊具體實現(例如`BlogService`)。這是因為具體實現可能也需要使用依賴注入容器解決依賴問題。 2. 每當從依賴注入容器中嘗試獲取介面物件的時候: 1. 我們取得了一個`ProxyGenerator`物件的例項 2. 我們獲得了一個介面的實現例項 3. 我們獲取到了所有註冊的攔截器 4. 使用代理生成器建立介面的代理物件,這個物件中包含了一個具體實現和其使用的攔截器。 現在,我們無論何時需要一個`IBlogService`介面物件,都可以通過依賴注入容器得到一個代理物件,這個代理物件會先經過所有的攔截器,然後呼叫`BlogService`中定義的實際方法。 但是這裡,相較與`Spring`,在ASP.NET Core中實現AOP還不夠簡單直接,但是我們可以輕鬆將其轉換為簡單的“框架”,我們可以使用`Castle.DynamicProxy`的一些特定方法,來執行一些更高階的操作。 希望本篇文章對你有所幫助,Happy