ABP原始碼分析三十五:ABP中動態WebAPI原理解析
動態WebAPI應該算是ABP中最Magic的功能之一了吧。開發人員無須定義繼承自ApiController的類,只須重用Application Service中的類就可以對外提供WebAPI的功能,這應該算是對DRY的最佳詮釋了. 如下圖所示,一行程式碼就為所有實現了IApplicationService的型別,自動建立對應的動態WebAPI.
這麼Magic的功能是如何實現的呢?
本文為你揭開其Magic的外表。你會發現,實現如此Magic的功能,最關鍵的程式碼只有四行。
先思考一個問題:如果不從ASP.NET WebApi 的ApiController繼承,我們能實現ASP.NET WebAPi嗎?
答案:不可以. 從APIController繼承來實現我們自己的HttpController是實現ASP.NET WebApi的前提。
那麼問題又來了。我們在使用ABP框架的時候,沒有建立任何從APIController繼承的類。那麼從APIController繼承的類在哪裡?和ApplicationService中的類又有怎樣的關係?
先給出答案:ABP框架自動給ApplicationService中的類建立了“HttpController”,他們從APIController繼承。
基於上面的分析,要實現WebApi有三個問題需要解決:如何定義HttpController?路由的規則如何設定?如何啟用和呼叫HttpController中的Action?
如何定義HttpController?
ABP中ApplicationService並不是從APIController繼承,或實現IHttpController介面。為解決HttpController型別缺失的問題,ABP首先為所有的ApplicationService動態的建立一個DynamicApiController<T> ,這個類繼承自AbpApiController,其中T是介面繼承自IApplicationService。
但是DynamicApiController<T> 是一個空的類,其沒有任何Action. 這樣的HttpController類顯然無用的。那麼如何給這些動態生成的DynamicApiController<T>物件根據T(ApplicationService介面)中對應的方法新增相對應的Action?
看似複雜的問題,ABP以一種巧妙的辦法解決了,關鍵就在AbpWebApiModule中的4行程式碼(下面59-62行)。這裡簡單解釋一下ABP的做法:
1. 通過Castle建立DynamicApiController<T>的代理類,
2. 為代理類動態新增ApplicationService介面(這裡就是指T,也就是讓代理類實現了介面T,這樣通過代理類就可以訪問介面T中定義的方法),
3. 同時為代理類新增攔截器。
這樣當ABP通過Castle獲取DynamicApiController<T>例項的時候,其實得到的是DynamicApiController<T>的代理類(關鍵)。 通過DynamicApiController<T>的代理類呼叫ApplicationService的介面中的定義的方法的時候(必須通過反射的方式呼叫,因為介面T中的方法對DynamicApiController<T>例項是不可見的。但實際上是可見的,因為你得到的是從T介面繼承的DynamicApiController<T>的代理類例項,而不是DynamicApiController<T>例項本身。),會被攔截器攔截。而攔截器則呼叫真正的ApplicationService物件來執行方法(這裡也很關鍵,因為代理類中只要方法的宣告,沒有實現。所以這裡需要攔截器將其方法呼叫攔截並路由到真正的ApplicationService物件上)。對這四行程式碼不理解的話可先閱讀下文:http://www.cnblogs.com/1zhk/p/5399548.html 。
舉個例子:
假設有一個ApplicationService的介面是IFooAppication.
第59行,DynamicApiController<IFooAppication>被register到Castle容器中。
第60行,為DynamicApiController<IFooAppication>建立proxy代理,併為該代理新增介面IFooAppication。
第61行,為proxy代理新增攔截器AbpDynamicApiControllerInterceptor<IFooAppication>
路由的規則如何設定?
通過AbpWebApiModule的InitializeRoutes方法硬編碼在Abp.Web.Api的程式碼中。很明顯這裡路由使用了*萬用字元,也就是所有api/services/XXXX的請求都是有效的,都會進入WebApi的訊息管道。
如何根據routedata啟用和呼叫具體生成的DynamicApiController<T>物件?
ABP通過AbpWebApiModule的InitializeAspNetServices方法使用自定義的物件替換了預設的IhttpControllerSelector物件,IHttpActionSelector物件,IHttpControllerActivatore物件。如果瞭解ASP.Net WebApi底層工作原理的開發人員一定對這三個介面應該很熟悉。如果不瞭解的同學要先做做功課,才能明白後文的內容。
至此,大概解釋了ABP的動態WebApi的工作原理。
以下是對與動態WebAPI相關的介面和物件逐一分析。這些介面和類都圍繞著兩個中心目標:為動態Controller建立可供ASP.NET WebApi使用的描述器和選擇器(Descriptor,Selector),以及構建和儲存動態Controller的型別資訊。
首先看看ApiController和Configuration
AbpApiController:繼承了MVC的ApiController,ABP 中的WebApi Controller直接或間接的都從AbpApiController繼承。第二張圖,顯示了AbpApiController引用了哪些ABP核心類庫中的功能模組的物件。
IDynamicApiController:空介面,用於標識其實現是一個動態生成的ApiController。
DynamicApiController<T>:用作所有動態生成的ApiController的基類。
IAbpWebApiModuleConfiguration/AbpWebApiModuleConfiguration : 封裝了HttpConfiguration屬性,初始化為GlobalConfiguration.Configuration物件。 因為ASP.NET Web API在Web Host下通過ASP.Net的靜態型別GlobalConfiguration的Configuration屬性獲取到的用於配置請求處理管道的HttpConfiguration物件。ABP的動態WebApi本質上仍是ASP.NET Web API,所以這樣配置HttpConfiguration是必然的。
與Controller啟用和呼叫相關的介面和類主要有下面這些。其實都是繼承自ASP.NET WebAPi中預設的使用的物件,並重載了一些方法以支援動態APiController的發現,啟用和呼叫。
DynamicHttpControllerDescriptor : 繼承自asp.net Webapi系統的HttpControllerDescriptor,與ASP.NET WebAPI 中預設的HttpControllerDescriptor相比,其多了一個IFilter[]陣列。這樣做的原因很簡單,因為ABP中的ApiController是動態生成的,是沒有標註Filter特性的。所以ABP通過下面這種方式給動態ApiController加上Filter。
DynamicHttpActionDescriptor : 繼承自asp.net Webapi系統的ReflectedHttpActionDescriptor,與ASP.NET WebAPI 中預設的HttpActionDescriptor相比,其多了一個IFilter[]陣列。這樣做的原因和上面一致
AbpHttpControllerSelector : 繼承自asp.net Webapi系統的DefaultHttpControllerSelector。通過重寫SelectController來返回HttpControllerDescriptor, 這是ABP能動態建立APIController的關鍵。ASP.Net WebAPI 中的IHttpControllerSelector物件負責根據HttpRouteData返回HttpControllerDescriptor。HttpControllerDescriptor中封裝了controller的型別等資訊。這裡ABP通過繼承DefaultHttpControllerSelector,並重寫SelectController方法來根據HttpRouteData中的資料建立HttpControllerDescriptor物件並返回
AbpApiControllerActivator:實現了IHttpControllerActivator介面,根據controllerType生成具體的controller. 由於ABP系統使用了Castle框架來管理物件。所以有必要實現自己的IHttpControllerActivator以替換ASP.Net系統預設的實現。
AbpApiControllerActionSelector : 繼承自ASP.Net WebAPI 的 ApiControllerActionSelector。 通過重寫SelectAction來返回HttpActionDescriptor的派生類DynamicHttpActionDescriptor的例項, 這是ABP能執行動態建立的APIController的Action方法的關鍵。AbpApiControllerActionSelector 通過呼叫DynamicApiServiceNameHelper的靜態方法(傳入routedata中的serviceNameWithAction)獲取action的那麼
DynamicApiServiceNameHelper:靜態類,提供四個靜態方法。兩個方法用於校驗servicename是否合規,還有兩個方法用於servicename中獲取service和action的name。
AbpDynamicApiControllerInterceptor<T> : 實現了Castle的IInterceptor。作為動態生成的DynamicApiController<T>的攔截器,它攔截所有對action的呼叫,然後通過反射呼叫底層真實的IApplicationService物件的方法。
在傳統的asp.net webapi應用中,系統會根據路由資訊,通過反射到程式集中去匹配對應的controller的型別資訊。而在ABP中,controller的型別資訊是初始化的時候直接新增到一個Dictionary集合中的。本文第一幅圖中的程式碼乾的就是這件事。完成這個功能模組所涉及的介面和類主要有以下這些。
上圖程式碼中所示,構建DynamicHttpControllerDescriptor 的資料來源於一個DynamicApiControllerInfo物件。那麼DynamicApiControllerInfo物件又是在什麼時候怎麼構建的呢?下圖是ABP關於構建applicationService的DynamicApiControllerInfo物件所涉及的型別和介面。
DynamicApiControllerInfo:ABP用於封裝ApiController的資訊,下圖顯示了其所有的屬性。其中最關鍵的屬性就是ApiControllerType.其實就是一個DynamicApiController<T>型別,其中的T就是具體的ApplicationService介面的型別。
DynamicApiActionInfo:用於封裝動態生成的ApiController的Action的資訊:actionName,Filters, methondinfo和httpVerb。DynamicApiControllerInfo封裝了一個DynamicApiActionInfo的字典物件,用以表示這個Controller可支援的Action列表。
DynamicApiControllerManager:提供了一個Dictionary容器管理所有的DynamicApiControllerInfo物件。共有三個方法:Register方法用於將DynamicApiControllerInfo新增到Dictionary容器中,另外兩個方法用於返回DynamicApiControllerInfo。
DynamicApiControllerBuilder:提供兩個方法,一個For<T>方法通過ApiControllerBuilder為某一個application service類建立DynamicApiControllerInfo。另一個ForAll<T>方法通過BatchApiControllerBuilder為某一類application service類(這一類application service會有個共同的介面)建立DynamicApiControllerInfo。
IApiControllerBuilder<T>/ApiControllerBuilder<T>:其內部封裝了一個字典物件IDictionary<string, ApiControllerActionBuilder<T>>用於存放T的每個方法對應的ApiControllerActionBuilder物件。最後通過呼叫Build()方法生成完整的DynamicApiControllerInfo物件。這裡注意觀察IApiControllerBuilder的程式碼,他是支援鏈式程式設計的,可以通過WithFilters的方法給這個Application Service的API controller新增filter
IBatchApiControllerBuilder<T>/BatchApiControllerBuilder<T> : 為assembly中符合命名規範的介面批量生成DynamicApiControllerInfo。其最後仍然是通過ApiControllerBuilder逐個為各個application service介面建立DynamicApiControllerInfo.
如下圖,ApiControllerBuilder在構建DynamicApiControllerInfo過程中,需要呼叫ApiControllerActionBuilder物件去構建該DynamicApiControllerInfo所包含的DynamicApiActionInfo
DynamicApiControllerActionHelper:靜態類,用於獲取一個type的所有方法(property除外,object的原生方法除外,ApplicationService除外)的列表。
DynamicApiVerbHelper:根據方法名按照約定返回httpVerb。
IApiControllerActionBuilder/ApiControllerActionBuilder:用於構建DynamicApiActionInfo物件的生成器。這裡有一個注意點:如上圖,如果action的methodName是以get開頭的,預設ABP會標註其httpVerb為Get, 但是有一個例外,如果方法的引數不為primitive 型別和不可為空型別時,ABP會標註其httpVerb為Post。
AbiApiExplorer:繼承自ApiExplorer類,實現了IApiExplorer介面。其ApiDescriptions屬性既包括你自己編寫的webApi (39-44行)又包括ABP動態生成的WebApi(47 -)。
ABP通過遍歷DynamicApiControllerManager中的DynamicControllerInfo,然後在遍歷DynamicControllerInfo的DynamicApiActionInfo,為他們逐個構建ApiDescription例項。
相關推薦
ABP原始碼分析三十五:ABP中動態WebAPI原理解析
動態WebAPI應該算是ABP中最Magic的功能之一了吧。開發人員無須定義繼承自ApiController的類,只須重用Application Service中的類就可以對外提供WebAPI的功能,這應該算是對DRY的最佳詮釋了. 如下圖所示,一行程式碼就為所有實現了IApplicationService的
ABP原始碼分析三十一:ABP.AutoMapper
這個模組封裝了Automapper,使其更易於使用。 下圖描述了改模組涉及的所有類之間的關係。 AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttribute:這三個attribute用於標註一個類到另外一個類的map方向。 Auto
ABP原始碼分析三十四:ABP.Web.Mvc
ABP.Web.Mvc模組主要完成兩個任務: 第一,通過自定義的AbpController抽象基類封裝ABP核心模組中的功能,以便利的方式提供給我們建立controller使用。 第二,一些常見的基礎功能的實現。 AbpController:這是一個抽象基類,繼承自MVC Controller,是A
ABP原始碼分析三十六:ABP.Web.Api
這裡的內容和ABP 動態webapi沒有關係。除了動態webapi,ABP必然是支援使用傳統的webApi。ABP.Web.Api模組中實現了一些同意的基礎功能,以方便我們建立和使用asp.net webApi。 AbpApiController:這是一個抽象基類,繼承自ApiController,是AB
ABP原始碼分析三十二:ABP.SignalR
Realtime Realtime是ABP底層模組提供的功能,用於管理線上使用者。它是使用SignalR實現給線上使用者傳送通知的功能的前提 IOnlineClient/OnlineClient: 封裝線上使用者的資訊 OnlineClientManager/IOnlineClientManager:
ABP原始碼分析三十九:ABP.Hangfire
ABP對HangFire的整合主要是通過實現IBackgroundJobManager介面的HangfireBackgroundJobManager類完成的。 HangfireBackgroundJobManager:實現了介面IBackgroundJobManager中的方法EnqueueAsync,通過
ABP原始碼分析三十七:ABP.Web.Api Script Proxy API
ABP提供Script Proxy WebApi為所有的Dynamic WebApi生成訪問這些WebApi的JQuery代理,AngularJs代理以及TypeScriptor代理。這些個代理就是javascript指令碼,通過這些代理可以簡單的訪問Dynamic webApi。 如下例項演示一個最基本的
ABP原始碼分析二十五:EventBus
IEventData/EventData: 封裝了EventData資訊,觸發event的源物件和時間 IEventBus/EventBus: 定義和實現了了一系列註冊,登出和觸發事件處理函式的方法。EventBus定義了一個Dictionary容器來存放事件和處理該事件的物件(其實不是物件而是Factor
ABP原始碼分析二十九:ABP.MongoDb
這個Module通過建立一個MongoDbRepositoryBase<TEntity> 基類,封裝了對MongoDb資料庫的操作。 這個module通過引用MongoDB.Driver,MongoDB.Bson,MongoDB.Driver.Core,MongoDB.Driver.Legacy類
ABP原始碼分析二十七:ABP.Entity Framework
IRepository:介面定義了Repository常見的方法 AbpRepositoryBase:實現了IRepository介面的常見方法 EfRepositoryBase:實現了AbpRepositoryBase中定義的抽象方法:GetAll,Insert,Delete,Update。在實
ABP原始碼分析二十八:ABP.MemoryDB
這個模組簡單,且無實際作用。一般實際專案中都有用資料庫做持久化,用了資料庫就無法用這個MemoryDB 模組了。原因在於ABP限制了UnitOfWork的型別只能有一個(前文以作介紹),一般用了資料庫的必然要注入efUnitOfWork. 而注入了efUnitOfWork就不能在注入MemoryDbUnitO
ABP原始碼分析四十七:ABP中的異常處理
ABP 中異常處理的思路是很清晰的。一共五種型別的異常類。 AbpInitializationException用於封裝ABP初始化過程中出現的異常,只要丟擲AbpInitializationException異常就可以,無須做額外處理。這類異常往往是需要維護人員介入分析的。 其他四個異常都在AbpCon
ABP原始碼分析四十二:ZERO的身份認證
ABP Zero模組通過自定義實現Asp.Net Identity完成身份認證功能, 對Asp.Net Identity做了較大幅度的擴充套件。同時重寫了ABP核心模組中的permission功能,以實現授權。注意:ABP僅僅使用了Asp.Net Identity的身份認證功能,但沒有使用Asp.Net Id
ABP原始碼分析二十六:核心框架中的一些其他功能
本文是ABP核心專案原始碼分析的最後一篇,介紹一些前面遺漏的功能 AbpSession AbpSession: 目前這個和CLR的Session沒有什麼直接的聯絡。當然可以自定義的去實現IAbpSession使之與CLR的Session關聯 IAbpSession:定義如下圖中的四個屬性。 NullA
ABP原始碼分析四十一:ZERO的Audit,Setting,Background Job
AuditLog: 繼承自Entity<long>的實體類。封裝AuditLog的資訊。 AuditingStore: 實現了IAuditingStore介面,實現了將AuditLog的資訊儲存到資料庫的功能。其通過IRepository<AuditLog, long>例項完成對資料
ABP原始碼分析三十八: ABP.Web.Api.OData
如果對OData不熟悉的話可參考OData的初步認識一文以獲取OData的一些初步知識。 API.Odata 模組唯一用處就是提供了一個泛型版本的ODataController,實現了Controller程式碼的常用。 AbpODataEntityController<TEntity, TPrima
ABP原始碼分析二十二:Navigation
MenuDefinition:封裝了導航欄上的主選單的屬性。 MenuItemDefinition:封裝了主選單的子選單的屬性。子選單可以引用其他子選單構成一個選單樹 UserMenu/UserMenuItem:封裝了用於顯示給使用者的選單/以及子選單集合。 ABP通過MenuDefinition/M
ABP原始碼分析二十一:Feature
Feature是什麼?Feature就是對function的分類方法,其與function的關係就比如Role和User的關係一樣。 ABP中Feature具有以下屬性: 其中最重要的屬性是name,用以表示feature的Identity,一個feature一個name. 一個Feature可以有一組子F
ABP原始碼分析四十四:ZERO的配置
ABP Zero模組中需要配置的地方主要集中在三塊:配置靜態的role,配置外部認證源,以及配置本地化語言和資源。 UserManagementConfig/IUserManagementConfig:定義了一個配置項,用於配置外部認證源。預設情況下,ABP Zero使用AbpUser表作為身份
ABP原始碼分析二十四:Notification
NotificationDefinition: 用於封裝Notification Definnition 的資訊。注意和Notification 的區別,如果把Notification看成是具體的訊息內容,NotificationDefinition則是對這個訊息自身的定義(可理解為訊息的型別)。