WebApi 插件式構建方案
公司要推行服務化,不可能都整合在一個解決方案內,因而想到了插件式的構建方案。最終定型選擇基於 WebApi 構建服務化,之所以不使用 WCF 是因為不符合 RESTful 風格,並且對 OData 開源查詢協議支持不是太好。
插件化構建的兩種思路
- 不進行二次開發,直接把編譯完成的程序集放到
bin
目錄即可。 - 針對程序集尋址做擴展,把插件程序集放到
bin
的二級目錄。
在這里,我對兩者的優缺點進行一下分析:
- 第一種方案:如果是臨時方案我沒有意見,畢竟是過渡用的,業務為重嘛。但是,作為萬人敬仰的程序員,你他娘的也太懶了吧!那么多程序集放在一起,萬一堆太多堆太久生蛆了腫么辦?萬一哪家小朋友不聽話,把自己埋了腫么辦?
- 第二種方案:嗯,還得開發,而且弄不好還會把項目搞砸!后果很嚴重,領導很生氣!腫么辦?無奈 + 無解。。。
其實不用那么麻煩,用別人開發的一套成熟的東西就好辦了。沒有?好,坐下來,聽我慢慢說。
WebApi 啟動流程簡介
任何基於 Http 的服務端程序,啟動管道和請求響應管道指定是跑不了的。
- 啟動管道是服務自我配置的過程:這個時候 XML 配置和一些默認的約定就起作用了,比如 IOC 容器 Unity 啟動時會從
unity.config
文件中讀取映射配置,WebApi 框架缺省情況下會尋找從ApiController
繼承並且類名稱結尾是Controller
的類作為控制器。 - 請求響應管道是服務器接到客戶端請求后的處理流程:可以想象一下我們公司里的審批流,你要請假了,必須得小組長先批准,然后總監批准,接着人力審核,財務扣錢等等。在這里,一般會首先進行認證,授權和附加信息獲取等流程,最后才會交給咱程序員寫的邏輯去處理。
注:對於自承載的服務,也會有一個類似的流程。
流程一:獲取程序集列表
掛載在 IIS 上時,會從 BuildManager
中獲取程序集列表:這個列表是經過 ASP.NET 程序底層優化過的,能夠獲取所有的程序集信息。自承載時,只是簡單粗暴地調用 Assembly
類的靜態方法,估計會有一些問題,我沒試過,這里不做討論。考慮到絕大多數人還是使用 IIS 掛載的,說太詳細沒什么意義,我也沒那個精力。
.Net 4.0 時代,ASP.NET 程序啟動時,可以給程序集加上一個 Attribute(PreApplicationStartMethod),可以添加一些啟動后不能變更的工作。這里我們要做的工作,就是把那些不在 bin
根目錄的程序集加載進去,通過 AddReferencedAssembly
靜態方法添加即可。
注意:
- 不要加載一個程序集的不同版本,這會對程序的運行產生不可預知的影響。
- ASP.NET 的某些變量還沒初始化,你的某些想法不會成立。
這里,我在加載了程序集的基礎上,又公開了一些模塊的元數據信息:
- 插件的初始配置:插件名稱、路徑、啟動順序、可用性、插件可見性、數據庫連接字符串。
- 插件運行需要加載的程序集列表。
這些信息使得我在服務運行后,有了較大的定制空間。比如后面根據模塊名稱產生模塊前綴 RESTful 服務;還有在另外的管理網站,統一管理服務的幫助系統等,源數據都是以其為依據的。
注:插件初始配置中,可用性決定了這個插件是否加載,這是總開關。
流程二:IOC 容器初始化
現代的網站,很難想象如果沒有 IOC ,程序的復雜性會提升多少。所以我把 IOC 的初始化提升到最重要的位置。但是考慮到 IOC 容器不同人會有不同的選擇,我這里只依照微軟 Unity
為藍本做講解。至於各個插件的使用上,統一采用 CommonServiceLocator
做 IOC 容器接口。
這里我解釋下為什么使用 CommonServiceLocator
做使用的接口方案:
- 接口穩定:君不見微軟這么多年來,從來沒更新過這貨嗎?
- 適配器豐富:可以這么說,.Net 世界的 IOC 容器,幾乎都有針對的適配器。所以說,不用考慮將來更換 IOC 容器實現時,各個插件的更新問題。這根本就不是事!
- 公司一直在在用
Unity
,而且還專門做了接口實現,沒得選沒的說,不解釋。
根據流程一提供的插件路徑,約定 Configuration\unity.config
為存儲接口映射的地方,在程序啟動時依據插件啟動順序,逐個加載映射信息。
這一步完成后,就可以直接通過容器獲取接口的實例了,很好的隔離了契約和實現,同時也讓我們的程序趨於簡單化,不用再用復雜的抽象工廠了。話說使用抽象工廠的配置也不比配置 IOC 容易,還都是私有的配置,一個個實現都不同,新人剛進入環境就是一頭霧水。
流程三:集成加載數據庫連接字符串
每個插件一般都會有自己的數據庫訪問功能,因為是插件化的方案,所以此時數據庫連接字符串就很不好處理:生成后就不用再考慮連接字符串;插件自己放着就好,不用拷貝到指定的地方去。
很幸運,.Net 提供了一個叫做反射的東西,允許做一些工作后更新到 .Net 框架自帶的連接字符串容器中,這樣我們就可以在運行時不用管這個叫數據庫連接字符串的東東了。唯一注意的一點就是:各個插件提供的數據庫連接字符串名稱不能有相同的。我相信這不是什么問題!
注:這個功能的實現依賴於流程一的插件初始配置,需要添加一個連接字符串所在文件的絕對路徑的東東,以便於讓系統指導從哪里獲取這些連接字符串。每個模塊都要配置一個,當然不用數據庫操作的模塊可以忽略。
流程四:重寫的控制器獲取工廠
缺省的控制器獲取工廠,只會根據約定產生諸如 {controller}/{action}/{id}
這類的地址,那要我們插件如何是好?萬一兩家寫的地址一樣呢?小朋友會不會找不到家了呢?在前面加上 {module}
可好?
好吧,我們假設方案是可行的,我們注冊 {module}/{controller}/{action}/{id}
這樣的路由,那就需要能解析這個路由的工廠才行啊,不然我們得不到控制器,談何執行?
沒什么可說的了,實現接口就好了,參考缺省實現,我們只需要把查找的 Key 加上模塊名字就好了。
流程五:沒什么可說的了
運行你的 WebApi 服務吧,網站不用引用你控制器項目,需要的時候把生成好的程序集放到 IIS 網站下你的目錄就好了。這里推薦放在 bin
目錄下面,好處是在你更新 bin
下面的文件時,IIS 會重新啟動這個網站。