Click here to Skip to main content
15,896,726 members
Articles / Web Development / ASP.NET

AJAX file upload

Rate me:
Please Sign up or sign in to vote.
2.04/5 (27 votes)
27 Feb 2006CPOL3 min read 454.4K   1.1K   97   44
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

JavaScript
//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.

JavaScript
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.

JavaScript
//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.

JavaScript
//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

C#
[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.

C#
[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)


Written By
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

 
GeneralRe: Not Woking Over Subnet [modified] Pin
ahmadzaro19-Jun-06 1:39
ahmadzaro19-Jun-06 1:39 
GeneralAutomation server can't create object Pin
Youssef Saad29-Apr-06 11:04
Youssef Saad29-Apr-06 11:04 
GeneralRe: Automation server can't create object Pin
fribbs13-May-06 18:32
fribbs13-May-06 18:32 
QuestionMicrosoft JScript runtime error: 'response' is null or not an object Pin
baimos30-Mar-06 20:37
baimos30-Mar-06 20:37 
AnswerRe: Microsoft JScript runtime error: 'response' is null or not an object Pin
baimos31-Mar-06 0:36
baimos31-Mar-06 0:36 
AnswerRe: Microsoft JScript runtime error: 'response' is null or not an object Pin
hartmuth.lohmiller17-May-06 22:43
hartmuth.lohmiller17-May-06 22:43 
GeneralHELP response from server is a NULL Object Pin
doctia15-Mar-06 11:11
doctia15-Mar-06 11:11 
GeneralRe: HELP response from server is a NULL Object Pin
Ehsanul Haque12-Sep-06 23:52
Ehsanul Haque12-Sep-06 23:52 
yes, i have face the same problem and overcome the problem by changing the 'http://localhost/AJAXUpload/Upload.asmx' to 'Upload.asmx' in the parameter of xmlhttp.open. you can also make a virtual directory and put the link there. may be it will be work.

Smile | :)
GeneralHElp Please Pin
doctia12-Mar-06 8:35
doctia12-Mar-06 8:35 
GeneralIf You Have Problem about creating ADODB.Stream Pin
tychi28-Feb-06 19:52
tychi28-Feb-06 19:52 
GeneralRe: If You Have Problem about creating ADODB.Stream Pin
thongtom19-Sep-06 16:50
thongtom19-Sep-06 16:50 
GeneralRe: If You Have Problem about creating ADODB.Stream Pin
Andrew Golik21-Mar-07 7:51
Andrew Golik21-Mar-07 7:51 
GeneralRe: If You Have Problem about creating ADODB.Stream Pin
Andrew Golik21-Mar-07 2:25
Andrew Golik21-Mar-07 2:25 
GeneralRe: If You Have Problem about creating ADODB.Stream Pin
octopuscode10-Jul-07 17:57
octopuscode10-Jul-07 17:57 
GeneralLimitations Pin
Rama Krishna Vavilala27-Feb-06 1:48
Rama Krishna Vavilala27-Feb-06 1:48 
GeneralRe: Limitations Pin
Dzianis@gmail.com27-Feb-06 4:26
Dzianis@gmail.com27-Feb-06 4:26 
GeneralRe: Limitations Pin
ChrisAdams23-May-06 10:09
ChrisAdams23-May-06 10:09 
GeneralRe: Limitations Pin
Akhmad Deniar31-Jul-07 16:59
Akhmad Deniar31-Jul-07 16:59 

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

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