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