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

選擇文件,點(diǎn)擊上傳按鈕之后:

一切就是這么簡(jiǎn)單!
我還會(huì )繼續完善這個(gè)控件,但是可能需要過(guò)個(gè)幾天才行。這周我會(huì )比較忙,可能不太再會(huì )去碰這個(gè)控件了。等控件成熟之后,我會(huì )詳細分析一下這個(gè)控件的實(shí)現方式。
點(diǎn)擊這里下載源代碼。
PS:
這里向大家道個(gè)歉。本周的WebCast,原計劃是“全面講解UpdatePanel的使用方式”,會(huì )涉及到從服務(wù)器端使用到客戶(hù)端生命周期的方方面面。但是目前看來(lái)這個(gè)內容太多了。因此我會(huì )將其拆分成兩次,3/29的那次只會(huì )對UpdatePanel的服務(wù)器端使用作一個(gè)完整的講解,并且會(huì )涉及到一些UpdatePanel的實(shí)現原理。而下一次得課程,我將會(huì )對客戶(hù)端的生命周期做一個(gè)全面的描述。
雖然分成了兩次,但是我還是盡力保證了每次課程內容的充實(shí)性。
聯(lián)系客服