宣告:FineUIMvc(基礎版)是免費軟體,本系列文章適用於基礎版。
使用者反饋
有網友在官方論壇丟擲了這麼一個問題,似乎對 FineUIMvc 中的瀏覽器端與伺服器端的互動方式很有異議。
這裡面的關鍵詞就是:回發!
似乎一提到回發(__doPostBack),就讓人聯想到 WebForms 中的 ViewState 和單表單提交,因為回發時會把頁面上所有控制元件的 ViewState 一股腦的提交到後臺,無疑加重了網路的上行資料量。從此 回發 這一名詞給人的印象就很晦澀了。
真的是這樣嗎?我們分別來比較 WebForms、ASP.NET MVC、以及FineUIMvc中的回發,來探索其中的聯絡和差異。
WebForms中的回發(__doPostBack)
每位經歷過 ASP.NET WebForms 的開發人員都不會忘記這個字串:__doPostBack,因為它出現在你寫的每一個 .aspx 頁面的瀏覽器原始碼中:
<script type="text/javascript"> //<![CDATA[ var theForm = document.forms['form1']; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } //]]> </script>
在 WebForms 中,整個頁面就是一個表單,所以早期的微軟工程師大搖大擺的定義了一個全域性變數:
var theForm = document.forms['form1'];
__doPostBack 函式則是對頁面上這個唯一的表單提交( theForm.submit),傳入的 eventTarget 和 eventArgument 分別用來標識本次回發的觸發控制元件以及回發引數。
這些都沒啥,關鍵是頁面上永遠都有一個 ViewState 隱藏欄位:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTQyNjcyNDU5MWRkpK.....aycxe0NfpWe+PGI0=" />
這裡面存放了頁面上所有控制元件的狀態資訊,比如下拉選單的資料,表格的資料,輸入框的資料等等,所有這個欄位一般會比較大,導致上傳的資料量動輒就有20K~1000K。在網路有限的情況下會非常影響效能,從而給人了臃腫的印象。
比如在 FineUI(開源版)中一個包含表格的頁面:
http://fineui.com/demo/iframe/grid_iframe.aspx
頁面回發時,請求的資料量就達到 19802 bytes = 19K
ASP.NET MVC中的回發(BegionForm)
在 ASP.NET MVC 中,我們可以定義多個表單,從而自行控制需要提交的表單,以及表單中欄位。也就是隻提交我們需要的資料,這樣不僅靈活而且上傳資料量不會很大。
在 MVC 的其他文件中,你可能很少會看到 回發 這個字眼,很多是這樣描述的:提交某個表單到控制器的某個操作方法,或者說發起一個 HTTP POST 請求。其實這些都對應於 WebForms 中的回發字眼,只不過操作的表單和表單欄位不同而已。本質上還是一樣的。
在我之前寫MVC系列教程中有一個典型的回發過程:【第四篇】ASP.NET MVC快速入門之完整示例(MVC5+EF6)
@using (Html.BeginForm()) { @Html.AntiForgeryToken() <p> 所學專業: @Html.DropDownList("Major", ViewBag.MajorList as IEnumerable<SelectListItem>, "全部") 姓名: @Html.TextBox("Name") <input type="submit" value="檢索" /> </p> }
透過 Html.BeginForm 輔助方法來生成一個表單,這個表單會提交到當前頁面對應的控制器方法,預設使用 POST 請求,生成到頁面的HTML結構類似:
<form action="/Students/Create" method="post"> </form>
表格裡面,明確定義了兩個表單欄位,分別是 Major 和 Name,以及一個提交按鈕(type=submit)。
對應的後臺控制器方法類似:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Index(string Major, string Name) { // ... return View(students.ToList()); }
在這個過程中,雖然我們可以控制要提交的表單,以及提交哪些引數,但這個過程還是整個頁面提交。
而 FineUIMvc 中,我們不僅不需要定義表單(只需要告訴FineUIMvc需要提交哪些引數即可),而且所有的提交都是AJAX過程。
FineUIMvc中的回發(OnClick、OnPageIndexChanged)
FineUIMvc中將對控制器方法的呼叫放到每個具體的控制元件中, 對應於 FineUI(開源版)中控制元件的事件。
無引數回發
按鈕的點選事件:
@(F.Button() .OnClick(Url.Action("btnHello_Click")) .ID("btnHello") .Text("點選彈出對話方塊") )
這樣就將按鈕的客戶端點選事件(click)和伺服器端控制器的方法(btnHello_Click)關聯起來,而且命名也和WebForms中的一模一樣,是不是倍感親切。
仔細觀察下這個 HTTP 請求,我們就能知道這個客戶端點選事件將去向何方:
從這張圖上,我們有如下收穫:
- Basic是區域名稱(Area),Hello是當前控制器名稱
- btnHello_Click是點選按鈕時對應的控制器方法
- 請求方法是 POST
- X-Requested-Width: XMLHttpRequest,表明當前請求是AJAX
再來看下HTTP請求正文資料:
只要一個防止跨站請求偽造(CSRF)的引數_RequestVerificationToken,再無其他引數。
對應的控制器方法:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult btnHello_Click() { Alert.Show("你好 FineUIMvc!", MessageBoxIcon.Warning); return UIHelper.Result(); }
因為在這個過程中,後臺無需引數,所以前臺也沒必要傳入任何資料。這樣一個AJAX回發過程就非常乾淨,不像之前的WebForms一樣,需要傳遞一堆引數了。
帶引數回發
來看下錶格的資料庫分頁事件,在資料庫分頁時後臺C#程式碼需要知道兩個引數:
1. 當前的頁碼
2. 表格中用到了哪些資料庫欄位,即使熟練的WebForms開發人員可能也不會意識到這一點,因為在WebForms中後臺能夠知道控制元件的所有引數,而MVC中回發時,你對錶格的任何引數一無所知,所有你需要的引數都需要透過前臺傳入
FineUIMvc對於帶引數的回發回發進行了深度最佳化,你根本無需自己透過JavaScript來獲取這些引數(當然你也可以這麼做,只要你願意),而是指定表格的ID即可:
@(F.Grid() .EnableCheckBoxSelect(true) .Width(850) .ShowHeader(true) .ShowBorder(true) .EnableCollapse(true) .Title("表格") .ID("Grid1") .DataIDField("Id") .DataTextField("Name") .AllowPaging(true) .PageSize(5) .IsDatabasePaging(true) .OnPageIndexChanged(Url.Action("Grid1_PageIndexChanged"), "Grid1") .Columns( F.RowNumberField(), F.RenderField() .HeaderText("姓名") .DataField("Name") .Width(80), .... ) .RecordCount(ViewBag.Grid1RecordCount) .DataSource(ViewBag.Grid1DataSource) )
注意 OnPageIndexChanged 的第二個引數,這樣在發起對控制器方法(Grid1_PageIndexChanged)的POST請求時,會自動附加所需的引數:
對應的控制器方法:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Grid1_PageIndexChanged(JArray Grid1_fields, int Grid1_pageIndex) { var grid1 = UIHelper.Grid("Grid1"); var recordCount = DataSourceUtil.GetTotalCount(); // 1.設定總項數(資料庫分頁回發時,如果總記錄數不變,可以不設定RecordCount) grid1.RecordCount(recordCount); // 2.獲取當前分頁資料 var dataSource = DataSourceUtil.GetPagedDataTable(pageIndex: Grid1_pageIndex, pageSize: 5, recordCount: recordCount); grid1.DataSource(dataSource, Grid1_fields); return UIHelper.Result(); }
小結
WebForms中的回發由於需要附加上ViewState而略顯臃腫;
ASP.NET MVC原生的回發需要藉助Html.BeginForm輔助方法來生成單獨的表單,並把需要提交的引數放置到表單中,回發過程是整個頁面提交;
FineUIMvc對回發過程進行深度最佳化,無需建立表單,只需要提供需要回發的引數,而且回發過程是AJAX的。對於部分控制元件比如表單和表格,甚至不需要指定回發引數,只需要設定控制元件ID即可,非常方便。
如果你還對WebForms中的回發念念不忘,那就無需忘卻。