asp.net中实现带进度显示的文件上传功能有很多选择,比如:Html5 XMLHttpRequest2、第三方控件、flash插件等等。本文介绍使用IHttpAsyncHandler对象实现进度显示。
其实采用这种方式并非最佳方案,但可以通过这个案例让我们了解IHttpAsyncHandler的工作原理,了解了它的工作原理其实我们还可以通过它来模拟长连接,实现即时通讯功能。
我们一般用HttpHandler来处理请求,处理过程是同步的,和它对应的就是IHttpAsyncHandler,它是一个异步的http请求处理对象。当请求到达时可以将任务交给另一线程执行,而不阻塞当前代码,从而达到异步效果,当需要返回结果时调用回调方法。好处是可以快速回收工作线程,使得整个站点可以提高并发处理量。
这里我们用到三个handler:guidGenerate.ashx(用来生成客户端唯一标示)、uploadFileHandler.ashx(用来上传文件)、requestProgressAsyncHandler.ashx(用来处理上传进度请求)
首先通过guidGenerate.ashx生成客户端唯一标示sessionId,上传时同时启动上传和接收进度请求,并带上生成的sessionId。进度没有变化时hold住,不返回请求,直到进度变化时【减少无谓的请求次数,避免增加服务器负担】返回客户端进度信息,并重新启动一个请求进度http get请求,依次循环就能实现实时进度显示。
这里有两种思路:
一、直接将处理的进度传给当前进度请求对象,并返回客户端。即上传线程和进度处理再同一个线程完成。
二、将处理的进度信息存在一个静态对象中,然后额外启动一个线程专门处理请求进度响应。
建议采用第二种方法,上传和进度请求分离,互不影响。
1、uploadFileHandler.ashx代码实现:
public class uploadFileHandler : IHttpHandler
{
//private static object _syncobject = new object();
public void ProcessRequest(HttpContext context)
{
object sessionId = context.Request.Form["sessionId"];
uploadFile(context);
}
private void uploadFile(object httpContext)
{
HttpContext context = httpContext as HttpContext;
object sessionId = context.Request.Form["sessionId"];
//如果提交的文件名是空,则不处理
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;
long lastPercent = 0;//存储上一次进度
//创建文件
using (FileStream fs = new FileStream(context.Server.MapPath("Upload") + "\\" + fileName, FileMode.Create, FileAccess.Write))
{
//如果写入文件的字节数小于上传的总字节数,就一直写,直到写完为止
int count=0;
while (writtenSize < totalLength)
{
System.Threading.Thread.Sleep(100);
//如果剩余的字节数不小于最小分配空间
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;
//计算当前上传文件的百分比
if (count == 0)
{
count++;
lastPercent = percent;
continue;
}
else
{
if (lastPercent != percent)//如果进度有变化则推送到前台显示
{
try
{
lastPercent = percent;
#region 方法1、当前线程处理进度
//SendPercentToClient(percent, sessionId.ToString());
#endregion
#region 方法2、另起一个线程集中处理所有请求进度
if (!requestProgressAsyncHandler.initThread)
{
requestProgressAsyncHandler.initThread = true;
Thread thread = new Thread(new ThreadStart(SendPercentToClient));
thread.Start();
}
if (requestProgressAsyncHandler.dictionPercent.ContainsKey(sessionId.ToString()))
{
requestProgressAsyncHandler.dictionPercent[sessionId.ToString()] = percent;
}
else
{
requestProgressAsyncHandler.dictionPercent.Add(sessionId.ToString(), percent);
}
#endregion
}
catch (Exception ex)
{
writeInLog("SessionId:" + sessionId.ToString() + ",PercentNumber:" + percent.ToString() + ex.Message + " " + ex.StackTrace);
}
}
}
}
}
}
public bool IsReusable
{
get
{
return false;
}
}
private void Process(string[] keys)
{
foreach (string key in keys)
{
if (requestProgressAsyncHandler.dictionRequest.ContainsKey(key))
{
AsyncResult result = requestProgressAsyncHandler.dictionRequest[key];
if (requestProgressAsyncHandler.dictionPercent.ContainsKey(key))
{
long currentPercent=requestProgressAsyncHandler.dictionPercent[key];
result.PercentNumber = currentPercent;
result.DoCompleteTask();
lock (requestProgressAsyncHandler._syncobject)
{
if (requestProgressAsyncHandler.dictionRequest.ContainsValue(result))
{
requestProgressAsyncHandler.dictionRequest.Remove(key);
}
if (currentPercent == 100)
{
requestProgressAsyncHandler.dictionPercent.Remove(key);
}
}
}
}
}
}
private void SendPercentToClient()
{
while (true)
{
string[] keys;
lock (requestProgressAsyncHandler._syncobject)
{
keys = requestProgressAsyncHandler.dictionRequest.Keys.ToArray();
}
if (keys.Length == 0)
{
requestProgressAsyncHandler.autoEvent.WaitOne();
requestProgressAsyncHandler.autoEvent.Reset();
}
else
{
Process(keys);
}
}
}
private void SendPercentToClient(long percent, string sessionId)
{
try
{
bool flag=true;
while (flag)
{
if (requestProgressAsyncHandler.dictionRequest.ContainsKey(sessionId))
{
AsyncResult ar = requestProgressAsyncHandler.dictionRequest[sessionId];
if (ar != null && ar.context != null)
{
if (ar.context.Response.IsClientConnected)
{
ar.PercentNumber = percent;
ar.DoCompleteTask();
lock (requestProgressAsyncHandler._syncobject)
{
if (requestProgressAsyncHandler.dictionRequest.ContainsValue(ar))
{
requestProgressAsyncHandler.dictionRequest.Remove(sessionId);
}
}
}
}
flag = false;
}
else
{
if (percent == 100)//如果上传完成后当前未有进度请求则一直等待
{
requestProgressAsyncHandler.autoEvent.WaitOne();
requestProgressAsyncHandler.autoEvent.Reset();
}
else
flag = false;
}
}
}
catch (Exception ex)
{
writeInLog(ex.Message+" "+ex.StackTrace);
}
}
}
2、requestProgressAsyncHandler.ashx代码实现:
public class requestProgressAsyncHandler : IHttpAsyncHandler
{
//存储所有进度请求对象
public static Dictionary<string, AsyncResult> dictionRequest = new Dictionary<string, AsyncResult>();
//存储处理进度信息
public static Dictionary<string, long> dictionPercent = new Dictionary<string, long>();
public static readonly object _syncobject = new object();
public static bool initThread = false;
public static AutoResetEvent autoEvent = new AutoResetEvent(false);
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);
string sessionId = context.Request.QueryString["sessionId"].ToString();
try
{
lock (_syncobject)
{
dictionRequest.Add(sessionId, result);
}
autoEvent.Set();//通知线程工作
}
catch (Exception ex)
{
writeInLog(ex.Message + " " + ex.StackTrace);
}
return result;
}
public void EndProcessRequest(IAsyncResult result)
{
//返回上传进度
AsyncResult ar = (AsyncResult)result;
ar.Send();
}
#endregion
}
public class AsyncResult : IAsyncResult
{
// 标示异步处理的状态
private bool isComplete = false;
//保存异步处理程序中的Http上下文
public HttpContext context;
//异步回调的委托
private AsyncCallback callback;
/// <summary>
/// 获取或设置保存下载文件的百分比数值部分
/// </summary>
public long PercentNumber;
//public string SessionId;
public AsyncResult(HttpContext context, AsyncCallback callback)
{
this.context = context;
this.callback = callback;
//this.SessionId = context.Request.QueryString["sessionId"].ToString();
}
/// <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
}
3、运行效果如下:
源代码可以在http://download.csdn.net/detail/taoerchun/9481759地址下载,有疑问的朋友可以随时向我留言。另外感谢程序诗人的一遍文章给我的启示。