本篇目錄
說在前面的話
不知不覺,我們送走了2015,同時迎來了2016。我相信,每一個人,都會在過去的一年有所失,但我更相信,我們所得到的更多。過去的就過去了,不要再計較了,但要從過去的各種不順中汲取經驗,這樣,在全新的2016年里繼續留下2015年遺憾的概率才會大大降低!祝大家在新的一年里,代碼bug越來越少!
本來給這個ABP理論學習系列的預算時間是2016年春節前完成,當時還恐怕完成不了任務,現在沒想到在2016年元旦假期就完成了。原本計划兩個月的完成的任務,結果用了不到一個月就完成了,我的效率還是蠻高的嘛!不過,這多虧了各位園友的鼓勵和支持,才讓我有了堅持下去的動力,在此謝謝了!
ABP理論學習完了,基本上的東西也就懂了!接下來,就需要好好地實戰一把了!雖然我寫了一步一步使用ABP搭建正式項目教程,但是那完全是讓新手能迅速使用ABP搭建的項目跑起來。接下來,我會寫一個ABP項目進階教程系列,教大家使用ABP框架搭建一個完整的例子,實戰的同時,講解一些難點的基本原理,也希望熱愛ABP的園友們多多關注。
先聲明一下,這篇文章稍長,希望大家多點耐心。
ABP提供了一些對象和函數集,使得了javascript開發簡單且標准化。
下面,我們一一講解ABP中的這些javascript API。
Ajax##
使用Ajax調用服務端的服務,並返回服務端響應的內容。因為ABP對於Ajax調用會返回一個標准的響應,因此,建議使用該方法處理標准的返回值。
Ajax操作的問題
現代應用程序頻繁使用Ajax調用,特別是在SPA中,它幾乎是和服務器交互的唯一方式。
Ajax調用包含了許多重復性的步驟:
一般來講,在客戶端javascript代碼應該提供一個URL,data是否提供是可選的,以及選擇一個執行Ajax調用的方法(Post,Get...)。必須等待然后才能處理返回的值。當向服務端發起調用時可能會發生錯誤(一般是網絡錯誤)。服務端也可能發生錯誤,服務器可能發送了一個具有錯誤信息的失敗響應。客戶端代碼應該處理服務端響應的錯誤,並選擇性地通知用戶(可能展示一個error對話框)。如果沒有錯誤,且服務器返回了數據,客戶端也必須處理。此外,一般它會阻塞一些(或者整個)屏幕區域,並展示一個繁忙的指示標志,直到Ajax操作結束。
服務端應該接收請求,然后執行服務端的代碼,捕獲任何異常並返回一個有效的響應給客戶端。在發生錯誤的情況下,它可以有選擇地發送一個錯誤消息給客戶端;成功時,它可以返回數據給客戶端。
ABP采用的方式
ABP通過使用abp.ajax函數封裝Ajax的調用,自動化了這其中的一些步驟。一個ajax調用的例子如下:
var newPerson = {
name: 'Dougles Adams',
age: 42
};
abp.ajax({
url: '/People/SavePerson',
data: JSON.stringify(newPerson)
}).done(function(data) {
abp.notify.success('created new person with id = ' + data.personId);
});
abp.ajax以一個對象作為接收選項。你可以傳遞任何在jQuery的$.ajax方法中有效的任何參數。這里有一些 默認的值:dataType是‘json’,type是‘POST’,contentType是‘application/json’(因此,在發送到服務器之前,我們可以調用JSON.stringify將javascript對象轉成JSON字符串)。你可以通過將選項傳給abp.ajax重寫默認值。
abp.ajax返回了promise。因此,你可以寫done,fail,then等處理函數。上面的例子中,我們向 PeopleController的SavePerson的action發送了簡單的Ajax請求。在 done處理函數中,我們獲得了新添加的person的數據庫Id,而且展示了一個成功的通知。讓我們看一下該Ajax請求的 MVC控制器:
public class PeopleController : AbpController
{
[HttpPost]
public JsonResult SavePerson(SavePersonModel person)
{
//TODO:將新的person保存到數據庫,並返回該person的Id
return Json(new {PersonId = 42});
}
}
SavePersonModel包含了Name和Age屬性。SavePerson標記有 HttpPost特性,因為abp.ajax默認的方法是POST。這里通過返回一個匿名的對象簡化了方法的實現。
這個看上去簡單明了,但是ABP背后處理了許多重要的事情。讓我們深入細節看一下:
Ajax返回的消息
雖然我們直接返回了一個具有PersonId=2的對象,但是ABP會使用一個MVCAjaxResponse對象封裝了它。實際的Ajax響應是像下面那樣的:
{
"success": true,
"result": {
"personId": 42
},
"error": null,
"targetUrl": null,
"unAuthorizedRequest": false
}
這里,所有的屬性都是camelCase的(因為在javascript中這是慣例),即使在服務端代碼中是PascalCased的。下面解釋一下所有的字段:
- success:一個布爾值,表示操作的成功狀態。如果是true,abp.ajax會解析該promise,並調用 done處理函數。如果是false(如果在方法調用中發生了異常),它會調用 fail處理函數並使用abp.message.error函數展示一個 error消息。
- result:控制器的action返回的實際值。如果success是true,而且服務器發送了一個返回值,它才有效。
- error:如果success是false,那么該字段是一個包含了 message和 detail字段的對象。
- targetUrl:這為服務器提供了一種重定向客戶端到其他Url的可能性。
- unAuthorizedRequest:這為服務器提供了通知客戶端該操作沒有授權或者用戶沒有認證的可能性。如果該值是true,那么abp.ajax會 重新加載當前的頁面。
通過從AbpController類中派生就可以將返回值轉換成一個封裝的Ajax響應。 abp.ajax會識別並計算該響應。因此,它們成對工作。如果沒有發生錯誤的話,那么abp.ajax的done處理函數會獲得控制器返回的實際值(一個具有personId屬性的對象)。
當從AbpApiController類派生時,也會存在相同的機制。
處理錯誤
正如上面描述的,ABP會處理服務器中的所有異常,並返回一個具有錯誤信息的對象,如下所示:
{
"targetUrl": null,
"result": null,
"success": false,
"error": {
"message": "An internal error occured during your request!",
"details": "..."
},
"unAuthorizedRequest": false
}
可以看到,success是false,result是null。abp.ajax處理該對象,而且使用abp.message.error函數展示一個錯誤信息給用戶。如果你的服務端代碼拋出了一個UserFriendlyException類型的異常,它會直接給用戶顯示異常信息。否則,它會隱藏實際的錯誤(將錯誤寫到日志中),並展示一個標准的“服務器內部錯誤...”信息給用戶。所有的這些都是ABP自動處理的。
動態Web API 層
雖然ABP提供了一種使得調用Ajax很簡單的機制,但是在真實世界的應用中,為每個Ajax調用編寫javascript函數是很經典的,比如:
//創建一個抽象了Ajax調用的function
var savePerson = function(person) {
return abp.ajax({
url: '/People/SavePerson',
data: JSON.stringify(person)
});
};
//創建一個新的 person
var newPerson = {
name: 'Dougles Adams',
age: 42
};
//保存該person
savePerson(newPerson).done(function(data) {
abp.notify.success('created new person with id = ' + data.personId);
});
對於每個Ajax調用都寫個函數是個好的做法,但是這耗時且乏味。ASP.NET為應用服務層方法提供了自動生成這些類型的函數機制。請閱讀《動態Web API層》
Notification##
展示自動關閉的通知。
我們喜歡一些事情發生時展示一些精致的自動消失的通知,比如當保存一條記錄或者問題發生時。ABP為這個定義了標准的APIs。
abp.notify.success('a message text', 'optional title');
abp.notify.info('a message text', 'optional title');
abp.notify.warn('a message text', 'optional title');
abp.notify.error('a message text', 'optional title');
通知API默認是使用toastr庫實現的。要使toastr生效,你應該引用toastr的css和javascript文件,然后再在頁面中包含abp.toastr.js作為適配器。一個toastr成功通知如下所示:
你也可以用你最喜歡的通知庫中實現通知。只需要在自定義javascript文件中重寫所有的函數,然后把它添加到頁面中而不是abp.toastr.js(你可以檢查該文件看它是否實現,這個相當簡單)中。
Message##
給用戶展示消息對話框。
消息API用於給用戶展示消息或者獲得用戶的確認。
消息API默認是使用sweetalert實現的。要讓sweetalert生效,你應該包含它的css和javascript文件,然后再頁面中添加 abp.sweet-alert.js的引用作為適配器。
展示消息
例子如下:
abp.message.info('some info message', 'some optional title');
abp.message.success('some success message', 'some optional title');
abp.message.warn('some warning message', 'some optional title');
abp.message.error('some error message', 'some optional title');
一個成功的消息如下所示:
確認
例子如下:
abp.message.confirm(
'User admin will be deleted.',
'Are you sure?',
function (isConfirmed) {
if (isConfirmed) {
//...刪除用戶
}
}
);
這里的第二個參數(title)是可選的,因此,回調函數也可以是第二個參數。
一個確認消息的例子如下所示:
ABP內部使用了Message API。比如,如果Ajax調用失敗了,那么它會調用abp.message.error。
UI block和Busy API##
使用一個區域(一個div,form,整個頁面等)阻塞用戶的輸入。此外,還使得一個區域處於繁忙狀態(具有一個繁忙的指示器,如‘loading...’)。
UI Block API
該API使用一個透明的塗層(transparent overlay)來阻塞整個頁面或者該頁面上的一個元素。這樣,用戶的點擊就無效了。當保存一個表單或者加載一個區域(一個div或者整個頁面)時這是很有用的,比如:
abp.ui.block(); //阻塞整個頁面
abp.ui.block($('#MyDivElement')); //可以使用jQuery 選擇器..
abp.ui.block('#MyDivElement'); //..或者直接使用選擇器
abp.ui.unblock(); //解除阻塞整個頁面
abp.ui.unblock('#MyDivElement'); //解除阻塞特定的元素
UI Block API默認使用jQuery的blockUI插件實現的。要是它生效,你應該包含它的javascript文件,然后在頁面中包含abp.blockUI.js作為適配器。
UI Busy API
該API用於使得某些頁面或者元素處於繁忙狀態。比如,你可能想阻塞一個表單,然后當提交表單至服務器時展示一個繁忙的指示器。例子:
abp.ui.setBusy('#MyLoginForm');
abp.ui.clearBusy('#MyLoginForm');
樣例截圖:
該參數應該是一個選擇器(如‘#MyLoginForm’)或者jQuery選擇器(如$('#MyLoginForm'))。要使得整個頁面處於繁忙狀態,你可以傳入null(或者'body')作為選擇器。
setBusy函數第二個參數接收一個promise(約定),當該約定完成時會自動清除繁忙的狀態。例子:
abp.ui.setBusy(
$('#MyLoginForm'),
abp.ajax({ ... })
);
因為abp.ajax返回promise,我們可以直接將它作為promise傳入。要學習慣於promise更多的東西,查看jQuery的Deferred。
UI Busy API是使用spin.js實現的。要讓它生效,應該包含它的javascript文件,然后在頁面中包含abp.spin.js作為適配器。
事件總線
用於注冊和觸發客戶端的全局事件。
介紹
Pub/sub事件模型廣泛用於客戶端,ABP包含了一個簡單的全局事件總線來 注冊並 觸發事件。
注冊事件
可以使用abp.event.on來注冊一個全局事件。一個注冊的例子:
abp.event.on('itemAddedToBasket', function (item) {
console.log(item.name + ' is added to basket!');
});
第一個參數是事件的唯一名稱。第二個是回調函數,當特定事件被觸發時,會被調用。
可以使用abp.event.off方法來從一個事件中取消注冊。注意:要取消注冊,要提供相同的函數。因此,對於上面的例子,你應該將回調函數設置為一個變量,然后在on和off方法中使用它。
觸發事件
abp.event.trigger用於觸發一個全局事件。觸發一個已經注冊的事件的代碼如下:
abp.event.trigger('itemAddedToBasket', {
id: 42,
name: 'Acme Light MousePad'
});
第一個參數是該事件的唯一名稱。第二個是(可選的)事件參數。你可以添加任何數量的參數,並且在回調方法中獲得它們。
Logging##
在客戶端記錄日志。
Javascript Logging API
當你想要在客戶端記錄一些簡單的日志時,你可以使用console.log('...')API,這你已經知道了。但是這種寫法不是所有的瀏覽器都支持的,而且可能會破壞你的腳本。因此,你應該首先檢查console是否可用,此外,你可能想在別的地方記錄日志,甚至你想以某種級別記錄日志。ABP定義了安全的日志函數:
abp.log.debug('...');
abp.log.info('...');
abp.log.warn('...');
abp.log.error('...');
abp.log.fatal('...');
你可以通過設置abp.log.level為abp.log.levels之一來更改日志級別(比如,abp.log.levels.INFO沒有記錄調試日志)。這些函數默認將日志記錄到了瀏覽器的控制台里了。但如果你需要的話,你也可以重寫或者擴展這個行為。
其他工具功能##
ABP提供了一些通用的工具功能。
abp.utils.createNamespace
用於立即創建更深的命名空間。假設我們有一個基命名空間‘abp’,然后想要創建或者獲得‘abp.utils.strings.formatting’命名空間。不需要下面這樣寫:
//創建或獲得namespace
abp.utils = abp.utils || {};
abp.utils.strings = abp.utils.strings || {};
abp.utils.strings.formatting = abp.utils.strings.formatting || {};
//給該namespace添加一個function
abp.utils.strings.formatting.format = function() { ... };
我們可以這樣寫:
var formatting = abp.utils.createNamespace(abp, 'utils.strings.formatting';
//給該namespace添加一個function
formatting.format = function() { ... };
這樣就簡化了安全地創建深入的命名空間了。注意,第一個參數是必須存在的根命名空間。
abp.utils.formatString
這個和C#中的string.Format()很相似。用法示例:
var str = abp.utils.formatString('Hello {0}!', 'World'); //str = 'Hello World!'
var str = abp.utils.formatString('{0} number is {1}.', 'Secret', 42); //str = 'Secret number is 42'