使用XmlHttpRequest對象實現(xiàn)文件上傳進度條
用過ajax的朋友應(yīng)該有聽過Asp.Net XmlHttpRequest對象,ajax其實就是通過XmlHttpRequest對象來向服務(wù)器發(fā)出異步請求,并從服務(wù)器獲得數(shù)據(jù),然后用javascript來操作DOM而更新頁面。
本篇就是要通過XmlHttpRequest對象來實現(xiàn)實時的文件上傳進度條顯示。
效果圖:

正文部分:
看過有些前輩的做法是通過設(shè)置HTTP請求的Refresh頭字段來定時刷新頁面從而顯示進度,但是這樣就會帶動整個頁面一起刷新,就算我們把進度條做成單獨的頁面,效果仍舊不是太好。我之前試過用ajax的Timer組件,但是不知道是何原因,Timer控件在IIS下預覽時總是無法正常發(fā)揮作用??鄲懒撕靡魂囎?,懷疑是MS的BUG。最后發(fā)現(xiàn)了一個很好的替代辦法就是利用XmlHttpRequest對象來自己實現(xiàn)定時刷新,這樣每次只需向服務(wù)器請求很少的數(shù)據(jù),減少了對服務(wù)器的壓力,在后期的測試中,發(fā)現(xiàn)這個辦法確實很好用,而且在IIS下也一切正常(上圖就是IIS下運行的效果)。
當然如果光有進度條沒有數(shù)據(jù),那這個進度條也只能是個擺設(shè),所以我把接下來的內(nèi)容分成兩塊:進度信息的保存、進度的顯示
1、進度信息的保存
首先我們要明白進度條在這里反應(yīng)的是什么的進度?毫無疑問是文件上傳的進度,我們對上傳的文件數(shù)據(jù)進行了提取,也就是說這個提取的進度就是我們要顯示給客戶端的進度,這樣才是文件上傳進度條的意義。那就簡單了,我們只要把已經(jīng)提取的文件大小與總的文件大小比對一下,就可以知道完成的百分比了??墒菃栴}來了,我們?nèi)绾沃郎蟼髁硕嗌倭四?答案肯定是要用一個變量來保存已經(jīng)上傳的數(shù)據(jù)量。那這個變量要放在哪里才能讓我們既可以在進度頁面中訪問,又可以在HTTP上傳模塊中訪問呢?
大家肯定知道一般情況下,用戶在多個頁面之間訪問,會用到Session對象或URL傳值來進行頁面之前的通信。但是前一篇所介紹的HTTP模塊并不屬于一個頁面,因此我們無法簡單的應(yīng)用Session讓進度頁面與上傳模塊實現(xiàn)通信。這里主要還是借鑒高山來客的思路:首先構(gòu)建一個用于存放文件信息的類,該類主要用來保存文件信息,如:文件名,路徑,當前上傳的數(shù)據(jù)量,上傳時間等。然后設(shè)置一個針對某次上傳的唯一ID做為頁面中通信的暗號,擁有這個暗號的頁面才能獲取對應(yīng)于某次上傳的文件信息?,F(xiàn)在已經(jīng)有了兩個變量了,接著就要使這兩個變量可以被多個頁面所使用,方法就是在上傳頁面中,將這個ID變量注冊為該頁面的一個隱藏域,這樣包含這個頁面的HTTP請求流中就會包含那個上傳ID。另一個類變量就保存在頁面緩存Cache中,并用上傳ID做為其編號。
現(xiàn)在假設(shè)已經(jīng)有了這么一個用于存放文件信息的類UploadFileInfo。
首先我們要在上傳頁面的PageLoad中new一個ID,然后注冊一個隱藏域用來保存此ID,同時實例化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模塊中訪問了。
因為在這次的HTTP請求流中包含了一個隱藏域,所以我們可以對獲取的HTTP請求流進行分析,從而獲取相應(yīng)的上傳ID,也就是我們之前說的暗號。然后通過Cache的編號找到Cache中的文件信息對象,從而我們可以在后來的數(shù)據(jù)讀取過程中對該對象的上傳數(shù)據(jù)量進行修改。由于是放在Cache中,加之是一個引用對象,所以對該對象修改后,其它代碼訪問到的都是最新的值。
- string sguid = GetUploadId(bPreloadedEnitityBody,
 - eContentEncode);//GetUploadId
 - 是自己寫的一個方法用來從請求流中獲取上傳ID
 - UploadFileInfo ufiFileInfo = (UploadFileInfo)HttpContext.
 - Current.Cache[sguid];//取出文件信息對象
 
其它頁面如果要使用這個對象就得先獲取ID,之后就可以自由操作了。
2、文件上傳進度條的顯示
從圖中我們可以看到,當顯示進度的時候,背后的頁面成灰色,并且無法響應(yīng)任何事件,有點類似模態(tài)窗口。這個效果大家可以在網(wǎng)上查查,還是挺容易實現(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;
 - }
 
接著講我們的重點:如何實現(xiàn)定時局部刷新。關(guān)于XmlHttpRequest對象,我這里就不詳細講述了,提供大家一個關(guān)于此的手冊下載。為了大家更容易理解,我舉個小例子:
- //頁面A.aspx
 - functionreturnresponse(url)
 - {
 - varxmlHttp=newActiveXObject('MSXML2.xmlHttp');
 - if(xmlHttp!=null)
 - {
 - xmlHttp.open("GET",url,true);
 - //向URL指定的頁面發(fā)送GET請求
 - xmlHttp.onreadystatechange=function()
 - {//當xmlHttp的readyState
 - 改變的時候就會引發(fā)這個事件
 - if(xmlHttp.readyState==4&&xmlHttp.status==200)
 - {//4="成功發(fā)送",200="所請求的頁面返回正常"
 - temp=xmlHttp.responseText;
 - //接收所請求頁面發(fā)回的數(shù)據(jù)
 - alert(temp);
 - }
 - }
 - xmlHttp.send(null);
 - }
 - else
 - {
 - alert("瀏覽器不支持XmlHttp.");
 - }
 - }
 - //URL所指向的頁面B的代碼.cs,
 - 當然也可以是同一個頁面的cs
 - if(Request.QueryString["event"]=="test")
 - {
 - Response.Write("測試");
 - }
 - /**//*
 - 然后我們在A頁中執(zhí)行returnresponse
 - (B.aspx?event="test");
 - 很快就會發(fā)現(xiàn)在A頁中彈出一個窗口,內(nèi)容是"測試"。
 - */
 
通過以上小例子,大家應(yīng)該已經(jīng)對該XmlHttpRequest對象有所了解了吧。為實現(xiàn)定時刷新,我把進度條單獨放在一個頁面中(如A.aspx),通過js的setTimeout來定時執(zhí)行類似returnresponse這樣的方法,然后在A.aspx.cs代碼中獲取文件信息對象,接著通過Response來反饋進度信息。這樣在A.aspx頁面中就可以獲取到信息,并進行顯示了。但是執(zhí)行ActiveXObject將要花費不少代價,而且我們是定時執(zhí)行該方法,顯然會造成性能下降。在參考了構(gòu)建一個pool來管理無刷新頁面的xmlhttp對象后,決定采用這一方法,事實證明該方法實現(xiàn)文件上傳進度條確實有效。
- 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();//建立一個全局的工廠實例
 - varstrevent="";
 - functionrefresh(url,interval,div)
 - {//該方法我用來定時刷新,因為除了SetTimeout還有一些其它活要干
 - varstr1="";
 - for(i=2;i〈arguments.length;i++)
 - {//因為可能需要刷新的div不只一個,
 - 所以利用js的arguments來解決動態(tài)參數(shù)的問題
 - if(i!=arguments.length-1)
 - {
 - str1=str1+arguments[i]+",";
 - }
 - else
 - {
 - str1=str1+arguments[i];
 - }
 - }
 - a.returnresponse(url,str1);//調(diào)用該方法實現(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);//定時執(zhí)行該方法
 - }
 
【編輯推薦】















 
 
 






 
 
 
 