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