[修練營ASP.NET]先透過PageMethods撈DB,來決定按鈕是否要confirm訊息
前言
會寫這篇文章的起因來自於MSDN forum上的一個問題:用confirm中斷再繼續執行,updatepanel就不會更新
以前也有遇過這樣的需求,在針對敏感或重要的資料進行操作時,如果那些資料符合了某些條件,
則需要在畫面上顯示提示訊息,也就是confirm,讓User可以知道這筆資料的狀態,再來決定是否繼續執行動作。
這問題的麻煩之處,在於client端與server端事件順序,在這需求裡面會有點卡住。
confirm是javascript的語法,也就是要在頁面Render出來的時候,就該註冊上去的code。
但是我們的confirm,需要根據user在畫面輸入的資料,才能判斷是否需要顯示confirm訊息。
也就是「不能提早註冊confirm的script在button上」。
流程應該是,
- 點了按鈕後,去DB撈資料判斷是否要提示user,撈資料的部分code應該寫在server端。
- 如果要提示user,則client端的畫面上呈現confirm
- user點了「確定」,則執行該button應該執行的code。點了「取消」,則畫面維持原狀,不做任何處理。
由於confirm是client端的動作,
按照上面的作法就會變成下列步驟
- client端的button.onclientclick(這邊用onclientclick來區分是server事件還是client事件)
- postback
- server端的button.click(sender,e)
- 撈DB資料判斷是否要提示
- 註冊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,只講步驟跟概念:
- button上沒有client端事件要處理的事,把最後要存檔的function獨立出來一個private的function。
- 點了按鈕,postback,執行button.click(sender,e)
- 到DB去撈資料,判斷是否要confirm
- 是,註冊confirm和_dopostback(‘我的postback’,’用得到的DB訊息’)的script到「頁面」上
- 第二次postback
- confirm訊息,使用者點「確定」
- 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之後,我發現我錯了…
期望的順序是:
- client端的button.onclientclick,呼叫PageMethods
- PageMethods判斷完DB的資料,return bool
- bool若true,則執行client的confirm
- if confirm為false,則return false
- confirm為true,則執行server端的button.clieck(sender,e)
結果跑出來的順序不一樣:
- client端的button.onclientclick,呼叫PageMethods
- 執行server端的button.clieck(sender,e)
- PageMethods判斷完DB的資料,return bool
- bool若true,則執行client的confirm
- 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後面執行。
加工的部分:
- 用一個hidden來存放PageMethods回傳的bool
- button.onclientclick呼叫一個js function檢查hidden的值,來決定要不要confirm
- 在textbox onchange事件就呼叫PageMethods。
- isPostBack為false的時候,呼叫PageMethods。
- 執行server端的button.click(sender,e)後要清掉hidden的值。
附上Sample code,撈資料的方式請自行修改,Sample Code上的方式是根據[修練營ASP.NET]使用Spring.Net輔助切層的專案架構 撈資料的。
.aspx
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>未命名頁面</title>
<script type="text/javascript">
function CallMe(src,dest)
{
var ctrl = document.getElementById(src);
PageMethods.IsDataExist(ctrl.value,CallSuccess,CallFailed,dest);
}
function CallSuccess(res, destCtrl)
{
var dest = document.getElementById(destCtrl);
dest.value=res;
}
function CallFailed(res, destCtrl)
{
alert(res.get_message());
}
function buttonclick()
{
if($get('HiddenField1').value=='true')
{
if(!confirm('已經存在資料,確定執行?'))
{return false;}}
else
{return true;}
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
ID:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:HiddenField ID="HiddenField1" runat="server" />
<asp:Button ID="Button1" runat="server" Text="存檔" onclick="Button1_Click" OnClientClick="if(buttonclick()==false){return false;}" />
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</form>
</body>
</html>
.cs
protected void Page_Load(object sender, EventArgs e)
{
this.TextBox1.Attributes.Add("onchange", "CallMe('TextBox1','HiddenField1');");
if (!IsPostBack)
{
this.TextBox1.Text = "2";
ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "detectID", "CallMe('TextBox1','HiddenField1');", true);
}
}
[System.Web.Services.WebMethod]
public static bool IsDataExist(string RegionID)
{
Core.Domain.Interface.IRegion region=(Core.Domain.Region)Core.WebUtility.Repository.Domain("Region");
if (RegionID.Length != 0)
{
region.Id = Convert.ToInt16(RegionID);
}
else
{ region.Id = null; }
IList regionList = region.GetRegion();
if (regionList.Count==0)
{
return false;
}
else
{
return true;
}
}
protected void Button1_Click(object sender, EventArgs e)
{
this.TextBox2.Text = "存檔成功,ID為 "+this.TextBox1.Text;
this.HiddenField1.Value = string.Empty;
ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "detectID", "CallMe('TextBox1','HiddenField1');", true);
}
畫面:(ID 1~4已經存在資料)
1.假設一開始textbox的值從DB撈出來assign給textbox。(測試沒有經過textbox的onchange事件)
2.點了存檔後,因為2已經存在DB,所以confirm。
3.點了取消,則畫面回到第一張圖。點了確定,則執行server端的button.click。
4.再點一次存檔按鈕,則仍顯示confirm,因為DB裡ID為2的資料仍然存在。
5.把ID改為5,點存檔,則沒有顯示confirm,直接執行server端的button.click(sender,e)
結論
這功能與需求其實並沒有什麼,Sample的情況也相當單純,
但是這個過程讓我發現了很多以前自己的觀念不一定是對的。
在面對與想像中不一樣而產生的bug時,再透過已知的觀念和技巧,去將bug fix掉,
再整理出來分享給大家,
希望大家的收穫可以比我還多。
補充相關連結:http://social.msdn.microsoft.com/Forums/zh-TW/236/thread/d9a39279-7626-4ce5-9cb8-df50d44c04ee
blog 與課程更新內容,請前往新站位置:http://tdd.best/