Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

AJAX file upload

0.00/5 (No votes)
27 Feb 2006 2  
This article describes how to create a file upload form using AJAX.

Introduction

File uploading through HTTP is always a big problem for websites. There are some restrictions from the client and server sides. But with growing internet channel bandwidths, one of the major problems is file size. Sometimes it's impossible to send a 500 MB file to a web server due to request length limits. One of the workarounds is to increase the maximal request length on the web server, but it may cause the server to restart when the memory limit is exceeded. For example: an IIS APS.NET web server. If we increase maxRequestLength to 500 MB, the memoryLimit default value is 60%; it means that the process will be recycled when it uses more than 60% of the physical memory. If we have 1 GB of physical memory in the system and a couple of users simultaneously upload 400 MB files, there is a high chance the web server will be restarted, because the server wouldn't have time to release memory from the Request objects.

Another big issue is file upload continuing, when a process is interrupted by some reason. Normally, the user needs to upload the whole file once again.

In this example, I'll describe how to implement a file uploading method using AJAX and Web Service technologies. Of course, this method has its own restrictions, but it would be quite useful for intranet solutions and administrative areas in internet websites.

How it works

The main idea is quite simple. We should read a file partially and send these parts to the web server.

Client-side

//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();
}

Allocate the file on the client and get the file size. I use the Scripting.FileSystemObject ActiveX object to get the file size because this object will not load the full file in memory. Then, init the form layout and upload process using the InitStatusForm() and InitUpload functions.

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", 
      "http://localhost/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);
}

Init upload: Create the XmlHttp object, and the send to the Web Service initial information such as file size, file name, and overwrite flag.

//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;
    } 
}

Asynchronous requests from the server-side is handled by the HandledStateChange() callback function. Parse these parameters from the server:

  • id - response-request identifier
  • offset - start position to read the file part
  • bufferLength - file block size to read

If requested, the start position should not exceed file size and upload should not be canceled by the user we send the file part to.

//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", 
                 "http://localhost/AJAXUpload/Upload.asmx", true);
    xmlhttp.setRequestHeader("SOAPAction", 
                             "http://tempuri.org/UploadData");
    xmlhttp.setRequestHeader("Content-Type", 
                             "text/xml; charset=utf-8");
    xmlhttp.send(xmlSOAP);
}

In this function, we create XmlSoap, read the file part using the ADODB.Stream ActiveX object, and send it to the web server with the file name and the file size. The sServer response from this operation would be handled by the same HandledStateChange() callback function.

Server-side

[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);
}

Init the upload server-side function. If a file with the same name already exists and the overwrite flag is false, the existing file will be appended to; otherwise, the file will be deleted. Then, construct the response using the GetXmlDocument function.

[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);
}

This method handles the request from the client with the file part data. Append the file part to the uploaded part and request the next part.

Install and run

To run the project, you should do a couple manipulations:

  1. Give read/write permissions to your IIS user for the upload folder.
  2. Enable ActiveX objects in your IE browser. (Add the website to the trusted websites list.)

Remarks

I've described the solution core; all layout functionality like upload panel and progress bar can be found in the included project.

Please don't use this solution as is in your projects. It's just an AJAX upload form example. When working with streams, files, and ActiveX objects, we should handle all error cases.

Links

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here