AJAX 文件上传






2.04/5 (20投票s)
本文介绍了如何使用 AJAX 创建文件上传表单。
引言
通过 HTTP 上传文件对于网站来说一直是个大问题。客户端和服务器端都有一些限制。但是随着互联网信道带宽的增长,一个主要问题是文件大小。有时,由于请求长度限制,无法将 500 MB 的文件发送到 Web 服务器。一种解决方法是增加 Web 服务器上的最大请求长度,但这可能会导致服务器在超过内存限制时重新启动。例如:IIS APS.NET Web 服务器。如果我们将 maxRequestLength
增加到 500 MB,则 memoryLimit
默认值为 60%;这意味着当进程使用的物理内存超过 60% 时,它将被回收。如果我们的系统中有 1 GB 的物理内存,并且几个用户同时上传 400 MB 的文件,则 Web 服务器很有可能会重新启动,因为服务器没有时间从 Request
对象中释放内存。
另一个大问题是文件上传中断时继续,例如由于某些原因导致进程中断。通常,用户需要再次上传整个文件。
在此示例中,我将描述如何使用 AJAX 和 Web 服务技术实现文件上传方法。当然,此方法有其自身的限制,但是对于 intranet 解决方案和互联网网站的管理区域来说,它非常有用。
工作原理
主要思想很简单。我们应该部分读取文件,并将这些部分发送到 Web 服务器。
客户端
//Receive intial file information and init upload
function getFileParams()
{
//Convert file path to appropriate format
this.filePath =
document.getElementById("file").value.replace(
/\\/g, "\\\\");
fso = new ActiveXObject( 'Scripting.FileSystemObject' );
if ( !fso.FileExists(this.filePath) )
{
alert("Can't open file.");
return;
}
f = fso.GetFile( this.filePath );
this.fileSize = f.size;
this.fileName = f.Name;
InitStatusForm();
InitUpload();
}
在客户端分配文件并获取文件大小。我使用 Scripting.FileSystemObject
ActiveX 对象来获取文件大小,因为此对象不会将完整文件加载到内存中。然后,使用 InitStatusForm()
和 InitUpload
函数初始化表单布局和上传过程。
function InitUpload()
{
document.getElementById("uploadConsole").style.display = "none";
document.getElementById("statusConsole").style.display = "block";
xmlhttp = new ActiveXObject( "Microsoft.XMLHTTP" );
xmlhttp.onreadystatechange = HandleStateChange;
var parameters = "fileSize=" + encodeURI(this.fileSize) +
"&fileName=" + encodeURI(this.fileName)+
"&overwriteFile=" +
encodeURI(document.getElementById("overwriteFile").checked);
xmlhttp.open("POST",
"https:///AJAXUpload/Upload.asmx/InitUpload", true);
xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xmlhttp.setRequestHeader("Content-length", parameters.length);
xmlhttp.setRequestHeader("Connection", "close");
xmlhttp.send(parameters);
}
初始化上传:创建 XmlHttp
对象,并将文件大小、文件名和覆盖标志等初始信息发送到 Web 服务。
//XMLHTTPRequest change state callback function
function HandleStateChange() {
switch (xmlhttp.readyState) {
case 4:
response = xmlhttp.responseXML.documentElement;
id = response.getElementsByTagName('ID')[0].firstChild.data;
offset = esponse.getElementsByTagName('OffSet')[0].firstChild.data;
bufferLength =
response.getElementsByTagName('BufferLength')[0].firstChild.data;
percentage = (offset/this.fileSize)*100;
if (offset<this.fileSize && !this.cancelUpload)
{
UpdateStatusConsole(percentage, "Uploading");
SendFilePart(offset, bufferLength);
}
else
{
SetButtonCloseState(false);
if (this.cancelUpload)
UpdateStatusConsole(percentage, "Canceled");
else
UpdateStatusConsole(percentage, "Complete");
}
break;
}
}
来自服务器端的异步请求由 HandledStateChange()
回调函数处理。从服务器解析这些参数
id
- 响应-请求标识符offset
- 读取文件部分的起始位置bufferLength
- 要读取的文件块大小
如果请求,起始位置不应超过文件大小,并且上传不应被用户取消,我们将文件部分发送到。
//Read part of file and send it to webservice
function SendFilePart(offset, length)
{
// create SOAP XML document
var xmlSOAP = new ActiveXObject("MSXML2.DOMDocument");
xmlSOAP.loadXML('<?xml version="1.0" encoding="utf-8"?>'+
'<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '+
'xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> '+
'<soap:Body>'+
'<UploadData xmlns="http://tempuri.org/" >'+
'<fileName>'+this.fileName+'</fileName>'+
'<fileSize>'+this.fileSize+'</fileSize>'+
'<file></file>'+
'</UploadData>'+
'</soap:Body>'+
'</soap:Envelope>');
// create a new node and set binary content
var fileNode = xmlSOAP.selectSingleNode("//file");
fileNode.dataType = "bin.base64";
// open stream object and read source file
if (adoStream.State != 1 )
{
adoStream.Type = 1; // 1=adTypeBinary
adoStream.Open();
adoStream.LoadFromFile(this.filePath);
}
adoStream.Position = offset;
// store file content into XML node
fileNode.nodeTypedValue = adoStream.Read(length);
//adoStream.Read(-1); // -1=adReadAll
if (adoStream.EOS)
{
//Close Stream
adoStream.Close();
}
// send XML document to Web server
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.onreadystatechange = HandleStateChange;
xmlhttp.open("POST",
"https:///AJAXUpload/Upload.asmx", true);
xmlhttp.setRequestHeader("SOAPAction",
"http://tempuri.org/UploadData");
xmlhttp.setRequestHeader("Content-Type",
"text/xml; charset=utf-8");
xmlhttp.send(xmlSOAP);
}
在此函数中,我们创建 XmlSoap
,使用 ADODB.Stream
ActiveX 对象读取文件部分,然后将其与文件名和文件大小一起发送到 Web 服务器。此操作的服务器响应将由同一 HandledStateChange()
回调函数处理。
服务器端
[WebMethod]
public XmlDocument InitUpload(int fileSize, string fileName, bool overwriteFile )
{
long offset = 0;
string filePath = GetFilePath(fileName);
if (File.Exists(filePath))
{
if (overwriteFile)
{
File.Delete(filePath);
}
else
{
using (FileStream fs = File.Open(filePath, FileMode.Append))
{
offset = fs.Length;
}
}
}
return GetXmlDocument(Guid.NewGuid(), string.Empty, offset,
(InitialBufferLength+offset)>fileSize?
(int)(fileSize-offset):InitialBufferLength);
}
初始化上传服务器端函数。如果已存在同名文件并且覆盖标志为 false
,则现有文件将附加到该文件;否则,该文件将被删除。然后,使用 GetXmlDocument
函数构造响应。
[WebMethod]
public XmlDocument UploadData(string fileName, int fileSize, byte[] file)
{
if (fileName == null || fileName == string.Empty || file == null)
return GetXmlDocument(Guid.NewGuid(),
"Incorrect UploadData Request", 0, 0);
string filePath = GetFilePath(fileName);
long offset=0;
using (FileStream fs = File.Open(filePath, FileMode.Append))
{
fs.Write(file, 0, file.Length);
offset = fs.Length;
}
return GetXmlDocument(Guid.NewGuid(), string.Empty, offset,
(InitialBufferLength+offset)>fileSize?
(int)(fileSize-offset):InitialBufferLength);
}
此方法使用文件部分数据处理来自客户端的请求。将文件部分附加到已上传的部分,然后请求下一个部分。
安装并运行
要运行该项目,您应该进行一些操作
- 为您的 IIS 用户提供对上传文件夹的读/写权限。
- 在您的 IE 浏览器中启用 ActiveX 对象。(将网站添加到受信任的网站列表。)
备注
我已描述了解决方案的核心;可以在包含的项目中找到所有布局功能,如上传面板和进度条。
请不要在您的项目按原样使用此解决方案。这只是一个 AJAX 上传表单示例。使用流、文件和 ActiveX 对象时,我们应该处理所有错误情况。
链接
- http://codeproject.com/Ajax/JavaScriptSOAPClient.asp - 一篇优秀的 AJAX 文章。
- http://www.15seconds.com/issue/010522.htm - 文件上传技术。