在基于MVC的Web項目中使用Web API和直接連接兩種方式混合式接入

在我之前介紹的混合式開發(fā)框架中陷嘴,其界面是基于Winform的實現(xiàn)方式念脯,后臺使用Web API档押、WCF服務以及直接連接數(shù)據庫的幾種方式混合式接入,在Web項目中我們也可以采用這種方式實現(xiàn)混合式的接入方式曙寡,雖然Web API或者WCF方式的調用,相對直接連接數(shù)據庫方式寇荧,響應效率上略差一些举庶,不過擴展性強,也可以調動更多的設備接入揩抡,包括移動應用接入户侥,網站接入罩缴,Winfrom客戶端接入卒蘸,這樣可以使得服務邏輯相對獨立鬓照,負責提供接口即可薛匪。這種方式中最有代表性的就是當前Web API的廣泛應用挟冠,促進了各個接入端的快速開發(fā)和獨立維護噩咪,極大提高了并行開發(fā)的速度和效率弄慰。在企業(yè)中勺远,我們可以合理規(guī)范好各種業(yè)務服務的Web API接口署尤,各個應用接入端可以獨立開發(fā)耙替,也可以交給外包團隊進行開發(fā)即可。

1曹体、Winform混合式接入方式回顧

從一開始俗扇,我們的Web API 的設計目的就是為了給各種不同的應用進行接入的,例如需要接入Winform客戶端箕别、APP程序铜幽、網站程序滞谢、以及微信應用等等,由于Web API層作為一個公共的接口層除抛,我們就很好保證了各個界面應用層的數(shù)據一致性狮杨。



上圖介紹了各種應用在Web API的接口層之上,一般情況下到忽,我們這層的接口都是提供標準的各種接口橄教,以及對身份的認證處理等等,在Web API層更多考慮的業(yè)務范疇的相關接口喘漏,而在各個界面層护蝶,考慮的是如何對Web API進行進一步的封裝,以方便調用翩迈,雖然Winform和Web調用Web API的機制有所不同持灰,不過我們還是可以對Web API的客戶端封裝層進行重用的。
在Winfrom界面調用混合式接入的接口方式帽馋,它的示意圖如下所示搅方,主要的思路是通過一個統(tǒng)一的門面層Facade接口層進行服務提供,以及客戶端調用的封裝處理接口绽族。



隨著Web API層的廣泛使用姨涡,這種方式帶來了非常大的靈活性,通過在框架層面對各個層的基類進行封裝吧慢,可以大大簡化所需的編碼涛漂,以及提供統(tǒng)一、豐富的基礎接口供調用检诗。
由于Winform調用Web API的時候匈仗,客戶端對Web API層進行了一個簡單的包裝,這種方式可以簡化對Web API接口的使用逢慌,只需要通過調用封裝類悠轩,并傳入相關的參數(shù)就可以獲得序列化后的對象(包括基礎對象和自定義類對象)。

這種封裝的方式攻泼,由于對基類的統(tǒng)一實現(xiàn)火架,以及提供對URL地址、參數(shù)的組裝等處理忙菠,非常利于Winform界面后臺代碼進行調用 何鸡,加快Winfom界面功能的開發(fā)。例如我們從進一步細化的架構圖上牛欢,可以看到整體各個層的一些基類(綠色部分)骡男。



在基于Web API層的構建上,我們提供了Web API服務層的提供了BaseApiController和BusinessController<B, T> 的Web API控制器基類對常規(guī)的業(yè)務處理進行轉賬傍睹;在Web API服務調用層上隔盛,我們提供了BaseApiService<T>的基類進行封裝常規(guī)接口犹菱;
同時提供IBaseService<T>的Facade門面層的統(tǒng)一接口,以及CallerFactory<T>的調用方式供Winform后臺代碼進行接口調用骚亿。
這種在Web API的基礎上進行接口的封裝已亥,可以極大簡化接口的調用熊赖,同時也可以提供給Web端的后臺控制器使用来屠,非常便于使用,下面就介紹在Web項目中進行混合式接入的實現(xiàn)過程震鹉。

2俱笛、Web混合式接入介紹

參照Winform混合式接入的方式,我們也可以利用這種方式應用于Web框架上传趾,具體的分層關系如下所示迎膜。



上圖整合了兩種非常常用的接入方式:Web API服務接入、直接連接數(shù)據庫的接入浆兰,一種具有非常強大的特性磕仅,一種具有快速的訪問效率,各有其應用場景簸呈,我們在不同的業(yè)務環(huán)境進行配置榕订,使其適應我們實際的應用即可,一般情況下蜕便,我們建議采用Web API方式進行構建整個業(yè)務系統(tǒng)的生態(tài)鏈劫恒。
Web API的接口調用,可以通過兩種方式進行轿腺,一種是采用純JS框架两嘴,類似AngularJS的方式,通過其控制器進行相關接口的調用族壳;還有一種方式憔辫,采用Asp.NET的MVC方式,前端界面通過JS調用后端的控制器實現(xiàn)數(shù)據處理仿荆,具體邏輯有后端邏輯控制器進行Web API的處理贰您,我們這里采用后者,以實現(xiàn)較為彈性的處理赖歌。
相對Winform來說枉圃,Web上的混合方式接入相對復雜一些,雖然Winform的界面類似Web的MVC中的視圖HTML代碼庐冯,Winform后臺邏輯代碼類似視圖的控制器對象孽亲,但是確實麻煩一些,相當于我們還需要在Web界面的后臺控制器Controller上在封裝下相關的處理接口展父。
在整個基于混合式接入方式的Web 項目中返劲,對于Web API接口的使用玲昧,整個項目的結構如下所示。



有了這些圖示的說明篮绿,我們應該對整體有一個大概的了解孵延,對于進一步的細節(jié)問題,我們可能依然不是太清楚亲配,需要以具體的項目代碼工程進行介紹尘应。

1)對于數(shù)據庫層

我們可以考慮的是多種數(shù)據庫的接入支持,如SQLServer吼虎、Oracle犬钢、SQLite、Access思灰,或者PostgreSQL的支持玷犹,這些都是基于關系型數(shù)據庫的支持,具有很好的可替代性和標準一致性洒疚。
它們可以通過遵循統(tǒng)一的SQL或者部分自定義的SQL語句進行歹颓,或者通過存儲過程實現(xiàn),均可以實現(xiàn)相應的功能油湖。
對于數(shù)據庫不同的支持方案巍扛,我這里采用了Enterprise Library的數(shù)據庫訪問組件進行一致性的支持,這樣可以降低各個不同數(shù)據庫模型的處理肺魁,統(tǒng)一使用這種組件訪問方式电湘,實現(xiàn)不同數(shù)據庫的訪問。

2)對于業(yè)務邏輯層

業(yè)務邏輯層鹅经,是有幾個不同的層進行綜合的使用寂呛。如項目中的核心層如下所示,包括了業(yè)務邏輯層BLL瘾晃、數(shù)據訪問層DAL(不同的實現(xiàn)層)贷痪、數(shù)據訪問接口層IDAL、以及傳遞數(shù)據的Entity實體層蹦误。



這些模塊劫拢,在各個層上都有標準的基類用來實現(xiàn)對接口或者功能的封裝處理。
如BLL層的繼承關系如下

    /// <summary>
    /// 基于BootStrap的圖標
    /// </summary>
    public class BootstrapIcon : BaseBLL<BootstrapIconInfo>

如IDAL層的繼承關系如下

    /// <summary>
    /// 基于BootStrap的圖標
    /// </summary>
    public interface IBootstrapIcon : IBaseDAL<BootstrapIconInfo>

基于Oracle的數(shù)據訪問層在DALOracle里面强胰,我們看到起繼承關系如下舱沧。

    /// <summary>
    /// 基于BootStrap的圖標
    /// </summary>
    public class BootstrapIcon : BaseDALOracle<BootstrapIconInfo>, IBootstrapIcon

實體層繼承關系如下所示。

    /// <summary>
    /// 基于BootStrap的圖標
    /// </summary>
    public class BootstrapIconInfo : BaseEntity

這些模塊偶洋,由于有了基類的封裝處理熟吏,多數(shù)邏輯不用再重寫代碼,關于它們具體的內容,可以參考之前的開發(fā)框架介紹文章了解牵寺,這里不再贅述悍引,主要用來介紹其他模塊層的繼承關系。

3)對于Web API服務層

Web API如果業(yè)務模塊比較多帽氓,可以參考我上篇隨筆《Web API項目中使用Area對業(yè)務進行分類管理》使用Area區(qū)域對業(yè)務進行分類管理趣斤,一般情況下,我們?yōu)槊總€Web API的接口類提供了基類的管理黎休,和我們其他模塊的做法一樣浓领。

/// <summary>
/// 所有接口基類
/// </summary>
[ExceptionHandling]
public class BaseApiController : ApiController

以及

/// <summary>
/// 本控制器基類專門為訪問數(shù)據業(yè)務對象而設的基類
/// </summary>
/// <typeparam name="B">業(yè)務對象類型</typeparam>
/// <typeparam name="T">實體類類型</typeparam>
public class BusinessController<B, T> : BaseApiController
    where B : class
    where T : WHC.Framework.ControlUtil.BaseEntity, new()

這樣,基本的增刪改查等常規(guī)接口奋渔,我們就可以在基類里面直接調用業(yè)務邏輯類實現(xiàn)數(shù)據的處理镊逝,具體的業(yè)務子類這不需要重寫這些接口實現(xiàn)了。

/// <summary>
/// 查詢數(shù)據庫,檢查是否存在指定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的約定玄渗,我們對于常規(guī)的獲取數(shù)據,使用前者狸眼,如果對數(shù)據發(fā)生修改藤树,或者需要復雜類型的參數(shù),使用POST方式處理拓萌。

子類的繼承關系如下所示

/// <summary>
/// 權限系統(tǒng)中用戶信息管理控制器
/// </summary>
public class UserController : BusinessController<User, UserInfo>

這樣這個UserController就具有了基類的一切功能岁钓,只需要實現(xiàn)一些特定的接口處理即可。

例如我們可以定義一個新的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區(qū)域來對業(yè)務模塊進行分類钧大,那么整個Web API項目的結構如下所示,各個業(yè)務區(qū)域分開罩旋,有利于對業(yè)務模塊代碼的維護啊央,其中BaseApiController和BusinessController則是對常規(guī)Web API接口的封裝處理。


4)對于Web API封裝層

為了實現(xiàn)Winform混合式框架和Web混合式框架的共同使用Web API服務的封裝層涨醋,那么我們需要獨立一個Web API封裝層瓜饥,也就是***Caller層,包含了直接訪問數(shù)據庫方式浴骂、Web API服務接口訪問方式乓土,或者加上WCF服務訪問方式等的封裝層。
這個層的目的是動態(tài)讀取Web API 接口的URL地址靠闭,以及封裝對Web API接口訪問的繁瑣細節(jié)帐我,是調用者能夠簡單坎炼、快速的訪問Web API接口。
整個Web API封裝層的架構拦键,就是基于Facade接口層進行不同的適配谣光,如直接訪問數(shù)據庫方式、Web API服務訪問方式的適配處理芬为,以便在客戶端調用的時候萄金,自動從不同的接口實現(xiàn)實例化對象,從不同方式來獲取所需要的接口數(shù)據媚朦。



對于用戶User對象來說氧敢,我們來舉一個例子來說明Caller層之間的繼承關系。
在Facade層的接口定義如下所示询张。

public interface IUserService : IBaseService<UserInfo>

在WebAPI的Caller層實現(xiàn)類代碼如下所示孙乖。

    /// <summary>
    /// 基于WebAPI方式的Facade接口實現(xiàn)類
    /// </summary>
    public class UserCaller : BaseApiService<UserInfo>, IUserService

對于直接連接方式,實現(xiàn)類的代碼如下所示份氧。

/// <summary>
/// 基于傳統(tǒng)Winform方式唯袄,直接訪問本地數(shù)據庫的Facade接口實現(xiàn)類
/// </summary>
public class UserCaller : BaseLocalService<UserInfo>, IUserService

這樣我們整理下它們關系如下圖所示。



對于不同的業(yè)務模塊蜗帜,我們基于對應不同的Facade層接口實現(xiàn)不同的Caller層恋拷,這樣即使有很多項目模塊,我們單獨維護起來也方便很多厅缺,在Winform客戶端或者Web端調用Caller層的時候蔬顾,需要引入對應的Caller層項目,以及業(yè)務核心層Core湘捎。
例如我們需要在使用的時候诀豁,同時引入Core層和Caller層,如下是項目中的部分引用關系消痛。


5)對于Web 界面層

這個Web界面層且叁,主要就是消費Facade層接口實現(xiàn),用來獲取數(shù)據展示在界面上的秩伞,我們界面上通過HTML + JS Ajax的方式逞带,實現(xiàn)從MVC控制器接口獲取數(shù)據,那么我們?yōu)榱朔奖闵葱拢琅f在控制器層進行抽象展氓,以便對常規(guī)的方法抽到基類里面,這樣子類代碼就不用重復了脸爱。
這樣的改變遇汞,對于我們已有的MVC項目來說,視圖處理代碼不需要任何改變,只需要控制器對數(shù)據訪問的處理調整即可空入,從而實現(xiàn)MVC普通方式獲取數(shù)據的界面層络它,順利轉換到基于Web API +直接訪問數(shù)據庫兩者合一的混合式方式上。
原先直接訪問數(shù)據庫的MVC視圖控制器的設計歪赢,基本上類似于Web API 中控制器的設計過程化戳,如下所示。



而對于MVC的Web界面層埋凯,以混合式方式來訪問數(shù)據点楼,我們需要引入一個新的控制器來實現(xiàn)適配處理。
這樣構建出來的繼承關系圖白对,和上面Web的MVC控制器類似掠廓。



不同的是,里面調用的任何訪問數(shù)據的方法甩恼,從原來BLLFactory<T>到CallerFactorry<T>的轉換了蟀瞧,這樣就實現(xiàn)了從簡單的直接訪問數(shù)據庫方式,切換到混合式訪問數(shù)據的方式媳拴,在Web框架里面黄橘,可以配置為直接訪問數(shù)據庫,也可以配置為通過Web API方式訪問數(shù)據屈溉,非常方便。
例如繼承關系類的代碼如下所示抬探。
/// <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;
}

也就是從傳統(tǒng)的BLLFactory<User>轉換為了CallerFactory<IUserService>小压,整體性的接口變化很小线梗,很容易過渡到混合式方式的訪問。
在Web界面端的視圖里面怠益,我們基本上就是根據HTML + Ajax的Javascript方式實現(xiàn)數(shù)據的交互處理的仪搔,包括顯示數(shù)據,提交修改等等操作蜻牢。
同樣我們可以通過JS的函數(shù)進行抽象烤咧,把基本的處理函數(shù),放到一個類庫里面抢呆,方便界面層使用煮嫌,然后引入JS文件即可。

@*腳本引用放在此處可以實現(xiàn)自定義函數(shù)自動提示*@
<script src="~/Scripts/CommonUtil.js"></script>

如下面所示抱虐,是調用JS自定義函數(shù)實現(xiàn)列表數(shù)據的綁定操作昌阿。

$("#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();//刷新頁面數(shù)據
    }
    else {
        showTips(data.ErrorMessage);
    }
});

以及對一些JS列表樹,以及下拉列表,都可以采用JS函數(shù)實現(xiàn)快速的處理懦冰,如下所示灶轰。

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項目進行講解介紹,貫穿了整個數(shù)據傳輸?shù)穆肪€和調用路線刷钢,當然其中還有很多細節(jié)方面有待細講笋颤,以及需要一些比較巧妙的整合封裝處理,整個目的就是希望借助混合式的訪問思路闯捎,實現(xiàn)多種數(shù)據接入方式的適配整合椰弊,以及最大程度簡化子類代碼的編寫,并且通過利用代碼生成工具對整體框架的各個層代碼的生成瓤鼻,我們關心的重點轉移到如何實現(xiàn)不同業(yè)務的接口上來秉版,從而使得我們能夠快速開發(fā)復雜的應用,而且又能合理維護好各個項目的代碼茬祷。

一句話總結整個開發(fā):簡單清焕、統(tǒng)一、高效祭犯。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末秸妥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沃粗,更是在濱河造成了極大的恐慌粥惧,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件最盅,死亡現(xiàn)場離奇詭異突雪,居然都是意外死亡,警方通過查閱死者的電腦和手機涡贱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門咏删,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人问词,你說我怎么就攤上這事督函。” “怎么了激挪?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵辰狡,是天一觀的道長。 經常有香客問我灌灾,道長搓译,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任锋喜,我火速辦了婚禮些己,結果婚禮上豌鸡,老公的妹妹穿的比我還像新娘。我一直安慰自己段标,他們只是感情好涯冠,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逼庞,像睡著了一般蛇更。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赛糟,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天派任,我揣著相機與錄音,去河邊找鬼璧南。 笑死掌逛,一個胖子當著我的面吹牛,可吹牛的內容都是我干的司倚。 我是一名探鬼主播豆混,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼动知!你這毒婦竟也來了皿伺?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤盒粮,失蹤者是張志新(化名)和其女友劉穎鸵鸥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丹皱,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡脂男,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了种呐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡弃甥,死狀恐怖爽室,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情淆攻,我是刑警寧澤阔墩,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站瓶珊,受9級特大地震影響啸箫,放射性物質發(fā)生泄漏。R本人自食惡果不足惜伞芹,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一忘苛、第九天 我趴在偏房一處隱蔽的房頂上張望蝉娜。 院中可真熱鬧,春花似錦扎唾、人聲如沸召川。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荧呐。三九已至,卻和暖如春纸镊,著一層夾襖步出監(jiān)牢的瞬間倍阐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工逗威, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留峰搪,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓庵楷,卻偏偏與公主長得像罢艾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尽纽,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容