2012年9月4日 星期二

ASP.NET MVC 與 Javascript Alert

現在寫網站應該很少不會碰到 Javascript 的,尤其是現在 Javascript Framework 越來越多樣、強大,不再是多年之前只被一般人視為用來做「特效」的前端功能而已,現在開發 ASP.NET MVC 網站時也一定會用到 Javascript,我在 View 的這個部分就會使用 jQuery 來完成前端作業,我們將網站切分為 M, V, C 三個部分的模式下,我們應該要使用「關注點分離」的觀念來開發每個部分,有關 Javascript 的操作,都會在 View 來完成,很少有機會在 Controller 去做處理,Model 就更不用說(是不會處理到前端的顯示),而所謂的「很少有機會在 Controller 去做處理」這個意思指的是在後端去「組 Javascript 程式碼」。

在開發 ASP.NET Web Forms 時,假如遇到要顯示一個 Javascript Alert 的動作,很多時候都是在後端去組 Javascript 程式碼,當 PostBack 後,頁面就會執行 Javascript Alert,但是這個動作到了 ASP.NET MVC 就會變得不一樣了,在 ASP.NET MVC 中用 ASP.NET Web Forms 相同的處理方式是行不通的,以致於很多人就會在這邊一直撞牆,這邊就簡單說明一下在 ASP.NET MVC 中對於 javascript Alert 的處理方式。

 


在 ASP.NET Web Forms 開發時,簡單處理一個 Javascript Alert 動作時的方式,如下:

protected void Button1_Click(object sender, EventArgs e)
{
    Response.Write("<script>alert('這是一個 Javascript Alert');</script>");
}

 

「MSDN - ClientScriptManager 類別」

比較建議是使用 ClientScriptManager 來管理客戶端指令碼,然後再加入到 Web Application 中,而 ClientScriptManager 可以從 Page 的 ClientScript 屬性中取得 ClientScriptManager 的類別參考,而動態加入客戶端指令碼可以使用 RegisterClientScriptBlock 方法、RegisterClientScriptInclude 方法、RegisterStartupScript 方法或 RegisterOnSubmitStatement 方法,視您要加入指令碼的時間和方式而定。

 

常用到的是「RegisterClientScriptBlock 方法」「RegisterStartupScript 方法」,

RegisterStartupScript 方法」 JavaScript 函式在網頁載入的同時一併啟用。

protected void Button1_Click(object sender, EventArgs e)
{
    ClientScriptManager cs = Page.ClientScript;
    cs.RegisterStartupScript(this.GetType(), "PopupScript", "alert('這是一個 Javascript Alert')", true);
}

前端網頁執行後的原始碼:

image

RegisterClientScriptBlock 方法」JavaScript 函式會直接出現在 HTML 指令碼的 <form> 項目開頭之後。

protected void Button1_Click(object sender, EventArgs e)
{
    ClientScriptManager cs = Page.ClientScript;
    cs.RegisterClientScriptBlock(this.GetType(), "PopupScript", "alert('這是一個 Javascript Alert')", true);
}

前端網頁執行後的原始碼:

image

假如網頁上有使用到 UpdatePanel 的話,那又是另一種的處理方式了,這邊就不再說明這一個部分,有關 ASP.NET Web Forms 如何執行 javascripot 程式的部份,可以參考 91 哥在點部落中一篇文章,

點部落 - In 91[修練營 ASP.NET]如何執行一段javascript

 


※ 注意:以下範例的 ASP.NET MVC 執行版本為 ASP.NET MVC 3.0

 

一開始先看一下 ASP.NET Web Form 於後端處理前端 Javascript 的方式,這是為了要跟 ASP.NET MVC 來做個區別,之前曾經看過有人在 ASP.NET MVC 專案想要處理這樣一段 Javascript,希望 Action 返回 Result 後到頁面上可以執行,

public ActionResult ProcessJavascript()
{
    Response.Write("<script>alert('這是一個用 JavaScriptResult 的顯示結果');</script>");
    return RedirectToAction("SimpleAlert", "Home");
}

or

public ActionResult ProcessJavascript()
{
    Response.Write("<script>alert('這是一個用 JavaScriptResult 的顯示結果');</script>");
    return View();
}

上面的處理方式當然是行不通的。

 

也是看到過有人像要這樣處理,如下:

public ActionResult ProcessJavascript()
{
    return Content("<script>alert('這是一個用 JavaScriptResult 的顯示結果');</script>");
}

or

public string ProcessJavascript()
{
    return "<script>alert('這是一個用 JavaScriptResult 的顯示結果');</script>";
}

上面的方法是可行的,但是所傳回的只是一個字串,在流程處理上並不是一個好的方式。

 

方式一:

如果只是要用 Javascript Alert 顯示輸出的結果,可以用以下的方式,

Controller:

public ActionResult SimpleAlert()
{
    return View();
}
 
public ActionResult DisplayMessage()
{
    TempData["message"] = "這是一個簡單的顯示結果";
    return RedirectToAction("SimpleAlert", "Home");
}
View:
<h2>SimpleAlert</h2>
 
@Html.ActionLink("Click Me", "DisplayMessage")
 
<br />
 
 
@if (TempData["message"] != null) {
    <script type="text/javascript">
        var message = @Html.Raw(Json.Encode(TempData["message"]));
        alert(message);
    </script>
}

這邊會用到的是 TempData,TempData 是一次性的資料儲存容器,用過 TempData 資料後,原本容器中的資料就會被移除,以下為執行結果:

image

網頁原始檔 (執行前):

image

網頁原始檔 (執行後):

image

 

上面的是使用一個簡單的 ActionLink 方式,而接下來就換成表單 POST 的方式,

Controller:

public ActionResult SimpleAlertPost()
{
    return View();
}
[HttpPost]
public ActionResult SimpleAlertPost(string Account, string Password)
{
    if (string.IsNullOrWhiteSpace(Account) || string.IsNullOrWhiteSpace(Password))
    {
        TempData["message"] = "輸入錯誤";
        return View();
    }
    else
    {
        TempData["message"] = "輸入完成";
        return RedirectToAction("SimpleAlertPostFinish", "Home");
    }
}
public ActionResult SimpleAlertPostFinish()
{
    return View();
}

 

View:

SimpleAlertPost

<h2>SimpleAlertPost</h2>
 
@using(Html.BeginForm("SimpleAlertPost", "Home", FormMethod.Post))
{
    <ul style="list-style-type: none;">
        <li>
            @Html.Label("Account:") 
            @Html.TextBox("Account")
        </li>
        <li>
            @Html.Label("Password:") 
            @Html.Password("Password")
        </li>
        <li>
            <input type="submit" value="送出" /> 
            <input type="reset" value="清除" />
        </li>
    </ul>
}
@if (TempData["message"] != null) {
    <script type="text/javascript">
        var message = @Html.Raw(Json.Encode(TempData["message"]));
        alert(message);
    </script>
}

SimpleAlertPostFinish

<h2>SimpleAlertPostFinish</h2>
 
@if (TempData["message"] != null) {
    <script type="text/javascript">
        var message = @Html.Raw(Json.Encode(TempData["message"]));
        alert(message);
    </script>
}

頁面是一個很簡單的輸入表單,

image

當沒有輸入表單欄位或是欄位輸入不齊全的時候,就回到原來的 SimpleAlertPost 頁面並顯示錯誤訊息,

image

當表單欄位資料輸入完全並送出後,導到 SimpleAlertPostFinish 並顯示訊息,

image

 

方法二:

開始接觸 ASP.NET MVC 的時候,進度到了 Controller 這部分時就一定要知道 ActionResult,

ActionResult 類別

而繼承 ActionResult 類別的眾多類別裡有一個 JavaScriptResult 總是讓人有無限遐想,

JavaScriptResult 類別

很多人以為 Action 方法傳回這個 JavaScriptResult 的結果就可以在前端頁面中執行 Javascript 程式,而且在 Google 所查詢的資料也很多人都是這樣用:

Controller

public JavaScriptResult DisplayOtherMessage()
{
    return JavaScript("alert('這是一個用 JavaScriptResult 的顯示結果');");
}

View

@Ajax.ActionLink("Display Other Message", "DisplayOtherMessage", null)

寫法都沒有錯,但是執行的時候卻不是我們想要的結果,不是顯示 Alert 而是直接顯示 Javasscript 程式字串,

image

為什麼會這樣呢?

遇到這樣的情況大部分是因為 View 沒有引入「jquery.unobtrusive-ajax.js」或「jquery.unobtrusive-ajax.min.js」,在 View 或是 _Layout.cshtml 加入上面所提到的 js 檔案就可以,因為是使用 Ajax.Action(),所以是以 Ajax 來處理,頁面不會有重新整理的情況。

image

 

方法三:

其實我在開發 ASP.NET MVC 的過程中,只要是前端頁面要呈現或是變化的,都會盡量以 Javascript 來做處理,而前端 Alert 的處理,我就不會在後端去組 Javascript 程式碼,而是前端 POST 表單欄位資料到後端處理,後端程式處理之後再以 JSON 傳回到前端,前端程式再依據傳回的 JSON 資料內容來做 Alert 的處理,當然接收 JSON 資料後的處理也不一定只能是 Alert,也可以是去做別的處理,例如處理 Cookie 或是清除頁面、轉頁等,就看系統操作流程的規格是怎麼規劃的,並不一定要什麼事情都丟到後端處理。

以上面的網頁表單輸入的例子來做修改,

View:

<form id="Form_Login">
    <ul style="list-style-type: none;">
        <li>
            @Html.Label("Account:") 
            @Html.TextBox("Account", "", new { id="TextBox_Account" })
        </li>
        <li>
            @Html.Label("Password:") 
            @Html.Password("Password", "", new { id = "TextBox_Password" })
        </li>
        <li>
            <input type="button" value="送出" id="ButtonSubmit" /> 
            <input type="reset" value="清除" />
        </li>
    </ul>
</form>

Controller:

後端的處理在接收前端輸入的資料後做判斷,最後的判斷結果就以 JSON 資料送回前端,這邊的 JSON 資料處理是使用 JSON.NET,

public ActionResult AlertPost()
{
    return View();
}
[HttpPost]
public ActionResult AlertPost(string Account, string Password)
{
    JObject jo = new JObject();
    if (string.IsNullOrWhiteSpace(Account) || string.IsNullOrWhiteSpace(Password))
    {
        jo.Add("result", false);
        jo.Add("msg", "輸入錯誤");
    }
    else
    {
        if (Account.Equals("111") && Password.Equals("222"))
        {
            jo.Add("result", true);
            jo.Add("msg", Account);
        }
        else
        {
            jo.Add("result", false);
            jo.Add("msg", "登入錯誤");
        }
    }
    return Content(JsonConvert.SerializeObject(jo), "application/json");
}

前端的 Javascript 程式:

前端的 Javascript 程式處理就比較多,這邊使用 jQuery.AJAX 來做表單資料的送出與接收結果後的處理,

<script type="text/javascript">
    $(document).ready(function () {
        $('#ButtonSubmit').click(function () { SubmitEventHandler(); });
    });
    function SubmitEventHandler() {
        var account = $.trim($('#TextBox_Account').val());
        var password = $.trim($('#TextBox_Password').val());
        if (account.length == 0 || password.length == 0) {
            alert('資料輸入不齊全');
        }
        else {
            
            var postData = $('#Form_Login').serialize();
            $.ajax({
                url: '@(Url.Action("AlertPost", "Home"))',
                type: "POST",
                data: postData,
                dataType: "json",
                async: false,
                cache: false,
                success: function (data) {
                    if (data) {
                        if (!data.result) {
                            alert(data.msg);
                        }
                        else {
                            $('#TextBox_Account').val('');
                            $('#TextBox_Password').val('');
                            alert(data.msg + ' 登入完成');
                        }
                    }
                    else {
                        alert('沒有回傳');
                    }
                }
            });
        }
    }
</script>

執行結果:

image

image

image

image

有關前端的資料驗證與傳送後端、接收後端傳回資料後的判斷與處理,這些都是在前端的 Javascript 程式來完成,後端 Controller 的程式就不必要去處理前端該怎麼顯示或是怎麼處理的問題,只需處理系統的流程運作。

 

不一樣的 Alert 顯示

假如你覺得 Javascript Alert 的樣式不是很喜歡,希望能有多一點的變化,其實我們也可以套用一些 jQuery Plugin,讓 Alert 的樣式可以有不一樣的外觀,很多人都會使用 jQuery.UI 的 dialog 來取代原本的 Javascript Alert,其實還是有很多不同的 plugin 可以選擇,這邊就用 jQuery Alert Dialogs 來取代 Javascript Alert,

jQuery Alert Dialogs - 網站

jQuery Alert Dialogs - Demo

image

下載好「jquery.alerts-1.1.zip」之後,解開檔案然後加入到 ASP.NET MVC 網站專案中,然後在 View Page 中加入 jquery.alert.dialogs 的必要檔案(css, js)

 

_Layout.cshtml

加入 jquery.alerts.css

<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Scripts/jquery.alerts-1.1/jquery.alerts.css")" rel="stylesheet" type="text/css" />
</head>

 

View Page

加入 jquer.alerts.js

而 jquery-ui-1.8.16.min.js 則看需不需要加入,有加入的話,顯示 jQuery.Alert.Dialogs 視窗時就可以拖曳移動,

<script src="@Url.Content("~/Scripts/jquery-ui-1.8.16.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.alerts-1.1/jquery.alerts.js")" type="text/javascript"></script>

 

最後修改一下原本的 Javascript Code 內容,將原本使用 alert() 的地方給為使用 jAlert()

<script src="@Url.Content("~/Scripts/jquery-ui-1.8.16.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.alerts-1.1/jquery.alerts.js")" type="text/javascript"></script>
<script type="text/javascript">
    $(document).ready(function () {
        $('#ButtonSubmit').click(function () { SubmitEventHandler(); });
    });
    function SubmitEventHandler() {
        var account = $.trim($('#TextBox_Account').val());
        var password = $.trim($('#TextBox_Password').val());
        if (account.length == 0 || password.length == 0) {
            //alert('資料輸入不齊全');
            jAlert("資料輸入不齊全", "Error");
        }
        else {
            
            var postData = $('#Form_Login').serialize();
            $.ajax({
                url: '@(Url.Action("AlertPost", "Home"))',
                type: "POST",
                data: postData,
                dataType: "json",
                async: false,
                cache: false,
                success: function (data) {
                    if (data) {
                        if (!data.result) {
                            //alert(data.msg);
                            jAlert(data.msg, "Error");
                        }
                        else {
                            $('#TextBox_Account').val('');
                            $('#TextBox_Password').val('');
                            //alert(data.msg + ' 登入完成');
                            jAlert(data.msg + ' 登入完成', "Success");
                        }
                    }
                    else {
                        //alert('沒有回傳');
                        jAlert(data.msg, "沒有回傳");
                    }
                }
            });
        }
    }
</script>

 

執行結果:

image

image

image

 

其實前端 Alert 的替代也不限定於使用 jQuery.Alert.Dialogs,還有很多相同功能的 jQuery Plugin 可以使用,而且功能還更為強大,就看開發人員依據專案的需求來做選擇與使用。

寫完這個範例之後才發現到,同樣的 jQuery Alert Dialogs 另外一個人也有寫了一套,而這另外寫的 plugin 則是依據「jQuery.Alert.Dialogs - Cory S.N. LaViska Version」來做一些修改以及補強,

 

Bill Beckelman - jQuery Alert Dialogs

Demo

Download

image

 

「jQuery.Alert.Dialogs - Cory S.N. LaViska Version」這一版的 Alert 顯示樣式只有一種,當我們想要對顯示內容做不一樣的顯示時,「Cory S.N. LaViska Version」的樣式就會顯得不足,所以建議各位也可以採用「 Bill Beckelman Version」的 jQuery Alert Dialogs。

 


對照 ASP.NET Web Forms 的開發,因為 PostBack 的機制,所以有些 Javascript 的程式碼就會在後端去做組合的動作,而也因為 PostBack 機制,所以在處理或是執行 Javascript 時,往往就要注意很多的細節,或是要額外處理後才能夠讓 Javascript 可以「正確執行」,為了要「可以正確執行」,所以就會衍生很多迥異、弔詭的處理方式,以致於很多 ASP.NET Web Forms 的開發人員對於 Javascript 就會很排斥,甚至完全不想接觸 jQuery,更多情況是只會「套用 jQuery Plugin」而完全不去了解真正的使用方式,當習慣開發 ASP.NET Web Forms 的人一旦轉換開發 ASP.NET MVC 時,碰到 Javascript 處理的問題時,因為不熟系 Javascript 或不會 jQuery,就會一直撞牆,如果把以前 Web Forms 的處理方式拿來用在 ASP.NET MVC 上,結果無法執行,就會開始覺得 MVC 不好或是其他惡評…

說了這麼多,老話一句就是「不要把以前開發 ASP.NET Web Forms 的觀念用在 ASP.NET MVC 上」!

另外還要強調的是,我這一篇只是介紹 ASP.NET MVC 中處理 Javascript Alert 方式的簡單做法,每個人所開發的專案有各種不同的需求與規格,選擇適合自己專案的顯示方式,並切記 MVC 關注點分離的開發方式。

 

以上

11 則留言:

  1. 好詳細的教學~ 解答了我的疑惑~十分感謝Orz

    回覆刪除
  2. 您好,感謝分享如此詳細的文章,
    想請教 文章有jQuery Post的程式片段 : var postData = $('#Form_Login').serialize();
    這個能讓定義的ViewModel自動Binding到後端嗎?
    想請教他跟內建的Ajax.BeginForm比較,何種方式較好 & 維護呢,謝謝!

    回覆刪除
    回覆
    1. Helo, 你好

      第一個問題:
      要讓後端可以將 JSON 自動 Model Binding 至指定的 ViewModel,還需要自行建立 ModelBinder,後續有機會的話在寫篇文章來做說明。

      第二個問題:
      這是我的觀點,不代表其他 ASP.NET MVC 開發者的觀點,我相當不建議使用 AjaxHelper。
      以維護性來說,使用 jQuery 的方式要比使用 AjaxHelper 來得好,
      而且在一個分工較為仔細的開發團隊來說,View 的開發及維護是有可能由 F2E (Front End Engineer) 來執行,
      F2E 熟悉前端語言,他們使用的除了 jQuery 與一般的 Javascript 之外有可能是其他的前端框架,例如 Knockout, AngularJS 等,
      他們處理 Ajax 是會有一定的方法與方式,所以我們不必另外去使用 AjaxHelper 的方法,避免造成開發團隊的困擾。

      另外就是,如果是小團隊開發,也就是團隊的開發成員不分前後端,成員都是全端工程師,也應當避免使用 AjaxHelper,
      因為使用 AjaxHelper 時如果發生問題,要到網路上找解決的方式時,可以發現到這個世界上真的很少人再使用 AjaxhHelper,
      而且如果是使用 jQuery 或是其他前端框架,可以找到相當多的答案以及解決方式,
      因為不只 ASP.NET MVC,其他的網頁開發技術像 Ruby on Rails, PHP 等,前端也都是這樣來做處理,所以找到的解決方式會更多。

      如果你是有一定資歷的 ASP.NET 開發者,可以回想早期微軟也曾經推出自己的前端 AJAX 框架,Microsoft Ajax Library,
      試圖與 AJAX Control Toolkits 與其本身 ASP.NET 的 AJAX 來做整合,但是 Microsoft AJAX Library 真的不好用,
      以致於後來官方直接將 jQuery 直接整合進專案範本內。

      其實你可以兩種方法都去使用,自己親身體驗並感受,相信經過一段時間之後你就會發現到,AjaxHelper 真的很少人在用,
      我們可以去瞭解和知道怎麼使用 AjaxHelper,但是實際的專案開發上,還有更好的技術可以用。

      刪除

  3. string strJS = @"Javascript Run Text
    ~~~
    ~~~
    ~~~";
    ViewBag.JS = strJS;
    return View();


    Html.Contents{
    ~~~
    ~~~
    ~~~
    }
    <script type="text/javascript">
    $(function () {
    @Html.Raw(ViewBag.JS)
    });
    </script>

    ※ViewBag 可以改用 ViewModel

    盲點在於底下
    ' → '
    " → "
    @Html.Raw(ViewBag.JS) ← 這行就是為了解決這個問題
    alert() 可以看到"正確的JS",但右鍵"檢視網頁原始碼"卻又不是這麼一回事。

    這個點卡很久了,分享一下。



    用意 → http://www.highcharts.com/
    多重圖表、簡短程式碼、好維護

    很多人對 JS 敬而遠之的最大問題在於 「變動 Value」 寫在前端,看到你眼睛脫窗都改不出來。
    這個問題只要把它丟到後端處理就解決了,尤其是對重複性高的大量 JS 內容,越能看出效益。

    回覆刪除
    回覆
    1. 這邊會變成這樣
      ' → &#39;
      " → &quot;

      刪除
    2. 這一篇在講 Javascript Alert,你是已經討論到其他的用途做法
      其實在我所做的專案裡,我是堅持不讓真正執行前端作業的 Javascript Code 出現在 View 裡面,
      而且全部都要移出並獨立為 js 檔案,而且不可能讓後端去產出前端所要的資料在 View 裡面,
      因為要確實作到前後端的程式分離,前端歸前端、後端歸後端,完全獨立。
      煩請下次還是針對文章的主題來做討論,謝謝。

      刪除
    3. 恩,明白了,非常抱歉也感謝指正。

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。