不過,其實這只是個半成品,或者說是一個原型,但是很明顯,我們做對了。:)
在實現(xiàn)上,我曾經(jīng)在兩個做法上斟酌了許久,第一種是繼承ScriptManager,第二種則是提供一個新的控件。最終我選擇了第二種方案,因為它能夠避免和ScriptManager過渡耦合,在使用上也更加方便。
實現(xiàn)方式簡析:
做這樣一個控件的思路其實非常簡單,那就是一個字:“騙”。你騙倒了客戶端,再騙倒了服務器端,一切不就成了嗎?
我把這個控件叫做AjaxFileUploadHelper。首先,它會輸出一段JavaScript腳本,用來修改客戶端的PageRequestManager類。我保存了它用于提交請求的方法,并且使用相同的名字重寫這個方法。在新提交方法中,首先判斷頁面中是否存在<input type="file" />元素,如果不存在,則使用原有方法提交,否則就開始我們的提交邏輯,例如創(chuàng)建隱藏的iframe等等。
由于按照ASP.NET AJAX的實現(xiàn),它是在Request Header里放入特殊的標記。我們?nèi)绻獙?shù)據(jù)POST到服務器端,則做不到這一點。因此,我們只能在客戶端使用JavaScript創(chuàng)建<input type="hidden" />,以此作為特殊標記。頁面中的AjaxFileUploadHelper會“盡快(但是總是要慢于ScriptManager)”檢查Request Body里的特殊標記,然后使用“反射”修改ScriptManager對象的屬性,并且“彌補”一些因為它沒有在“第一時間”做出反應而出現(xiàn)的問題。這樣,剩下的操作,ScriptManager就會認為它正在進行一個UpdatePanel刷新了。當然,我們可以在服務器端使用客戶端上傳的文件。
然后要做的就是使用自己的頁面輸出方法替換掉ASP.NET AJAX提供的頁面輸出方法,然后根據(jù)客戶端能夠識別的方式,重新提供輸出。由于ASP.NET AJAX“封裝”的過于完好,我甚至無法重新指定新的Content-Type(ASP.NET AJAX使用了text/plain作為Content-Type,再FireFox中直接用iframe顯示則會出現(xiàn)一些問題),最后只能使用大量的反射用于輸出與客戶端配套的JavaScript代碼——沒錯,是JavaScript。誰讓我們放棄了XMLHttpRequest呢,我們既然使用了iframe就要放置一個頁面了。
客戶端的代碼自然會響應iframe的onload事件,然后查找iframe里頁面中有沒有我們需要的JavaScript方法,如果沒有,則說明出現(xiàn)了錯誤,于是就要按照PageRequestManager的規(guī)則來“表現(xiàn)”錯誤。如果一切正常,則客戶端就可以獲得以前必須要從XMLHttpRequest中才能獲得的字符串。接著組成我們偽造的對象,交給原有的客戶端方法去解析。剩下的,一切照舊。
JavaScript真的很容易騙,不像客戶端代碼,非要使用反射……
上面的描述聽上去似乎很簡單,不過在編寫的控件中,一些細節(jié)方面的問題還是非常麻煩的。如果有機會,再讓我慢慢道來。
目前控件還需要改進的地方:
目前控件只是一個半成品,它還有以下一些需要改進的地方:
控件的使用方式:
控件的使用非常簡單,我們只需要在代碼中緊貼ScriptManager控件放置一個AjaxFileUploadHelper控件即可(這很重要,因為AjaxFileUploadHelper需要在第一時間讓ScriptManager“認為”目前是部分刷新)。如下:
然后我們就可以隨意在UpdatePanel內(nèi)或外放置FileUpload控件了(當然,您自己寫<input type="file" />也是可以的)。如下:
與之對應的Code Behind代碼是:
我們來看一下使用效果。第一次打開頁面時,頁面上的兩個時間相同:
選擇文件,點擊上傳按鈕之后:
一切就是這么簡單!
我還會繼續(xù)完善這個控件,但是可能需要過個幾天才行。這周我會比較忙,可能不太再會去碰這個控件了。等控件成熟之后,我會詳細分析一下這個控件的實現(xiàn)方式。
點擊這里下載源代碼。
PS:
這里向大家道個歉。本周的WebCast,原計劃是“全面講解UpdatePanel的使用方式”,會涉及到從服務器端使用到客戶端生命周期的方方面面。但是目前看來這個內(nèi)容太多了。因此我會將其拆分成兩次,3/29的那次只會對UpdatePanel的服務器端使用作一個完整的講解,并且會涉及到一些UpdatePanel的實現(xiàn)原理。而下一次得課程,我將會對客戶端的生命周期做一個全面的描述。
雖然分成了兩次,但是我還是盡力保證了每次課程內(nèi)容的充實性。