65.9K
CodeProject 正在变化。 阅读更多。
Home

AJAX 文件上传

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.04/5 (20投票s)

2006年2月27日

CPOL

3分钟阅读

viewsIcon

458442

downloadIcon

1081

本文介绍了如何使用 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);
}

此方法使用文件部分数据处理来自客户端的请求。将文件部分附加到已上传的部分,然后请求下一个部分。

安装并运行

要运行该项目,您应该进行一些操作

  1. 为您的 IIS 用户提供对上传文件夹的读/写权限。
  2. 在您的 IE 浏览器中启用 ActiveX 对象。(将网站添加到受信任的网站列表。)

备注

我已描述了解决方案的核心;可以在包含的项目中找到所有布局功能,如上传面板和进度条。

请不要在您的项目按原样使用此解决方案。这只是一个 AJAX 上传表单示例。使用流、文件和 ActiveX 对象时,我们应该处理所有错误情况。

链接

© . All rights reserved.