使用XmlHttpRequest對(duì)象實(shí)現(xiàn)文件上傳進(jìn)度條
用過ajax的朋友應(yīng)該有聽過Asp.Net XmlHttpRequest對(duì)象,ajax其實(shí)就是通過XmlHttpRequest對(duì)象來向服務(wù)器發(fā)出異步請(qǐng)求,并從服務(wù)器獲得數(shù)據(jù),然后用javascript來操作DOM而更新頁面。
本篇就是要通過XmlHttpRequest對(duì)象來實(shí)現(xiàn)實(shí)時(shí)的文件上傳進(jìn)度條顯示。
效果圖:
正文部分:
看過有些前輩的做法是通過設(shè)置HTTP請(qǐng)求的Refresh頭字段來定時(shí)刷新頁面從而顯示進(jìn)度,但是這樣就會(huì)帶動(dòng)整個(gè)頁面一起刷新,就算我們把進(jìn)度條做成單獨(dú)的頁面,效果仍舊不是太好。我之前試過用ajax的Timer組件,但是不知道是何原因,Timer控件在IIS下預(yù)覽時(shí)總是無法正常發(fā)揮作用。苦惱了好一陣子,懷疑是MS的BUG。最后發(fā)現(xiàn)了一個(gè)很好的替代辦法就是利用XmlHttpRequest對(duì)象來自己實(shí)現(xiàn)定時(shí)刷新,這樣每次只需向服務(wù)器請(qǐng)求很少的數(shù)據(jù),減少了對(duì)服務(wù)器的壓力,在后期的測(cè)試中,發(fā)現(xiàn)這個(gè)辦法確實(shí)很好用,而且在IIS下也一切正常(上圖就是IIS下運(yùn)行的效果)。
當(dāng)然如果光有進(jìn)度條沒有數(shù)據(jù),那這個(gè)進(jìn)度條也只能是個(gè)擺設(shè),所以我把接下來的內(nèi)容分成兩塊:進(jìn)度信息的保存、進(jìn)度的顯示
1、進(jìn)度信息的保存
首先我們要明白進(jìn)度條在這里反應(yīng)的是什么的進(jìn)度?毫無疑問是文件上傳的進(jìn)度,我們對(duì)上傳的文件數(shù)據(jù)進(jìn)行了提取,也就是說這個(gè)提取的進(jìn)度就是我們要顯示給客戶端的進(jìn)度,這樣才是文件上傳進(jìn)度條的意義。那就簡(jiǎn)單了,我們只要把已經(jīng)提取的文件大小與總的文件大小比對(duì)一下,就可以知道完成的百分比了??墒菃栴}來了,我們?nèi)绾沃郎蟼髁硕嗌倭四?答案肯定是要用一個(gè)變量來保存已經(jīng)上傳的數(shù)據(jù)量。那這個(gè)變量要放在哪里才能讓我們既可以在進(jìn)度頁面中訪問,又可以在HTTP上傳模塊中訪問呢?
大家肯定知道一般情況下,用戶在多個(gè)頁面之間訪問,會(huì)用到Session對(duì)象或URL傳值來進(jìn)行頁面之前的通信。但是前一篇所介紹的HTTP模塊并不屬于一個(gè)頁面,因此我們無法簡(jiǎn)單的應(yīng)用Session讓進(jìn)度頁面與上傳模塊實(shí)現(xiàn)通信。這里主要還是借鑒高山來客的思路:首先構(gòu)建一個(gè)用于存放文件信息的類,該類主要用來保存文件信息,如:文件名,路徑,當(dāng)前上傳的數(shù)據(jù)量,上傳時(shí)間等。然后設(shè)置一個(gè)針對(duì)某次上傳的唯一ID做為頁面中通信的暗號(hào),擁有這個(gè)暗號(hào)的頁面才能獲取對(duì)應(yīng)于某次上傳的文件信息?,F(xiàn)在已經(jīng)有了兩個(gè)變量了,接著就要使這兩個(gè)變量可以被多個(gè)頁面所使用,方法就是在上傳頁面中,將這個(gè)ID變量注冊(cè)為該頁面的一個(gè)隱藏域,這樣包含這個(gè)頁面的HTTP請(qǐng)求流中就會(huì)包含那個(gè)上傳ID。另一個(gè)類變量就保存在頁面緩存Cache中,并用上傳ID做為其編號(hào)。
現(xiàn)在假設(shè)已經(jīng)有了這么一個(gè)用于存放文件信息的類UploadFileInfo。
首先我們要在上傳頁面的PageLoad中new一個(gè)ID,然后注冊(cè)一個(gè)隱藏域用來保存此ID,同時(shí)實(shí)例化UploadFileInfo類,并將相應(yīng)的信息寫入該類,最后把該類放入Catch:
- if (!IsPostBack)
- {
- UploadFileInfo ufi = new UploadFileInfo();
- ufi.strFileGuid = Guid.NewGuid().
- ToString;//用GUID來表示唯一的ID;
- ufi.strTempDir = Server.MapPath
- ("TempUpload/" + ufi.strFileGuid + "http://");
- ClientScript.RegisterHiddenField
- ("UploadID", ufi.strFileGuid);
- //隱藏域,名字為UploadID,值為ufi.strFileGuid
- HttpContext.Current.Cache.Add(ufi.strFileGuid, ufi,
- null, DateTime.Now.AddDays(10), TimeSpan.Zero,
- System.Web.Caching.CacheItemPriority.High, null);//加入到Catch中
- }
經(jīng)過以上步驟,我們就可以在HTTP模塊中訪問了。
因?yàn)樵谶@次的HTTP請(qǐng)求流中包含了一個(gè)隱藏域,所以我們可以對(duì)獲取的HTTP請(qǐng)求流進(jìn)行分析,從而獲取相應(yīng)的上傳ID,也就是我們之前說的暗號(hào)。然后通過Cache的編號(hào)找到Cache中的文件信息對(duì)象,從而我們可以在后來的數(shù)據(jù)讀取過程中對(duì)該對(duì)象的上傳數(shù)據(jù)量進(jìn)行修改。由于是放在Cache中,加之是一個(gè)引用對(duì)象,所以對(duì)該對(duì)象修改后,其它代碼訪問到的都是最新的值。
- string sguid = GetUploadId(bPreloadedEnitityBody,
- eContentEncode);//GetUploadId
- 是自己寫的一個(gè)方法用來從請(qǐng)求流中獲取上傳ID
- UploadFileInfo ufiFileInfo = (UploadFileInfo)HttpContext.
- Current.Cache[sguid];//取出文件信息對(duì)象
其它頁面如果要使用這個(gè)對(duì)象就得先獲取ID,之后就可以自由操作了。
2、文件上傳進(jìn)度條的顯示
從圖中我們可以看到,當(dāng)顯示進(jìn)度的時(shí)候,背后的頁面成灰色,并且無法響應(yīng)任何事件,有點(diǎn)類似模態(tài)窗口。這個(gè)效果大家可以在網(wǎng)上查查,還是挺容易實(shí)現(xiàn)的。我這里有一段js顯示此效果的代碼(搜集于網(wǎng)上):
- functionModalDialog
- (name,divid,width,height,leftop,topop,color)
- {
- this.name=name;//名稱
- this.div=divid;//要放入窗體中的元素名稱
- this.width=width;//窗體寬
- this.height=height;//窗體高
- this.leftop=leftop;//左側(cè)位置
- this.topop=topop;//上部位置
- this.color=color;//整體顏色
- this.show=function()//顯示窗體
- {
- document.all(obj.name+"_divshow").style.
- width=obj.width;
- document.all(obj.name+"_divshow").style.
- height=obj.height;
- document.all(obj.name+"_divshow").style.
- left=obj.leftop;
- document.all(obj.name+"_divshow").style.
- top=obj.topop;
- document.all(obj.name+"_mask").style.
- width=document.body.clientWidth;
- document.all(obj.name+"_mask").style.
- height=document.body.clientHeight;
- document.all(obj.name+"_divshow").style.
- visibility="visible";
- document.all(obj.name+"_mask").style.
- visibility="visible";
- }
- this.close=function()//關(guān)閉窗體
- {
- document.all(obj.name+"_divshow").style.width=0;
- document.all(obj.name+"_divshow").style.height=0;
- document.all(obj.name+"_divshow").style.left=0;
- document.all(obj.name+"_divshow").style.top=0;
- document.all(obj.name+"_mask").style.width=0;
- document.all(obj.name+"_mask").style.height=0;
- document.all(obj.name+"_divshow").
- style.visibility="hidden";
- document.all(obj.name+"_mask").
- style.visibility="hidden";
- }
- this.toString=function()
- {
- vartmp="〈divid='"+this.name+"_divshow'
- style='position:absolute;left:0;top:0;z-index:10;
- visibility:hidden;width:0;height:0'〉";
- tmp+="〈tablecellpadding=0cellspacing=
- 0border=0width=100%height=100%〉";
- tmp+="〈tr〉";
- tmp+="〈tdid='"+this.name+"_content'valign=top〉〈/td〉";
- tmp+="〈/tr〉"
- tmp+="〈/table〉";
- tmp+="〈/div〉";
- tmp+="〈divid='"+this.name+"_mask'
- style='position:absolute;top:0;left:0;width:0;height:0;
- background:#666;filter:ALPHA(opacity=50);
- z-index:9;visibility:hidden'〉〈/div〉";
- document.write(tmp);
- document.all(this.name+"_content").insertBefore
- (document.all(this.div));
- }
- varobj=this;
- }
接著講我們的重點(diǎn):如何實(shí)現(xiàn)定時(shí)局部刷新。關(guān)于XmlHttpRequest對(duì)象,我這里就不詳細(xì)講述了,提供大家一個(gè)關(guān)于此的手冊(cè)下載。為了大家更容易理解,我舉個(gè)小例子:
- //頁面A.aspx
- functionreturnresponse(url)
- {
- varxmlHttp=newActiveXObject('MSXML2.xmlHttp');
- if(xmlHttp!=null)
- {
- xmlHttp.open("GET",url,true);
- //向URL指定的頁面發(fā)送GET請(qǐng)求
- xmlHttp.onreadystatechange=function()
- {//當(dāng)xmlHttp的readyState
- 改變的時(shí)候就會(huì)引發(fā)這個(gè)事件
- if(xmlHttp.readyState==4&&xmlHttp.status==200)
- {//4="成功發(fā)送",200="所請(qǐng)求的頁面返回正常"
- temp=xmlHttp.responseText;
- //接收所請(qǐng)求頁面發(fā)回的數(shù)據(jù)
- alert(temp);
- }
- }
- xmlHttp.send(null);
- }
- else
- {
- alert("瀏覽器不支持XmlHttp.");
- }
- }
- //URL所指向的頁面B的代碼.cs,
- 當(dāng)然也可以是同一個(gè)頁面的cs
- if(Request.QueryString["event"]=="test")
- {
- Response.Write("測(cè)試");
- }
- /**//*
- 然后我們?cè)贏頁中執(zhí)行returnresponse
- (B.aspx?event="test");
- 很快就會(huì)發(fā)現(xiàn)在A頁中彈出一個(gè)窗口,內(nèi)容是"測(cè)試"。
- */
通過以上小例子,大家應(yīng)該已經(jīng)對(duì)該XmlHttpRequest對(duì)象有所了解了吧。為實(shí)現(xiàn)定時(shí)刷新,我把進(jìn)度條單獨(dú)放在一個(gè)頁面中(如A.aspx),通過js的setTimeout來定時(shí)執(zhí)行類似returnresponse這樣的方法,然后在A.aspx.cs代碼中獲取文件信息對(duì)象,接著通過Response來反饋進(jìn)度信息。這樣在A.aspx頁面中就可以獲取到信息,并進(jìn)行顯示了。但是執(zhí)行ActiveXObject將要花費(fèi)不少代價(jià),而且我們是定時(shí)執(zhí)行該方法,顯然會(huì)造成性能下降。在參考了構(gòu)建一個(gè)pool來管理無刷新頁面的xmlhttp對(duì)象后,決定采用這一方法,事實(shí)證明該方法實(shí)現(xiàn)文件上傳進(jìn)度條確實(shí)有效。
- functionxmlHttpPoolFactory()
- {
- this.XmlHttpPool=newArray();
- this.MaxPoolLength=10;
- this.Add=function()
- {
- if(this.XmlHttpPool.length〈this.MaxPoolLength)
- {
- xmlHttp=null;
- if(window.XMLHttpRequest)
- {//codeforallnewbrowsers
- xmlHttp=newXMLHttpRequest();
- }
- elseif(window.ActiveXObject)
- {//codeforIE5andIE6
- try
- {
- xmlHttp=newActiveXObject('MSXML2.xmlHttp');
- }
- catch(e)
- {
- try
- {
- xmlHttp=newActiveXObject('Microsoft.xmlHttp');
- }
- catch(e2)
- {
- }
- }
- }
- if(xmlHttp!=null)
- {
- this.XmlHttpPool.push(xmlHttp);
- }
- returnxmlHttp;
- }
- };
- this.GetXmlHttp=function()
- {
- varxmlHttp=null;
- varpool=this.XmlHttpPool;
- for(vari=0;i〈pool.length;++i)
- {
- if(pool[i].readyState==4||pool[i].readyState==0)
- {
- xmlHttp=pool[i];
- break;
- }
- }
- if(xmlHttp==null)
- {
- returnthis.Add();
- }
- returnxmlHttp;
- };
- this.returnresponse=function(url,div)
- {
- varxmlHttp=this.GetXmlHttp();
- varparam=div.split(',');
- if(xmlHttp!=null)
- {
- xmlHttp.open("GET",url,true);
- xmlHttp.onreadystatechange=function()
- {
- if(xmlHttp.readyState==4&&xmlHttp.status==200)
- {//4="loaded",200="OK"
- temp=xmlHttp.responseText;
- vartemparray=temp.split(",");
- document.getElementById(param[0]).
- innerText=temparray[0];
- document.getElementById(param[1]).
- innerText=temparray[1]+"KB/S";
- document.getElementById(param[2]).
- innerHTML="
- 〈tablewidth='"+temparray[2]*3+"' 〉
- 〈tr 〉〈td 〉〈/td 〉〈/tr 〉〈/table 〉";
- document.getElementById(param[3]).
- innerText=temparray[2]+"%";
- }
- }
- xmlHttp.send(null);
- }
- else
- {
- alert("YourbrowserdoesnotsupportxmlHttp.");
- }
- };
- this.AportAll=function()
- {
- for(vari=0;i〈this.XmlHttpPool.length;++i)
- {
- this.XmlHttpPool[i].abort();
- }
- };
- }
- vara=newxmlHttpPoolFactory();//建立一個(gè)全局的工廠實(shí)例
- varstrevent="";
- functionrefresh(url,interval,div)
- {//該方法我用來定時(shí)刷新,因?yàn)槌薙etTimeout還有一些其它活要干
- varstr1="";
- for(i=2;i〈arguments.length;i++)
- {//因?yàn)榭赡苄枰⑿碌膁iv不只一個(gè),
- 所以利用js的arguments來解決動(dòng)態(tài)參數(shù)的問題
- if(i!=arguments.length-1)
- {
- str1=str1+arguments[i]+",";
- }
- else
- {
- str1=str1+arguments[i];
- }
- }
- a.returnresponse(url,str1);//調(diào)用該方法實(shí)現(xiàn)異部通信
- varstr="";
- for(i=0;i〈arguments.length;i++)
- {
- if(i!=arguments.length-1)
- str=str+"'"+arguments[i]+"',";
- else
- str=str+"'"+arguments[i]+"'";
- }
- setTimeout("refresh("+str+")",interval);//定時(shí)執(zhí)行該方法
- }
【編輯推薦】