2005-07-22

ASP.Net 中 Form_name.submit() 的靈異現像~

這兩天同事寫的ASP.Net網頁元件有一種怪異的現像發生…
現像如何?待我說來吧~

主角:同事開發的custom control元件,此處稱為「A君」
某同事放的一個web control--LinkButton,此處稱為「LB君」
來插花的使用者,此處稱為「路人甲」

主角行為描述:LB君在server side有一個method是其Click事件的delegation,
此事件會執行一行程式碼『Response.Redirect("TextFile1.zip");』,
此動作會讓前端的路人甲看到檔案下載確認視窗。
A君看起來是一個網頁上的Button,他的client side script event:onclick被觸發後
(就是A君被點了一下之後)會執行『Form_name.submit();』,用來將所在的form送出。
路人甲就是什麼都不知道,只想按按看按鍵的天神user。

劇情:路人甲他要先按了LB君下載完檔案之後再點一下A君做其他事。
當路人甲按了LB君之後正常跳出下載檔案對話窗,接著路人甲下載完檔案後便點了A君一下,
這時神奇的事發生了,剛剛出現過的下載檔案對話窗又出現了!
路人甲便在覺得莫明其妙的狀況下又下載了一次檔案…

發現問題:經過一陣兵荒馬亂的debug之後發現問題完全不在server side…
其實一切的一切都很合理,也完全沒有靈異現像,至於為什麼會發生這樣的結果,
我們要從ASP.Net的事件觸發方式開始討論起。

寫過以前的ASP或JSP或有其他任何動態網頁程式開發經驗的人都瞭解,
server side是不可能無端端知道client side的使用者剛剛按了那個按扭,
或是那個DropDownList剛被更動過。
那為什麼ASP.Net的網頁可以讓程式設計師像寫WinForm程式一樣產生UI上control所觸發的事件呢?
重點就在於client side的一支名叫,『__doPostBack(eventTarget, eventArgument)』
的script function,這支function做的事很簡單但卻很重要,它的程式碼如下:
===============================================================================
function __doPostBack(eventTarget, eventArgument) {
var theform;
if (window.navigator.appName.toLowerCase().indexOf("netscape") > -1) {
theform = document.forms["Form1"];
}
else {
theform = document.Form1;
}
theform.__EVENTTARGET.value = eventTarget.split("$").join(":");
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
===============================================================================
這支function就是負責將client side觸發事件control(可能是button或是dropdownlist甚或是textbox)
的id和它要傳送的event由__EVENTTARGET和__EVENTARGUMENT這兩個隱藏欄位送到server side,
server side在收到form的request之後便會依這兩個欄位的值來決定server side要執行那個method。

所以之前路人甲點了LB君之後再點A君會發生LB君的事件被再次觸發就有了合理的解釋,
在路人甲點了LB君之後,LB君會以__doPostBack('LB君','');的方式將訊息送到server side,
此時__doPostBack()會把__EVENTTARGET.value的值設為'LB君',
server在看到這段之後就去找到LB君在server side的delegation,進而達成他應該達成的任務。
之後路人甲又點了A君,因為A君是單純的以client script呼叫Form_name.submit(),
因此剛剛已經被設了值的__EVENTTARGET.value還是保留著剛剛被__doPostBack()更改過的值,
因此server side還是以為這次呼叫的也是LB君,因此還是盡責的把該做的事再做一次。

解決方案:知道了前因後果後,要解決這個問題就簡單多了。
其實我們要做的就是把A君在client side的動作由Form_name.submit()
改成呼叫__doPostBack('A君','')就可以了,又或著可以在呼叫Form_name.submit()前先把
__EVENTTARGET和__EVENTARGUMENT兩個隱藏欄位的value清空就可以了。

後記:ASP.Net讓web開發作業變的更簡單輕鬆,但卻也在程式的背後做了更多我們沒看到的事。
所以發生問題時我們必需要更細心的去尋找其所以然,否則便常常會發生所謂的靈異事件了。