天天看点

ASP.Net 大文件上传解决方案

在ASP.Net中,大文件上传一直是个棘手的问题。在WEB.Config文件中有对上传文件大小的限制。对于稍大点的文件可以适当更改配置,但我们不能无限制的括大它,这是由ASP.Net的上传机制所决定的,因为.Net是把文件全部加载到内存中才可以进行操作的(MSDN上说,对于过大的文件实际上也还是要放在硬盘上缓冲的)。对于此问题,最简单的解决办法是使用别人开发的组件,如:

例如,若想将上传文件的上限提高至20MB,我们只需要这样修改:

<system.web>

<httpRuntime executionTimeout="240" maxRequestLength="20480" />

</system.web>

  • FileUploader.NET (MediaChase公司,$310以上)
  • RadUpload (Telerik公司,$249)
  • NeatUpload (免费,遵守LGPL协议)

但有时候,我们并不想使用组件,那还有一个解决办法,在服务器端截获上传事件,并把数据分块上传。代码如下:

using System;

using System.Collections;

using System.Collections.Specialized;

using System.Globalization;

using System.IO;

using System.Text;

using System.Web;

using System.Reflection;

//实现IHttpModule接口

public class HttpUploadModule : IHttpModule

{

///<summary>

///默认无参构造函数

///</summary>

public HttpUploadModule()

{

}

///<summary>

///初始化,在此添加事件的响应

///</summary>

///<param name="application">Web应用程序对像</param>

public void Init(HttpApplication application)

{

//订阅响应HTTP请求事件

application.BeginRequest += new EventHandler(this.Application_BeginRequest);

}

///<summary>

///释放使用的资源

///</summary>

public void Dispose()

{

//

//TODO:如果需要手工为类做一些清除工作,在此添加代码

//

}

///<summary>

///开始一个HTTP请求事件的处理

///</summary>

///<param name="sender"></param>

///<param name="e"></param>

private void Application_BeginRequest(Object sender, EventArgs e)

{

HttpApplication app = sender as HttpApplication; //获得Web应用程序对像

HttpWorkerRequest request = GetWorkerRequest(app.Context); //获得Http请求对像

Encoding encoding = app.Context.Request.ContentEncoding; //得到请求文本的编码类型

int bytesRead = 0; //已读数据大小

int read; //当前读取的块的大小

int count = 8192; //分块大小

byte[] buffer; //保存所有上传的数据

if (request != null) //如果请求不为空

{

//返回 HTTP 请求正文已被读取的部分。

byte[] tempBuff = request.GetPreloadedEntityBody();

//如果是附件上传

if (tempBuff != null && IsUploadRequest(app.Request))

{

//获取上传大小

long length = long.Parse(request.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentLength));

buffer = new byte[length]; //定义相应大小的缓存区

count = tempBuff.Length; //分块大小

//将已上传数据复制过去

Buffer.BlockCopy(tempBuff, //源数据

0, //从0开始读

buffer, //目标容器

bytesRead, //指定存储的开始位置

count); //要复制的字节数。

//开始记录已上传大小

bytesRead = tempBuff.Length;

//循环分块读取,直到所有数据读取结束

while (request.IsClientConnected() && !request.IsEntireEntityBodyIsPreloaded() && bytesRead < length)

{

//如果最后一块大小小于分块大小,则重新分块

if (bytesRead + count > length)

{

count = (int)(length - bytesRead);

tempBuff = new byte[count];

}

//分块读取

read = request.ReadEntityBody(tempBuff, count);

//复制已读数据块

Buffer.BlockCopy(tempBuff, 0, buffer, bytesRead, read);

//记录已上传大小

bytesRead += read;

}

if (request.IsClientConnected() && !request.IsEntireEntityBodyIsPreloaded())

{

//传入已上传完的数据

InjectTextParts(request, buffer);

}

}

}

}

///<summary>

///获得Http请求对像

///</summary>

///<param name="context">Http请求内容</param>

///<returns>Http请求对像</returns>

HttpWorkerRequest GetWorkerRequest(HttpContext context)

{

IServiceProvider provider = (IServiceProvider)HttpContext.Current;

return (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));

}

///<summary>

///传入已上传完的数据

///</summary>

///<param name="request">Http请求对像</param>

///<param name="textParts"></param>

void InjectTextParts(HttpWorkerRequest request, byte[] textParts)

{

BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;

Type type = request.GetType(); //获得请求的类型

while ((type != null) && (type.FullName != "System.Web.Hosting.ISAPIWorkerRequest"))

{

type = type.BaseType;

}

if (type != null)

{

type.GetField("_contentAvailLength", bindingFlags).SetValue(request, textParts.Length);

type.GetField("_contentTotalLength", bindingFlags).SetValue(request, textParts.Length);

type.GetField("_preloadedContent", bindingFlags).SetValue(request, textParts);

type.GetField("_preloadedContentRead", bindingFlags).SetValue(request, true);

}

}

/// <summary>

/// 比较两个字符串指定位置的指定长度是否相等

/// 忽略大小写及区域不同

/// </summary>

/// <param name="s1">要比较的第一个字符串</param>

/// <param name="s2">要比较的第二个字符串</param>

/// <returns>

/// 小于零 str1 中的子字符串小于 str2 中的子字符串。

/// 等于零 子字符串相等,或者 length 为零。

/// 大于零 str1 中的子字符串大于 str2 中的子字符串。

/// </returns>

private static bool StringStartsWithAnotherIgnoreCase(string str1, string str2)

{

return (string.Compare(str1, 0, str2, 0, str2.Length,true, CultureInfo.InvariantCulture) == 0);

}

///<summary>

///是否为附件上传

///判断的根据是ContentType中有无multipart/form-data

///</summary>

///<param name="request"></param>

///<returns></returns>

bool IsUploadRequest(HttpRequest request)

{

return StringStartsWithAnotherIgnoreCase(request.ContentType, "multipart/form-data");

}

}

还有需要在WEB.Config中配置一下:

<httpModules>

<add name="upFile" type="HttpUploadModule"/>

</httpModules>

这样我们的代码才会起作用。

继续阅读