FineUIMvc隨筆(3)不能忘卻的回發(__doPostBack)

三生石上(FineUI控制元件)發表於2017-03-03

 宣告: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 請求,我們就能知道這個客戶端點選事件將去向何方:

 

從這張圖上,我們有如下收穫:

  1. Basic是區域名稱(Area),Hello是當前控制器名稱
  2. btnHello_Click是點選按鈕時對應的控制器方法
  3. 請求方法是 POST
  4. 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中的回發念念不忘,那就無需忘卻。 

 

相關文章