Click here to Skip to main content
Click here to Skip to main content

AJAX file upload

, 27 Feb 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dzianis@gmail.com
Web Developer
Belarus Belarus
Graduated in Belurassian State University on chair of Artificial Intelligence.
Take a part in development video alarm system for PC with live AI analyzing multiple video streams.
 
3 years of ASP.NET experience in development multi-thier applications, integrated applications, enterprise level solutions.
 
Almost 1 year experience in ASP.NET 2.0 starting from BETA 2.
 
Now working in
http://pro-softsolutions.com

Comments and Discussions

 
GeneralNot working Pinmembersuresh_kumar_s9-Nov-09 2:27 
GeneralRe: Not working Pinmembermaverick91120-Jan-10 4:29 
GeneralUpload file Pinmemberbillgate_qn27-May-09 1:39 
GeneralSolved timeout problem Pinmembermaverick91124-Feb-09 6:47 
QuestionError While upload File Pinmemberrahul_check24-Sep-08 2:33 
GeneralADODB.Stream loads whole file into memory Pinmemberwzhao200021-Nov-07 7:14 
QuestionAJAX Fileupload Automation Error Pinmemberjpthomas27-Oct-07 0:45 
GeneralADODB.Stream PinmemberDizzyinc27-Sep-07 22:57 
GeneralRe: ADODB.Stream Pinmembermosejas24-Oct-07 11:57 
Questiona question from Krishna PinadminSean Ewington11-Jun-07 8:56 
GeneralHi compatriot, I wonder if it's possible to run it on windows with sp2 installed. [modified] Pinmemberandrew.golik21-Mar-07 1:54 
GeneralRe: Hi compatriot, I wonder if it's possible to run it on windows with sp2 installed. Pinmembermagicxman6-Sep-07 0:47 
GeneralRe: Hi compatriot, I wonder if it's possible to run it on windows with sp2 installed. Pinmembermbaocha23-Apr-09 5:34 
GeneralWorks Great After Tweaking Pinmemberkodhedz15-Mar-07 3:03 
GeneralRe: Works Great After Tweaking Pinmemberandrew.golik21-Mar-07 2:09 
QuestionadoStream.state is null or not an object PinmemberCan Gunaydin22-Jan-07 6:54 
GeneralFailed to create fso, new ActiveXObject("Scripting.FileSystemObject"); Pinmemberanhboston13-Dec-06 20:03 
GeneralRe: Failed to create fso, new ActiveXObject("Scripting.FileSystemObject"); Pinmembertejas_chonkar24-Jan-07 7:14 
Questionit didn/t work with me PinmemberSyri14-Nov-06 21:31 
AnswerRe: it didn/t work with me PinmemberDzianis@gmail.com15-Nov-06 23:27 
GeneralRe: it didn/t work with me PinmemberJohnny McSpankwit13-Dec-06 2:14 
AnswerRe: it didn/t work with me Pinmembermagicxman21-Aug-07 21:04 
GeneralGmail attachment Pinmembersmil14-Jun-06 3:01 
GeneralNot Woking Over Subnet Pinmembergrbala2-May-06 10:16 
GeneralRe: Not Woking Over Subnet Pinmemberahmadzaro2-May-06 21:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150123.1 | Last Updated 27 Feb 2006
Article Copyright 2006 by Dzianis@gmail.com
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid