2011年7月7日 星期四

先透過PageMethods撈DB,來決定按鈕是否要confirm訊息

前言

會寫這篇文章的起因來自於MSDN forum上的一個問題:用confirm中斷再繼續執行,updatepanel就不會更新

以前也有遇過這樣的需求,在針對敏感或重要的資料進行操作時,如果那些資料符合了某些條件,
則需要在畫面上顯示提示訊息,也就是confirm,讓User可以知道這筆資料的狀態,再來決定是否繼續執行動作。

這問題的麻煩之處,在於client端與server端事件順序,在這需求裡面會有點卡住。
confirm是javascript的語法,也就是要在頁面Render出來的時候,就該註冊上去的code。
但是我們的confirm,需要根據user在畫面輸入的資料,才能判斷是否需要顯示confirm訊息。
也就是「不能提早註冊confirm的script在button上」。
流程應該是,
  1. 點了按鈕後,去DB撈資料判斷是否要提示user,撈資料的部分code應該寫在server端。
  2. 如果要提示user,則client端的畫面上呈現confirm
  3. user點了「確定」,則執行該button應該執行的code。點了「取消」,則畫面維持原狀,不做任何處理。

由於confirm是client端的動作,
按照上面的作法就會變成下列步驟
  1. client端的button.onclientclick(這邊用onclientclick來區分是server事件還是client事件)
  2. postback
  3. server端的button.click(sender,e)
  4. 撈DB資料判斷是否要提示
  5. 註冊confirm訊息???????
問題的起源就卡在第5的步驟。
在button.click(sender,e)裡面註冊confirm到button上,已經太晚了,因為已經在server端的階段了,client端的處理在步驟1就已經結束了。
即便confirm出來了,按了確定,又要執行一次該按鈕在server端的code(例如存檔),再次執行server端的button.click(sender,e),可不能再去檢查一次DB資料 再confirm一次。

Suvery Solution

按照前言裡面的步驟,比較有經驗的developer,直覺想法可能是postback兩次,做好程式事件接口來處理這整個client->server->client->server的動作。
這邊就不貼出sample code,只講步驟跟概念:
  1. button上沒有client端事件要處理的事,把最後要存檔的function獨立出來一個private的function。
  2. 點了按鈕,postback,執行button.click(sender,e)
    • 到DB去撈資料,判斷是否要confirm
    • 是,註冊confirm和_dopostback(‘我的postback’,’用得到的DB訊息’)的script到「頁面」上
  3. 第二次postback
  4. confirm訊息,使用者點「確定」
  5. Page_load事件裡面判斷,此次的postback是否由「我的postback」所引起的,(透過eventTarget)
    • 是,則呼叫存檔的function

這樣的作法,的確可行,因為我們需要的是server->client->server,postback兩次的cycle是client->server->client->server
缺點是得postback兩次,
postback,當頁面一長,控制項一多,viewstate一肥,user頻寬太小,就很容易造成user需等待時間拉長。
有沒有可能不postback兩次呢?

直覺就是想到處理非同步的Ajax。
有了web service與ajax的PageMethods,很多東西都不需要postback來處理了。
於是有了想法,
在Button.onclientclick的時候,呼叫PageMethods,
去DB檢查是否要confirm,如果要,則呼叫confirm,
點了「確定」則不處理,繼續postback,執行server端的button.click(sender,e)
點了「取消」則return false,讓按鈕不postback。

直到我把上面的想法實做成sample code之後,我發現我錯了…

期望的順序是:
  1. client端的button.onclientclick,呼叫PageMethods
  2. PageMethods判斷完DB的資料,return bool
  3. bool若true,則執行client的confirm
  4. if confirm為false,則return false
  5. confirm為true,則執行server端的button.clieck(sender,e)
結果跑出來的順序不一樣:
  1. client端的button.onclientclick,呼叫PageMethods
  2. 執行server端的button.clieck(sender,e)
  3. PageMethods判斷完DB的資料,return bool
  4. bool若true,則執行client的confirm
  5. if confirm為false,則return false
client->PageMethods->server,變成了client->server->PageMethods。
這讓我頗為吃驚,跟原本想像的順序不太一樣。
但我還是很不甘願,決定加工把這樣的問題處理掉,基於PageMethods,還是要達到我們的目的。

Play it!

首先要使用PageMethos該有的設定,請參考:[ASP.NET AJAX]PageMethod:用javascript呼叫server端的method
概念都跟上面的期望順序一樣,
問題點只有在,怎麼樣讓server端的button.click(sender,e)在PageMethods後面執行。
加工的部分:
  1. 用一個hidden來存放PageMethods回傳的bool
  2. button.onclientclick呼叫一個js function檢查hidden的值,來決定要不要confirm
  3. 在textbox onchange事件就呼叫PageMethods。
  4. isPostBack為false的時候,呼叫PageMethods。
  5. 執行server端的button.click(sender,e)後要清掉hidden的值。
附上Sample code,撈資料的方式請自行修改,Sample Code上的方式是根據[修練營ASP.NET]使用Spring.Net輔助切層的專案架構 撈資料的。
.aspx
01<html xmlns="http://www.w3.org/1999/xhtml">
02<head runat="server">
03    <title>未命名頁面</title>
04  
05    <script type="text/javascript">
06    function CallMe(src,dest)
07     {    
08         var ctrl = document.getElementById(src);         
09         PageMethods.IsDataExist(ctrl.value,CallSuccess,CallFailed,dest);
10     }
11   
12       
13     function CallSuccess(res, destCtrl)
14     {    
15         var dest = document.getElementById(destCtrl);         
16         dest.value=res;         
17     }
18       
19       
20     function CallFailed(res, destCtrl)
21     {
22         alert(res.get_message());
23     }
24       
25     function buttonclick()
26     {
27        if($get('HiddenField1').value=='true')
28        {
29            if(!confirm('已經存在資料,確定執行?'))
30            {return false;}}
31        else
32        {return true;}
33     }
34    </script>
35  
36</head>
37<body>
38    <form id="form1" runat="server">
39    <div>
40        <asp:ScriptManager ID="ScriptManager1" runat="server"  EnablePageMethods="true">
41        </asp:ScriptManager>
42        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
43            <ContentTemplate>
44                ID:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
45                <asp:HiddenField ID="HiddenField1" runat="server" />
46                <asp:Button ID="Button1" runat="server" Text="存檔" onclick="Button1_Click" OnClientClick="if(buttonclick()==false){return false;}" />
47                <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
48            </ContentTemplate>
49        </asp:UpdatePanel>
50    </div>
51    </form>
52</body>
53</html>

.cs
01protected void Page_Load(object sender, EventArgs e)
02{
03    this.TextBox1.Attributes.Add("onchange", "CallMe('TextBox1','HiddenField1');");
04      
05    if (!IsPostBack)
06    {
07        this.TextBox1.Text = "2";
08        ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "detectID", "CallMe('TextBox1','HiddenField1');", true);
09    }
10}
11[System.Web.Services.WebMethod]
12public static bool IsDataExist(string RegionID)
13{
14    Core.Domain.Interface.IRegion region=(Core.Domain.Region)Core.WebUtility.Repository.Domain("Region");
15      
16    if (RegionID.Length != 0)
17    {
18        region.Id = Convert.ToInt16(RegionID);
19    }
20    else
21    { region.Id = null; }
22    IList regionList = region.GetRegion();
23    if (regionList.Count==0)
24    {
25        return false;
26    }
27    else
28    {
29        return true;
30    }
31}
32protected void Button1_Click(object sender, EventArgs e)
33{
34    this.TextBox2.Text = "存檔成功,ID為 "+this.TextBox1.Text;
35    this.HiddenField1.Value = string.Empty;
36    ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "detectID", "CallMe('TextBox1','HiddenField1');", true);
37}
畫面:(ID 1~4已經存在資料)
1.假設一開始textbox的值從DB撈出來assign給textbox。(測試沒有經過textbox的onchange事件)
1
2.點了存檔後,因為2已經存在DB,所以confirm。
2
3.點了取消,則畫面回到第一張圖。點了確定,則執行server端的button.click。
3

4.再點一次存檔按鈕,則仍顯示confirm,因為DB裡ID為2的資料仍然存在。
4
5.把ID改為5,點存檔,則沒有顯示confirm,直接執行server端的button.click(sender,e)
5

結論

這功能與需求其實並沒有什麼,Sample的情況也相當單純,
但是這個過程讓我發現了很多以前自己的觀念不一定是對的。
在面對與想像中不一樣而產生的bug時,再透過已知的觀念和技巧,去將bug fix掉,
再整理出來分享給大家,
希望大家的收穫可以比我還多。
補充相關連結:http://social.msdn.microsoft.com/Forums/zh-TW/236/thread/d9a39279-7626-4ce5-9cb8-df50d44c04ee

沒有留言:

張貼留言