在ASP.NET MVC中实现大文件异步上传(2)
2011-04-15 16:51
711 查看
HttpWorkerRequest有VIP访问传入的请求,通常它是由ASP.NET本身支持工作的,但我们绑架了请求,然后欺骗剩下的请求,让它们误以为前面的请求已经全部得到处理,为了做到这一点,我们需要上面例子中未出现的UploadProcessor类,这个类的职责是物理读取来自浏览器的每个数据块,然后将其保存到磁盘上,因为上传的内容被分解成多个部分,UploadProcessor类需要找出内容头,然后拼接成带状数据输出,这一可以在一个上传中同时上传多个文件。
在处理代码的中间位置,你应该注意到了另一个类StaticWorkerRequest,这个类负责欺骗ASP.NET,在点击提交按钮时,它欺骗ASP.NET,让他认为没有文件上传,这是必需的,因为当上传完毕时,如果我们要重定向到所需的页面时,ASP.NET将会检查到在HTTP实体主体中仍然有数据,然后会尝试缓存整个上传,于是我们兜了一圈又回到了原点,为了避免这种情况,我们必须欺骗HttpWorkerRequest,将它注入到HttpContext中,获得请求开始部分的StaticWorkerRequest,它是唯一有用的数据。
使用StaticWorkerRequest建立虚假的声明,现在你可以在ASP.NET MVC中通过直接访问数据流上传大文件,使用这个代码作为开始,你可以很容易地保存过程数据,并使用Ajax调用另一个控制器行为展示其进度,将大文件缓存到一个临时区域,可以实现断点续传,不用再等待ASP.NET进程将整个文件缓存到磁盘上,同样,保存文件时也不用消耗另存为方法那么多的内存了。
internal class UploadProcessor { private byte[] _buffer; private byte[] _boundaryBytes; private byte[] _endHeaderBytes; private byte[] _endFileBytes; private byte[] _lineBreakBytes; private const string _lineBreak = "/r/n"; private readonly Regex _filename = new Regex(@"Content-Disposition:/s*form-data/s*;/s*name/s*=/s*""file""/s*;/s*filename/s*=/s*""(.*)""", RegexOptions.IgnoreCase | RegexOptions.Compiled); private readonly HttpWorkerRequest _workerRequest; public UploadProcessor(HttpWorkerRequest workerRequest) { _workerRequest = workerRequest; } public void StreamToDisk(IServiceProvider provider, Encoding encoding, string rootPath) { var buffer = new byte[8192]; if (!_workerRequest.HasEntityBody()) { return; } var total = _workerRequest.GetTotalEntityBodyLength(); var preloaded = _workerRequest.GetPreloadedEntityBodyLength(); var loaded = preloaded; SetByteMarkers(_workerRequest, encoding); var body = _workerRequest.GetPreloadedEntityBody(); if (body == null) // IE normally does not preload { body = new byte[8192]; preloaded = _workerRequest.ReadEntityBody(body, body.Length); loaded = preloaded; } var text = encoding.GetString(body); var fileName = _filename.Matches(text)[0].Groups[1].Value; fileName = Path.GetFileName(fileName); // IE captures full user path; chop it var path = Path.Combine(rootPath, fileName); var files = new List {fileName}; var stream = new FileStream(path, FileMode.Create); if (preloaded > 0) { stream = ProcessHeaders(body, stream, encoding, preloaded, files, rootPath); } // Used to force further processing (i.e. redirects) to avoid buffering the files again var workerRequest = new StaticWorkerRequest(_workerRequest, body); var field = HttpContext.Current.Request.GetType().GetField("_wr", BindingFlags.NonPublic | BindingFlags.Instance); field.SetValue(HttpContext.Current.Request, workerRequest); if (!_workerRequest.IsEntireEntityBodyIsPreloaded()) { var received = preloaded; while (total - received >= loaded && _workerRequest.IsClientConnected()) { loaded = _workerRequest.ReadEntityBody(buffer, buffer.Length); stream = ProcessHeaders(buffer, stream, encoding, loaded, files, rootPath); received += loaded; } var remaining = total - received; buffer = new byte[remaining]; loaded = _workerRequest.ReadEntityBody(buffer, remaining); stream = ProcessHeaders(buffer, stream, encoding, loaded, files, rootPath); } stream.Flush(); stream.Close(); stream.Dispose(); } private void SetByteMarkers(HttpWorkerRequest workerRequest, Encoding encoding) { var contentType = workerRequest.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentType); var bufferIndex = contentType.IndexOf("boundary=") + "boundary=".Length; var boundary = String.Concat("--", contentType.Substring(bufferIndex)); _boundaryBytes = encoding.GetBytes(string.Concat(boundary, _lineBreak)); _endHeaderBytes = encoding.GetBytes(string.Concat(_lineBreak, _lineBreak)); _endFileBytes = encoding.GetBytes(string.Concat(_lineBreak, boundary, "--", _lineBreak)); _lineBreakBytes = encoding.GetBytes(string.Concat(_lineBreak + boundary + _lineBreak)); } private FileStream ProcessHeaders(byte[] buffer, FileStream stream, Encoding encoding, int count, ICollection files, string rootPath) { buffer = AppendBuffer(buffer, count); var startIndex = IndexOf(buffer, _boundaryBytes, 0); if (startIndex != -1) { var endFileIndex = IndexOf(buffer, _endFileBytes, 0); if (endFileIndex != -1) { var precedingBreakIndex = IndexOf(buffer, _lineBreakBytes, 0); if (precedingBreakIndex > -1) { startIndex = precedingBreakIndex; } endFileIndex += _endFileBytes.Length; var modified = SkipInput(buffer, startIndex, endFileIndex, ref count); stream.Write(modified, 0, count); } else { var endHeaderIndex = IndexOf(buffer, _endHeaderBytes, 0); if (endHeaderIndex != -1) { endHeaderIndex += _endHeaderBytes.Length; var text = encoding.GetString(buffer); var match = _filename.Match(text); var fileName = match != null ? match.Groups[1].Value : null; fileName = Path.GetFileName(fileName); // IE captures full user path; chop it if (!string.IsNullOrEmpty(fileName) && !files.Contains(fileName)) { files.Add(fileName); var filePath = Path.Combine(rootPath, fileName); stream = ProcessNextFile(stream, buffer, count, startIndex, endHeaderIndex, filePath); } else { var modified = SkipInput(buffer, startIndex, endHeaderIndex, ref count); stream.Write(modified, 0, count); } } else { _buffer = buffer; } } } else { stream.Write(buffer, 0, count); } return stream; } |
private static FileStream ProcessNextFile(FileStream stream, byte[] buffer, int count, int startIndex, int endIndex, string filePath) { var fullCount = count; var endOfFile = SkipInput(buffer, startIndex, count, ref count); stream.Write(endOfFile, 0, count); stream.Flush(); stream.Close(); stream.Dispose(); stream = new FileStream(filePath, FileMode.Create); var startOfFile = SkipInput(buffer, 0, endIndex, ref fullCount); stream.Write(startOfFile, 0, fullCount); return stream; } private static int IndexOf(byte[] array, IList value, int startIndex) { var index = 0; var start = Array.IndexOf(array, value[0], startIndex); if (start == -1) { return -1; } while ((start + index) < array.Length) { if (array[start + index] == value[index]) { index++; if (index == value.Count) { return start; } } else { start = Array.IndexOf(array, value[0], start + index); if (start != -1) { index = 0; } else { return -1; } } } return -1; } private static byte[] SkipInput(byte[] input, int startIndex, int endIndex, ref int count) { var range = endIndex - startIndex; var size = count - range; var modified = new byte[size]; var modifiedCount = 0; for (var i = 0; i < input.Length; i++) { if (i >= startIndex && i < endIndex) { continue; } if (modifiedCount >= size) { break; } modified[modifiedCount] = input[i]; modifiedCount++; } input = modified; count = modified.Length; return input; } private byte[] AppendBuffer(byte[] buffer, int count) { var input = new byte[_buffer == null ? buffer.Length : _buffer.Length + count]; if (_buffer != null) { Buffer.BlockCopy(_buffer, 0, input, 0, _buffer.Length); } Buffer.BlockCopy(buffer, 0, input, _buffer == null ? 0 : _buffer.Length, count); _buffer = null; return input; } } |
internal class StaticWorkerRequest : HttpWorkerRequest { readonly HttpWorkerRequest _request; private readonly byte[] _buffer; public StaticWorkerRequest(HttpWorkerRequest request, byte[] buffer) { _request = request; _buffer = buffer; } public override int ReadEntityBody(byte[] buffer, int size) { return 0; } public override int ReadEntityBody(byte[] buffer, int offset, int size) { return 0; } public override byte[] GetPreloadedEntityBody() { return _buffer; } public override int GetPreloadedEntityBody(byte[] buffer, int offset) { Buffer.BlockCopy(_buffer, 0, buffer, offset, _buffer.Length); return _buffer.Length; } public override int GetPreloadedEntityBodyLength() { return _buffer.Length; } public override int GetTotalEntityBodyLength() { return _buffer.Length; } public override string GetKnownRequestHeader(int index) { return index == HeaderContentLength ? "0" : _request.GetKnownRequestHeader(index); } // All other methods elided, they're just passthrough } |
相关文章推荐
- ASP.NET MVC 使用Uploadify实现多文件异步无刷新上传
- (转)在ASP.NET MVC中实现大文件异步上传
- 在ASP.NET MVC中实现大文件异步上传
- 在ASP.NET MVC中实现大文件异步上传(1)
- 在ASP.NET MVC中实现大文件异步上传(2)
- 在ASP.NET MVC中实现大文件异步上传(2)
- ASP.NET MVC 网站开发总结(二)——一个或多个文件的异步或同步上传
- asp.net下实现支持文件分块多点异步上传的 Web Services
- asp.net mvc实现上传文件
- [代码示例]用Fine Uploader+ASP.NET MVC实现ajax文件上传
- asp.net下实现支持文件分块多点异步上传的 Web Services
- ASP.NET MVC 文件异步上传问题处理
- c#、asp.net 基于ajaxfileupload.js 实现文件异步上传
- Asp.net实现MVC处理文件的上传下载功能实例教程
- 用Html5与Asp.net MVC上传多个文件的实现代码
- ASP.NET MVC实现多文件上传
- sql server 关于表中只增标识问题 C# 实现自动化打开和关闭可执行文件(或 关闭停止与系统交互的可执行文件) ajaxfileupload插件上传图片功能,用MVC和aspx做后台各写了一个案例 将小写阿拉伯数字转换成大写的汉字, C# WinForm 中英文实现, 国际化实现的简单方法 ASP.NET Core 2 学习笔记(六)ASP.NET Core 2 学习笔记(三)
- 为ASP.NET MVC开发一些常用插件(二)—— 实现无刷新文件上传
- Asp.net实现MVC处理文件的上传下载功能实例教程
- 关于jQuery在Asp.Net Mvc 框架下Ajax文件上传的实现