Asp.Net 無重新整理檔案上傳並顯示進度條的實現方法及思路

來源:互聯網
上載者:User

相信通過Asp.Net的伺服器控制項上傳檔案在簡單不過了,通過AjaxToolkit控制項實現上傳進度也不是什麼難事,為什麼還要自己辛辛苦苦來 實現呢?我並不否認”拿來主義“,只是我個人更喜歡凡是求個所以然。本篇將闡述通過Html,IHttpHandler和 IHttpAsyncHandler實現檔案上傳和上傳進度的原理,希望對你有多協助。

本文涉及到的知識點:
1.前台用到Html,Ajax,JQuery,JQuery UI

2.後台用到一般處理常式(IHttpHandler)和一般非同步處理常式(IHttpAsyncHandler),並涉及到”推模式“

一、建立Html網頁
1、在建立的Web工程中添加一個Html檔案,命名為UploadFile.htm,在標頭檔中引入JQuery,JQuery UI

複製代碼 代碼如下:<link href="Styles/jquery-ui-1.8.16.custom.css" rel="stylesheet" type="text/css" />
    <script src="Scripts/jquery-1.6.2.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>

2、關於無重新整理檔案上傳

通過Ajax是不能上傳檔案的,無重新整理上傳是靠隱藏的iframe來實現的

複製代碼 代碼如下:<form id="form" target = "frameFileUpload" enctype="multipart/form-data">
<div id="progressBar" style="font-size: 1em;"></div>
<input type="file" id="fileUpload" name="fileUpload" /><span id="progressValue"></span>
<iframe id="frameFileUpload" name="frameFileUpload" style="display:none;" ></iframe>
<br />
<input type="submit" value="上傳" id = "submit"/>
</form>

要將form標籤的target屬性設定為iframe的id,當然別忘了將form的enctype設定為multipart/form-data複製代碼 代碼如下:<div id="progressBar" style="font-size: 1em;"></div>

是用來顯示上傳檔案時的進度條

在JS中加入如下處理:

複製代碼 代碼如下:    <script type="text/javascript">
        $(function () {
            $("#submit").button();
            $("#fileUpload").button();
        });
    </script>

此時效果:

二、實現檔案上傳
添加一個一般處理常式,命名為UploadFileHandler.ashx

複製代碼 代碼如下:        public void ProcessRequest(HttpContext context)
        {
            //如果提交的檔案名稱是空,則不處理
            if (context.Request.Files.Count == 0 || string.IsNullOrWhiteSpace(context.Request.Files[0].FileName))
                return;
            //擷取檔案流
            Stream stream = context.Request.Files[0].InputStream;
            //擷取檔案名稱
            string fileName = Path.GetFileName(context.Request.Files[0].FileName);
            //聲明位元組數組
            byte[] buffer;
            //為什麼是4096呢?這是作業系統中最小的分配空間,如果你的檔案只有100個位元組,其實它佔用的空間是4096個位元組
            int bufferSize = 4096;
            //擷取上傳檔案流的總長度
            long totalLength = stream.Length;
            //已經寫入的位元組數,用於做上傳的百分比
            long writtenSize = 0;
            //建立檔案
            using (FileStream fs = new FileStream(@"C:\" + fileName, FileMode.Create, FileAccess.Write))
            {
                //如果寫入檔案的位元組數小於上傳的總位元組數,就一直寫,直到寫完為止
                while (writtenSize < totalLength)
                {
                    //如果剩餘的位元組數不小於最小分配空間
                    if (totalLength - writtenSize >= bufferSize)
                    {
                        //用最小分配空間建立新的位元組數組
                        buffer = new byte[bufferSize];
                    }
                    else
                        //用剩餘的位元組數建立位元組數組
                        buffer = new byte[totalLength - writtenSize];
                    //讀取上傳的檔案到位元組數組
                    stream.Read(buffer, 0, buffer.Length);
                    //將讀取的位元組數組寫入到建立的檔案流中
                    fs.Write(buffer, 0, buffer.Length);
                    //增加寫入的位元組數
                    writtenSize += buffer.Length;
                    //計算當前上傳檔案的百分比
                    long percent = writtenSize * 100 / totalLength;
                }
            }
        }

在form中添加action和method屬性,修改之後的複製代碼 代碼如下:<form action="UploadFileHandler.ashx" method="post" id="form" target = "frameFileUpload" enctype="multipart/form-data">

這樣檔案上傳就完成了。

三、實現檔案上傳的進度顯示
我的思路:

  檔案上傳的處理過程中,是不可以在處理過程中將資訊傳回用戶端的,只有當所有的處理都完畢之後才會傳回用戶端,所以如果是在上面的處理常式中寫 入context.Response.Write(percent);是不可能得到處理的過程,只能等到處理結束後,用戶端一次性得到所有的值。

  要想得到處理過程中的值,我的解決是這樣,在檔案上傳時,要開啟另一個請求,來擷取進度資訊。而這個請求是非同步,我指的是用戶端非同步請求和服 務端非同步處理。因為要涉及到兩個不同的請求處理常式之間資訊的傳遞,將"處理檔案上傳的程式"得到的進度資訊傳遞給"處理進度請求的程式",而"處理進度 請求的處理常式"要依賴於"處理檔案上傳的處理常式"。處理圖:

  首先用戶端同時(幾乎是)發出兩個請求,一個是檔案上傳,一個是進度請求。由於"處理請求進度的程式"是非同步處理的,當該程式沒有資訊發給客戶 端時,我們讓它處於等待狀態,這裡有點像Tcp,這樣用戶端跟伺服器就一直處於串連狀態。當"處理檔案上傳的程式"開始處理時,通過把進度值賦值給"處理 請求進度程式"的非同步作業的狀態,並觸發"處理請求進度的程式"傳回值給用戶端。用戶端擷取進度值,並處理。這樣一次請求進度值的請求就結束了,我們知道 伺服器是不會主動給用戶端發送資訊的,只有用戶端請求,伺服器才會響應。顯然,要想在檔案儲存的過程中向用戶端發送進度資訊,用戶端得到每得到一個返回結 果,都是一次請求。為了得到連續的請求值,用戶端再向"處理請求進度的程式"發出請求,依次迴圈,知道檔案上傳結束。

技術實現:
  非同步處理用到介面IHttpAsyncHandler,建立一個一般處理常式,命名為RequestProgressAsyncHandler.ashx,將預設的介面改為IHttpAsyncHandler

複製代碼 代碼如下:    public class RequestProgressAsyncHandler : IHttpAsyncHandler
    {
        public void ProcessRequest(HttpContext context)
        {
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
        #region IHttpAsyncHandler 成員
        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            throw new NotImplementedException();
        }
        public void EndProcessRequest(IAsyncResult result)
        {
            throw new NotImplementedException();
        }
        #endregion
    }

BeginProcessRequest和EndProcessRequest是兩個核心的方法,其他的兩個不用處理。當該處理常式處理請求 時,BeginProcessRequest是第一個被調用的函數,返回一個包含非同步狀態資訊的對象,該對象是IAsyncResult類型,是實現非同步 的關鍵,用於控制什麼時候調用EndProcessRequest來結束處理常式的等待狀態,BeginProcessRequest被調用之後,程式就 處於等待狀態。EndProcessRequest是在結束請求時的處理函數,通過該函數可以向用戶端寫入資訊。

實現介面IAsyncResult

複製代碼 代碼如下:    public class AsyncResult : IAsyncResult
    {
        // 標示非同步處理的狀態
        private bool isComplete = false;

        //儲存非同步處理常式中的Http上下文
        private HttpContext context;

        //非同步回調的委託
        private AsyncCallback callback;
        /// <summary>
        /// 擷取或設定儲存下載檔案的百分比數值部分
        /// </summary>
        public long PercentNumber;

        public AsyncResult(HttpContext context, AsyncCallback callback)
        {
            this.context = context;
            this.callback = callback;
        }
        /// <summary>
        /// 向用戶端寫入資訊
        /// </summary>
        public void Send()
        {
            this.context.Response.Write(PercentNumber);
        }
        /// <summary>
        /// 完成非同步處理,結束請求
        /// </summary>
        public void DoCompleteTask()
        {
            if (callback != null)
                callback(this);//會觸發處理常式中的EndProcessRequest函數,結束請求
            this.isComplete = true;
        }
        #region IAsyncResult 成員

        public object AsyncState
        {
            get { return null; }
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { return null; }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return isComplete; }
        }

        #endregion

    }

修改 RequestProgressAsyncHandler.ashx檔案:
複製代碼 代碼如下:    public class RequestProgressAsyncHandler : IHttpAsyncHandler
    {
        /// <summary>
        /// 儲存非同步處理狀態資訊的集合
        /// </summary>
        public static List<AsyncResult> AsyncResults = new List<AsyncResult>();
        public void ProcessRequest(HttpContext context)
        {
        }
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
        #region IHttpAsyncHandler 成員

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {

            AsyncResult result = new AsyncResult(context, cb);
            AsyncResults.Add(result);
            return result;
        }

        public void EndProcessRequest(IAsyncResult result)
        {
            //保證集合中只用一個元素
            AsyncResults.Clear();
            AsyncResult ar = (AsyncResult)result;
            ar.Send();
        }

        #endregion
    }

在UploadFileHandler.ashx添加如下代碼:
複製代碼 代碼如下:        private static void SendPercentToClient(long percent)
        {
            //當上傳完畢後,保證處理常式能向用戶端傳回
            while (RequestProgressAsyncHandler.AsyncResults.Count == 0 && percent == 100)
            {

            }
            //因為本處理常式和"處理請求進度的程式"是並發的,不能保證RequestProgressAsyncHandler.AsyncResults一定含有子項
            if (RequestProgressAsyncHandler.AsyncResults.Count != 0)
            {
                RequestProgressAsyncHandler.AsyncResults[0].PercentNumber = percent;
                RequestProgressAsyncHandler.AsyncResults[0].DoCompleteTask();
            }
        }

在函數ProcessRequest中加入以上方法:
複製代碼 代碼如下:             ...
                     ...
             //計算當前上傳檔案的百分比
                    long percent = writtenSize * 100 / totalLength;

                    SendPercentToClient(percent);

服務端OK!修改用戶端,添加JS處理函數:
複製代碼 代碼如下:        function RequestProgress() {
            $.post("RequestProgressAsyncHandler.ashx", function (data, status) {
                if (status == "success") {
                    $("#progressValue").text(data + "%");
                    data = parseInt(data);
                    $("#progressBar").progressbar({ value: data });//JQuery UI 設定進度條值
                    //如果進度不是 100,則重新請求
                    if (data != 100) {
                        RequestProgress();
                    }
                }
            });
        }

在form中添加事件omsubmit的處理函數為RequestProgress
複製代碼 代碼如下:<form action="UploadFileHandler.ashx" onsubmit = "RequestProgress();" method="post" id="form" target = "frameFileUpload" enctype="multipart/form-data">

補充幾點:
1.預設Asp.Net允許的上傳檔案的大小是4M,可以在Web.config中修改其大小限制
複製代碼 代碼如下:    <system.web>
        <httpRuntime maxRequestLength="444444"/>
    </system.web>

maxRequestLength的單位是KB

2.在IE 8.0測試中,在檔案上傳完畢後,狀態列還處於請求中

反正不是後台還在請求,這個放心,只要把滑鼠在按鈕和瀏覽上面來回移動幾下就沒了,可能是JQuery UI 的問題。FF和Chrom下沒這個問題,就是顯示效果會有點差,但是上傳沒問題的。

原始碼下載:UploadFileDemo.rar

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.