Click here to Skip to main content
15,879,535 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 454K   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

 
GeneralNot working Pin
suresh_kumar_s9-Nov-09 1:27
suresh_kumar_s9-Nov-09 1:27 
GeneralRe: Not working Pin
maverick91120-Jan-10 3:29
maverick91120-Jan-10 3:29 
GeneralUpload file Pin
taoquandecor27-May-09 0:39
taoquandecor27-May-09 0:39 
GeneralSolved timeout problem Pin
maverick91124-Feb-09 5:47
maverick91124-Feb-09 5:47 
QuestionError While upload File Pin
rahul_check24-Sep-08 1:33
rahul_check24-Sep-08 1:33 
GeneralADODB.Stream loads whole file into memory Pin
wzhao200021-Nov-07 6:14
wzhao200021-Nov-07 6:14 
QuestionAJAX Fileupload Automation Error Pin
jpthomas26-Oct-07 23:45
jpthomas26-Oct-07 23:45 
GeneralADODB.Stream Pin
Dizzyinc27-Sep-07 21:57
Dizzyinc27-Sep-07 21:57 
GeneralRe: ADODB.Stream Pin
mosejas24-Oct-07 10:57
mosejas24-Oct-07 10:57 
Questiona question from Krishna Pin
Sean Ewington11-Jun-07 7:56
staffSean Ewington11-Jun-07 7:56 
GeneralHi compatriot, I wonder if it's possible to run it on windows with sp2 installed. [modified] Pin
Andrew Golik21-Mar-07 0:54
Andrew Golik21-Mar-07 0:54 
GeneralRe: Hi compatriot, I wonder if it's possible to run it on windows with sp2 installed. Pin
magicxman5-Sep-07 23:47
magicxman5-Sep-07 23:47 
GeneralRe: Hi compatriot, I wonder if it's possible to run it on windows with sp2 installed. Pin
mbaocha23-Apr-09 4:34
mbaocha23-Apr-09 4:34 
GeneralWorks Great After Tweaking Pin
kodhedz15-Mar-07 2:03
kodhedz15-Mar-07 2:03 
GeneralRe: Works Great After Tweaking Pin
Andrew Golik21-Mar-07 1:09
Andrew Golik21-Mar-07 1:09 
QuestionadoStream.state is null or not an object Pin
Can Gunaydin22-Jan-07 5:54
Can Gunaydin22-Jan-07 5:54 
GeneralFailed to create fso, new ActiveXObject("Scripting.FileSystemObject"); Pin
anhboston13-Dec-06 19:03
anhboston13-Dec-06 19:03 
GeneralRe: Failed to create fso, new ActiveXObject("Scripting.FileSystemObject"); Pin
tejas_chonkar24-Jan-07 6:14
tejas_chonkar24-Jan-07 6:14 
Questionit didn/t work with me Pin
Syri14-Nov-06 20:31
Syri14-Nov-06 20:31 
AnswerRe: it didn/t work with me Pin
Dzianis@gmail.com15-Nov-06 22:27
Dzianis@gmail.com15-Nov-06 22:27 
GeneralRe: it didn/t work with me Pin
Johnny McSpankwit13-Dec-06 1:14
Johnny McSpankwit13-Dec-06 1:14 
AnswerRe: it didn/t work with me Pin
magicxman21-Aug-07 20:04
magicxman21-Aug-07 20:04 
GeneralGmail attachment Pin
smil14-Jun-06 2:01
smil14-Jun-06 2:01 
GeneralNot Woking Over Subnet Pin
grbala2-May-06 9:16
grbala2-May-06 9:16 
Hi
Its a great example, I really like the approach. I tried to customize the post url from localhost to other name and IP's. Its works great. But there is a small problem, It does not work over subnet, I tried IP and Name's. Is there any specific reason

Bala
GeneralRe: Not Woking Over Subnet Pin
ahmadzaro2-May-06 20:46
ahmadzaro2-May-06 20:46 

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.