在我之前介紹的混合式開發框架中,其界面是基於Winform的實現方式,后台使用Web API、WCF服務以及直接連接數據庫的幾種方式混合式接入,在Web項目中我們也可以采用這種方式實現混合式的接入方式,雖然Web API或者WCF方式的調用,相對直接連接數據庫方式,響應效率上略差一些,不過擴展性強,也可以調動更多的設備接入,包括移動應用接入,網站接入,Winfrom客戶端接入,這樣可以使得服務邏輯相對獨立,負責提供接口即可。這種方式中最有代表性的就是當前Web API的廣泛應用,促進了各個接入端的快速開發和獨立維護,極大提高了並行開發的速度和效率。在企業中,我們可以合理規范好各種業務服務的Web API接口,各個應用接入端可以獨立開發,也可以交給外包團隊進行開發即可。
1、Winform混合式接入方式回顧
從一開始,我們的Web API 的設計目的就是為了給各種不同的應用進行接入的,例如需要接入Winform客戶端、APP程序、網站程序、以及微信應用等等,由於Web API層作為一個公共的接口層,我們就很好保證了各個界面應用層的數據一致性。
上圖介紹了各種應用在Web API的接口層之上,一般情況下,我們這層的接口都是提供標准的各種接口,以及對身份的認證處理等等,在Web API層更多考慮的業務范疇的相關接口,而在各個界面層,考慮的是如何對Web API進行進一步的封裝,以方便調用,雖然Winform和Web調用Web API的機制有所不同,不過我們還是可以對Web API的客戶端封裝層進行重用的。
在Winfrom界面調用混合式接入的接口方式,它的示意圖如下所示,主要的思路是通過一個統一的門面層Facade接口層進行服務提供,以及客戶端調用的封裝處理接口。
隨着Web API層的廣泛使用,這種方式帶來了非常大的靈活性,通過在框架層面對各個層的基類進行封裝,可以大大簡化所需的編碼,以及提供統一、豐富的基礎接口供調用。
由於Winform調用Web API的時候,客戶端對Web API層進行了一個簡單的包裝,這種方式可以簡化對Web API接口的使用,只需要通過調用封裝類,並傳入相關的參數就可以獲得序列化后的對象(包括基礎對象和自定義類對象)。
這種封裝的方式,由於對基類的統一實現,以及提供對URL地址、參數的組裝等處理,非常利於Winform界面后台代碼進行調用 ,加快Winfom界面功能的開發。例如我們從進一步細化的架構圖上,可以看到整體各個層的一些基類(綠色部分)。
在基於Web API層的構建上,我們提供了Web API服務層的提供了BaseApiController和BusinessController<B, T> 的Web API控制器基類對常規的業務處理進行封裝;在Web API服務調用層上,我們提供了BaseApiService<T>的基類進行封裝常規接口;
同時提供IBaseService<T>的Facade門面層的統一接口,以及CallerFactory<T>的調用方式供Winform后台代碼進行接口調用。
這種在Web API的基礎上進行接口的封裝,可以極大簡化接口的調用,同時也可以提供給Web端的后台控制器使用,非常便於使用,下面就介紹在Web項目中進行混合式接入的實現過程。
2、Web混合式接入介紹
參照Winform混合式接入的方式,我們也可以利用這種方式應用於Web框架上,具體的分層關系如下所示。
上圖整合了兩種非常常用的接入方式:Web API服務接入、直接連接數據庫的接入,一種具有非常強大的特性,一種具有快速的訪問效率,各有其應用場景,我們在不同的業務環境進行配置,使其適應我們實際的應用即可,一般情況下,我們建議采用Web API方式進行構建整個業務系統的生態鏈。
Web API的接口調用,可以通過兩種方式進行,一種是采用純JS框架,類似AngularJS的方式,通過其控制器進行相關接口的調用;還有一種方式,采用Asp.NET的MVC方式,前端界面通過JS調用后端的控制器實現數據處理,具體邏輯有后端邏輯控制器進行Web API的處理,我們這里采用后者,以實現較為彈性的處理。
相對Winform來說,Web上的混合方式接入相對復雜一些,雖然Winform的界面類似Web的MVC中的視圖HTML代碼,Winform后台邏輯代碼類似視圖的控制器對象,但是確實麻煩一些,相當於我們還需要在Web界面的后台控制器Controller上在封裝下相關的處理接口。
在整個基於混合式接入方式的Web 項目中,對於Web API接口的使用,整個項目的結構如下所示。
有了這些圖示的說明,我們應該對整體有一個大概的了解,對於進一步的細節問題,我們可能依然不是太清楚,需要以具體的項目代碼工程進行介紹。
1)對於數據庫層
我們可以考慮的是多種數據庫的接入支持,如SQLServer、Oracle、SQLite、Access,或者PostgreSQL的支持,這些都是基於關系型數據庫的支持,具有很好的可替代性和標准一致性。
它們可以通過遵循統一的SQL或者部分自定義的SQL語句進行,或者通過存儲過程實現,均可以實現相應的功能。
對於數據庫不同的支持方案,我這里采用了Enterprise Library的數據庫訪問組件進行一致性的支持,這樣可以降低各個不同數據庫模型的處理,統一使用這種組件訪問方式,實現不同數據庫的訪問。
2)對於業務邏輯層
業務邏輯層,是有幾個不同的層進行綜合的使用。如項目中的核心層如下所示,包括了業務邏輯層BLL、數據訪問層DAL(不同的實現層)、數據訪問接口層IDAL、以及傳遞數據的Entity實體層。
這些模塊,在各個層上都有標准的基類用來實現對接口或者功能的封裝處理。
如BLL層的繼承關系如下
/// <summary> /// 基於BootStrap的圖標 /// </summary> public class BootstrapIcon : BaseBLL<BootstrapIconInfo>
如IDAL層的繼承關系如下
/// <summary> /// 基於BootStrap的圖標 /// </summary> public interface IBootstrapIcon : IBaseDAL<BootstrapIconInfo>
基於Oracle的數據訪問層在DALOracle里面,我們看到起繼承關系如下。
/// <summary> /// 基於BootStrap的圖標 /// </summary> public class BootstrapIcon : BaseDALOracle<BootstrapIconInfo>, IBootstrapIcon
實體層繼承關系如下所示。
/// <summary> /// 基於BootStrap的圖標 /// </summary> public class BootstrapIconInfo : BaseEntity
這些模塊,由於有了基類的封裝處理,多數邏輯不用再重寫代碼,關於它們具體的內容,可以參考之前的開發框架介紹文章了解,這里不再贅述,主要用來介紹其他模塊層的繼承關系。
3)對於Web API服務層
Web API如果業務模塊比較多,可以參考我上篇隨筆《Web API項目中使用Area對業務進行分類管理》使用Area區域對業務進行分類管理,一般情況下,我們為每個Web API的接口類提供了基類的管理,和我們其他模塊的做法一樣。
/// <summary> /// 所有接口基類 /// </summary> [ExceptionHandling] public class BaseApiController : ApiController
以及
/// <summary> /// 本控制器基類專門為訪問數據業務對象而設的基類 /// </summary> /// <typeparam name="B">業務對象類型</typeparam> /// <typeparam name="T">實體類類型</typeparam> public class BusinessController<B, T> : BaseApiController where B : class where T : WHC.Framework.ControlUtil.BaseEntity, new()
這樣,基本的增刪改查等常規接口,我們就可以在基類里面直接調用業務邏輯類實現數據的處理,具體的業務子類這不需要重寫這些接口實現了。
/// <summary> /// 查詢數據庫,檢查是否存在指定ID的對象 /// </summary> /// <param name="id">對象的ID值</param> /// <returns>存在則返回指定的對象,否則返回Null</returns> [HttpGet] public virtual T FindByID(string id, string token) { //如果用戶token檢查不通過,則拋出MyApiException異常。 //檢查用戶是否有權限,否則拋出MyDenyAccessException異常 base.CheckAuthorized(AuthorizeKey.ViewKey, token); T info = baseBLL.FindByID(id); return info; }
對於HttpGet和HttpPost的約定,我們對於常規的獲取數據,使用前者,如果對數據發生修改,或者需要復雜類型的參數,使用POST方式處理。
子類的繼承關系如下所示
/// <summary> /// 權限系統中用戶信息管理控制器 /// </summary> public class UserController : BusinessController<User, UserInfo>
這樣這個UserController就具有了基類的一切功能,只需要實現一些特定的接口處理即可。
例如我們可以定義一個新的Web API接口,如下所示。
/// <summary> /// 通過用戶名稱獲取用戶對象 /// </summary> /// <param name="userName">用戶名稱</param> /// <param name="token">訪問令牌</param> /// <returns></returns> [HttpGet] public UserInfo GetUserByName(string userName, string token) { //令牌檢查,不通過則拋出異常 CheckResult checkResult = CheckToken(token); return BLLFactory<User>.Instance.GetUserByName(userName); }
這樣對於Web API架構來說,控制器的整個繼承關系大概如下所示。
如果使用Area區域來對業務模塊進行分類,那么整個Web API項目的結構如下所示,各個業務區域分開,有利於對業務模塊代碼的維護,其中BaseApiController和BusinessController則是對常規Web API接口的封裝處理。
4)對於Web API封裝層
為了實現Winform混合式框架和Web混合式框架的共同使用Web API服務的封裝層,那么我們需要獨立一個Web API封裝層,也就是***Caller層,包含了直接訪問數據庫方式、Web API服務接口訪問方式,或者加上WCF服務訪問方式等的封裝層。
這個層的目的是動態讀取Web API 接口的URL地址,以及封裝對Web API接口訪問的繁瑣細節,是調用者能夠簡單、快速的訪問Web API接口。
整個Web API封裝層的架構,就是基於Facade接口層進行不同的適配,如直接訪問數據庫方式、Web API服務訪問方式的適配處理,以便在客戶端調用的時候,自動從不同的接口實現實例化對象,從不同方式來獲取所需要的接口數據。
對於用戶User對象來說,我們來舉一個例子來說明Caller層之間的繼承關系。
在Facade層的接口定義如下所示。
public interface IUserService : IBaseService<UserInfo>
在WebAPI的Caller層實現類代碼如下所示。
/// <summary> /// 基於WebAPI方式的Facade接口實現類 /// </summary> public class UserCaller : BaseApiService<UserInfo>, IUserService
對於直接連接方式,實現類的代碼如下所示。
/// <summary> /// 基於傳統Winform方式,直接訪問本地數據庫的Facade接口實現類 /// </summary> public class UserCaller : BaseLocalService<UserInfo>, IUserService
這樣我們整理下它們關系如下圖所示。
對於不同的業務模塊,我們基於對應不同的Facade層接口實現不同的Caller層,這樣即使有很多項目模塊,我們單獨維護起來也方便很多,在Winform客戶端或者Web端調用Caller層的時候,需要引入對應的Caller層項目,以及業務核心層Core。
例如我們需要在使用的時候,同時引入Core層和Caller層,如下是項目中的部分引用關系。
5)對於Web 界面層
這個Web界面層,主要就是消費Facade層接口實現,用來獲取數據展示在界面上的,我們界面上通過HTML + JS Ajax的方式,實現從MVC控制器接口獲取數據,那么我們為了方便,依舊在控制器層進行抽象,以便對常規的方法抽到基類里面,這樣子類代碼就不用重復了。
這樣的改變,對於我們已有的MVC項目來說,視圖處理代碼不需要任何改變,只需要控制器對數據訪問的處理調整即可,從而實現MVC普通方式獲取數據的界面層,順利轉換到基於Web API +直接訪問數據庫兩者合一的混合式方式上。
原先直接訪問數據庫的MVC視圖控制器的設計,基本上類似於Web API 中控制器的設計過程,如下所示。
而對於MVC的Web界面層,以混合式方式來訪問數據,我們需要引入一個新的控制器來實現適配處理。
這樣構建出來的繼承關系圖,和上面Web的MVC控制器類似。
不同的是,里面調用的任何訪問數據的方法,從原來BLLFactory<T>到CallerFactorry<T>的轉換了,這樣就實現了從簡單的直接訪問數據庫方式,切換到混合式訪問數據的方式,在Web框架里面,可以配置為直接訪問數據庫,也可以配置為通過Web API方式訪問數據,非常方便。
例如繼承關系類的代碼如下所示。
/// <summary> /// 基於混合訪問方式的用戶信息控制器類 /// </summary> public class UserController : ApiBusinessController<IUserService, UserInfo>
其中對於Web 界面端的控制器,使用混合式訪問方式的后台控制器代碼如下所示。
/// <summary> /// 根據角色獲取對應的用戶 /// </summary> /// <param name="roleid">角色ID</param> /// <returns></returns> public ActionResult GetUsersByRole(string roleid) { ActionResult result = Content(""); if (!string.IsNullOrEmpty(roleid) && ValidateUtil.IsValidInt(roleid)) { List<UserInfo> roleList = CallerFactory<IUserService>.Instance.GetUsersByRole(Convert.ToInt32(roleid)); result = ToJsonContent(roleList); } return result; }
也就是從傳統的BLLFactory<User>轉換為了CallerFactory<IUserService>,整體性的接口變化很小,很容易過渡到混合式方式的訪問。
在Web界面端的視圖里面,我們基本上就是根據HTML + Ajax的Javascript方式實現數據的交互處理的,包括顯示數據,提交修改等等操作。
同樣我們可以通過JS的函數進行抽象,把基本的處理函數,放到一個類庫里面,方便界面層使用,然后引入JS文件即可。
@*腳本引用放在此處可以實現自定義函數自動提示*@ <script src="~/Scripts/CommonUtil.js"></script>
如下面所示,是調用JS自定義函數實現列表數據的綁定操作。
$("#Dept_ID").on("change", function (e) { var deptid = $("#Dept_ID").val(); BindSelect("PID", "/User/GetUserDictJson?deptId="+ deptid); });
或者刪除的JS代碼如下所示
var postData = { ids: ids }; $.post("/User/ConfirmDeleteByIds", postData, function (json) { var data = $.parseJSON(json); if (data.Success) { showTips("刪除選定的記錄成功"); Refresh();//刷新頁面數據 } else { showTips(data.ErrorMessage); } });
以及對一些JS列表樹,以及下拉列表,都可以采用JS函數實現快速的處理,如下所示。
var treeUrl = '/Function/GetFunctionJsTreeJsonByUser?userId=' + info.ID; bindJsTree("jstree_function", treeUrl); $('#lbxRoles').empty(); $.getJSON("/Role/GetRolesByUser?r=" + Math.random() + "&userid=" + info.ID, function (json) { $.each(json, function (i, item) { $('#lbxRoles').append('<option value="' + item.ID + '">' + item.Name + '</option>'); }); });
以上就是我從整個基於混合式訪問的Web項目進行講解介紹,貫穿了整個數據傳輸的路線和調用路線,當然其中還有很多細節方面有待細講,以及需要一些比較巧妙的整合封裝處理,整個目的就是希望借助混合式的訪問思路,實現多種數據接入方式的適配整合,以及最大程度簡化子類代碼的編寫,並且通過利用代碼生成工具對整體框架的各個層代碼的生成,我們關心的重點轉移到如何實現不同業務的接口上來,從而使得我們能夠快速開發復雜的應用,而且又能合理維護好各個項目的代碼。
一句話總結整個開發:簡單、統一、高效。