Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现)
相信通过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
分类: Asp.Net 杂谈
标签: Asp.Net无刷新上传文件 , Asp.Net上传文件并显示进度条 , Asp.net非服务器控件实现文件上传
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息查看更多关于Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现)的详细内容...