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

Upload File Using Ajax And HTML5 in MVC

0.00/5 (No votes)
23 Dec 2016 1  
Simple native file Uploader with a progress bar without using flash player or any external file Upload plugins

Just download the project demo from here and enjoy. :)

Introduction

I was working in MVC application project and I wanted to upload file or multiple files with a progress bar info without using flash player or any upload file plugins. So in this post, we will explain a simple way to:

  • Upload single or multiple files with a progress bar info
  • Upload multiple files via Selecting files or Drag and Drop files

Background

HTML5 provides a standard way to interact with local files using File API specification, it gives us an opportunity to access files data or use client side validation to verify the size / type of uploaded files.

This specification includes several interfaces to access files:

  • A File interface which includes a read only info about file such as name, type and size in bytes.
  • A FileList interface which represents a list of individually selected files (list of file object) the user interface for selection can be invoked via <input type="file"> or drag & drop.

XMLHTTPRequest2 is one of the heroes of HTML5 universe, XHR2 are the same XMLHttpRequest but with many changes. It includes the following new features:

  • Uploading/downloading binary data.
  • Progress events during uploading. This event includes the following information:
    • Total: integer value that specifies total bytes being transferred.
    • Loaded: integer value that specifies bytes uploaded.
    • lengthComputable: Boolean value that checks if total Size of uploaded data is known.
  • Cross-origin requests

These features allow AJAX to work confidently with new technologies of HTML5 like the File API so by putting all these together, uploading file will be very easy and not a big deal without using flash player, external plugins or use normal html <form> and depend on server side to enable showing upload progress.

In this post, we’ll write a small application that is able to:

  • Upload single file and provide upload progress info?
  • Create a thumbnail preview of images before sending to server?
  • Upload multiple files via selecting multiple files or drag & drop files?

And we will check Browsers support for XHR2, File API, FormData and drag & drop.

Using the Code

How to Upload Single File and Providing Upload Progress Info?

All we have to do is create a simple View like below:

  • Form consists of input file element and button to submit the form.
  • Section for file info and a progress bar I used bootstrap progress bar:
<div id="FormContent">
           <form id="FormUpload"
           enctype="multipart/form-data" method="post">
               <span class="btn btn-success fileinput-button">
                   <i class="glyphicon glyphicon-plus"></i>
                   <span>Add files...</span>
                   <input type="file"
                   name="UploadedFile" id="UploadedFile" />
               </span>
               <button class="btn btn-primary start"
               type="button" id="Submit_btn">
                   <i class="glyphicon glyphicon-upload"></i>
                   <span>Start upload</span>
               </button>
                <button class="btn btn-warning cancel"
                type="button" id="Cancel_btn">
                   <i class="glyphicon glyphicon-ban-circle"></i>
                   <span>close</span>
               </button>
           </form>
           <div class="progress CustomProgress">
               <div id="FileProgress"
               class="progress-bar" role="progressbar"
       aria-valuenow="0" aria-valuemin="0"
       aria-valuemax="100" style="width: 0%;">
                   <span></span>
               </div>
           </div>
           <div class="InfoContainer">
               <div id="Imagecontainer"></div>
               <div id="FileName" class="info">
               </div>
               <div id="FileType" class="info">
               </div>
               <div id="FileSize" class="info">
               </div>
           </div>
       </div>

Then, we will add input file element’s onchange event and assign it to JS method called SingleFileSelected like in the below code snippet, so this method will be called every time user chooses/changes a file. In this method, we will select the input file element and access its files object of type FileList and select the first file (files[0]), this file of type file object gives us some read only information about file like file name, file type (mime type). We can use it to restrict some files and File size in bytes. This size we will convert it into MB and KB in our method so we can display them on the browser.

function singleFileSelected(evt) {
    //var selectedFile = evt.target.files can use this  or select input file element 
    //and access it's files object
    var selectedFile = ($("#UploadedFile"))[0].files[0];//FileControl.files[0];
    if (selectedFile) {
        var FileSize = 0;
        var imageType = /image.*/;
        if (selectedFile.size > 1048576) {
            FileSize = Math.round(selectedFile.size * 100 / 1048576) / 100 + " MB";
        }
        else if (selectedFile.size > 1024) {
            FileSize = Math.round(selectedFile.size * 100 / 1024) / 100 + " KB";
        }
        else {
            FileSize = selectedFile.size + " Bytes";
        }
        // here we will add the code of thumbnail preview of upload images
       
        $("#FileName").text("Name : " + selectedFile.name);
        $("#FileType").text("type : " + selectedFile.type);
        $("#FileSize").text("Size : " + FileSize);
    }
}

We can also use File reader object to read the uploaded file content into memory, the reader object has some events like onload, onError, four functions for reading data readAsBinaryString(), readAsText(), readAsArrayBuffer() and readAsDataURL() and the result property which represent file's contents. This property is only valid after the read operation is complete, and the format of the data depends on which of the methods was used to initiate the read operation.

We will not explain the File reader in detail, but we will use it in our SingleFileSelected method to preview images as thumbnail. See the below code snippet.

This code filters out images from uploaded files, then creates an object of Filereader and uses the onload event callback to create the image preview. This callback will get called after file reading operation is completed and the result is assigned to reader.result property, then we will call reader.readAsDataURL() that returns data as encoded DataURL.

if (selectedFile.type.match(imageType)) {
           var reader = new FileReader();
           reader.onload = function (e) {

               $("#Imagecontainer").empty();
               var dataURL = reader.result;
               var img = new Image()
               img.src = dataURL;
               img.className = "thumb";
               $("#Imagecontainer").append(img);
           };
           reader.readAsDataURL(selectedFile);
       }

So now, we will be able to load file name, type, size and Image preview like the figure below:

Now, we need to send the uploaded file to the server, so we will add onclick event and assign it to the JS method called uploadFile(). See the below code snippet:

function UploadFile() {
    //we can create form by passing the form to Constructor of formData object
    //or creating it manually using append function 
    //but please note file name should be same like the action Parameter
    //var dataString = new FormData();
    //dataString.append("UploadedFile", selectedFile);

    var form = $('#FormUpload')[0];
    var dataString = new FormData(form);
    $.ajax({
        url: '/Uploader/Upload',  //Server script to process data
        type: 'POST',
        xhr: function () {  // Custom XMLHttpRequest
            var myXhr = $.ajaxSettings.xhr();
            if (myXhr.upload) { // Check if upload property exists
                //myXhr.upload.onprogress = progressHandlingFunction
                myXhr.upload.addEventListener('progress', progressHandlingFunction, 
				false); // For handling the progress of the upload
            }
            return myXhr;
        },
        //Ajax events
        success: successHandler,
        error: errorHandler,
        complete:completeHandler,
        // Form data
        data: dataString,
        //Options to tell jQuery not to process data or worry about content-type.
        cache: false,
        contentType: false,
        processData: false
    });
}

In this method, we will send form using Form data object to serialize such file values we can create formdata manually by instantiating it, then appending fields to it by calling its append() method or retrieving a FormData object from an HTML form like we did in the above function by passing Form element to formdata constructor when creating it and also we can append more information to it. Then, we create JQuery AJAX with the right options:

  • XHR: Create custom XMLHTTPRequest check if upload Property Exists, then add a Progress event and assign it to JS method progressHandlingFunction that will handle progress of upload property .
  • processData: Set to false to tell jQuery not to process the data.
  • contentType: Set to false to tell jQuery not to set contentType.
  • Data: Set it to the formdata object.
    Other options are regular Ajax options and are pretty self-explanatory .

Let’s take a look at progressHandlingFunction. In this method, we check if total Size of uploaded data is known via e.lengthComputable, then we use e.loaded(value of uploaded bytes) and e.total(value of total bytes being transferred) to compute the percentage of uploaded data.

function progressHandlingFunction(e) {
    if (e.lengthComputable) {
        var percentComplete = Math.round(e.loaded * 100 / e.total);
        $("#FileProgress").css("width", 
        percentComplete + '%').attr('aria-valuenow', percentComplete);
        $('#FileProgress span').text(percentComplete + "%");
    }
    else {
        $('#FileProgress span').text('unable to compute');
    }
}

So now, we are able to send data to server and provide a progress. Let's take a look at the server side code in the below snippet which is a very simple action called upload in controller called uploader.

In this action, we received the file in HttpPostedfileBase object. This object contains information about the uploaded file like Filename property, Contenttype property and inputStream property that contains file content with this information we can validate file on server save file.

[HttpPost]

        public JsonResult Upload(HttpPostedFileBase uploadedFile)
        {
            if (uploadedFile != null && uploadedFile.ContentLength > 0)
            {
                byte[] FileByteArray = new byte[uploadedFile.ContentLength];
                uploadedFile.InputStream.Read(FileByteArray, 0, uploadedFile.ContentLength);
                Attachment newAttchment = new Attachment();
                newAttchment.FileName = uploadedFile.FileName;
                newAttchment.FileType = uploadedFile.ContentType;
                newAttchment.FileContent = FileByteArray;
                OperationResult operationResult = attachmentManager.SaveAttachment(newAttchment);
                if (operationResult.Success)
                {
                    string HTMLString = CaptureHelper.RenderViewToString
                    ("_AttachmentItem", newAttchment, this.ControllerContext);
                    return Json(new
                    {
                        statusCode = 200,
                        status = operationResult.Message,
                        NewRow = HTMLString
                    }, JsonRequestBehavior.AllowGet);

                }
                else
                {
                    return Json(new
                    {
                        statusCode = 400,
                        status = operationResult.Message,
                        file = uploadedFile.FileName
                    }, JsonRequestBehavior.AllowGet);

                }
            }
            return Json(new
            {
                statusCode = 400,
                status = "Bad Request! Upload Failed",
                file = string.Empty
            }, JsonRequestBehavior.AllowGet);
        }

Upload Multiple Files Via Selecting Files or Drag & Drop Files?

In this section, we will implement the same uploader, but with some new features:

  • Allow selecting multiple files
  • Drag and drop files

We will create the same view like the one in single uploader section, but we need to add a few things:

  • Adding multiple attribute to the input file element that allow selecting multiple files
  • Adding section for drag and drop files like in the below code snippet
<div id="drop_zone">Drop images Here</div>

Then, we will add onchange event and assign it to JS method called MultiplefileSelected like we did before with SingleFileSelected method, but here we will play with all the files in the files list object and allow Drag and Drop files. See the code snippet.

In this method, we assigned the selected/dragged files to a global variable called selectedFiles, then for each file in selectedfiles, we will read the file using Read method in our DataURLreader object. I made this object for making the code a little bit clearer and readable instead of creating file reader for each file in the same method. After reading file, we can render the image information or any error. Let’s take a look at DataURLreader object.

function MultiplefileSelected(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    $('#drop_zone').removeClass('hover');
    selectedFiles = evt.target.files || evt.dataTransfer.files;
    if (selectedFiles) {
        $('#Files').empty();
        for (var i = 0; i < selectedFiles.length; i++) {
            DataURLFileReader.read(selectedFiles[i], function (err, fileInfo) {
                if (err != null) {
                    var RowInfo = '<div id="File_' + i + '" 
                    class="info"><div class="InfoContainer">' +
                                   '<div class="Error">' + err + '</div>' +
                                  '<div data-name="FileName" 
                                  class="info">' + fileInfo.name + '</div>' +
                                  '<div data-type="FileType" 
                                  class="info">' + fileInfo.type + '</div>' +
                                  '<div data-size="FileSize" 
                                  class="info">' + fileInfo.size() + 
                                  '</div></div><hr/></div>';
                    $('#Files').append(RowInfo);
                }
                else {
                    var image = '<img src="' + fileInfo.fileContent + 
                    '" class="thumb" title="' + 
                    fileInfo.name + '" />';
                    var RowInfo = '<div id="File_' + i + '" 
                    class="info"><div class="InfoContainer">' +
                                  '<div data_img="Imagecontainer">' + 
                                  image + '</div>' +
                                  '<div data-name="FileName" 
                                  class="info">' + fileInfo.name + '</div>' +
                                  '<div data-type="FileType" 
                                  class="info">' + fileInfo.type + '</div>' +
                                  '<div data-size="FileSize" 
                                  class="info">' + fileInfo.size() + 
                                  '</div></div><hr/></div>';
                    $('#Files').append(RowInfo);
                }
            });
        }
    }
}

The DataURLFileReader object contains read method that takes a file and callback method as parameters, at the first of the method, we create a new fileReader and handle its onload and onerror callback methods and at the end of function, we call readAsDataURL method to read the file, we create an object called fileInfo that will contain all file information and file content after it is loaded.

var DataURLFileReader = {
    read: function (file, callback) {
        var reader = new FileReader();
        var fileInfo = {
            name: file.name,
            type: file.type,
            fileContent: null,
            size: function () {
                var FileSize = 0;
                if (file.size > 1048576) {
                    FileSize = Math.round(file.size * 100 / 1048576) / 100 + " MB";
                }
                else if (file.size > 1024) {
                    FileSize = Math.round(file.size * 100 / 1024) / 100 + " KB";
                }
                else {
                    FileSize = file.size + " bytes";
                }
                return FileSize;
            }
        };
        if (!file.type.match('image.*')) {
            callback("file type not allowed", fileInfo);
            return;
        }
        reader.onload = function () {
            fileInfo.fileContent = reader.result;
            callback(null, fileInfo);
        };
        reader.onerror = function () {
            callback(reader.error, fileInfo);
        };
        reader.readAsDataURL(file);
    }
};

Using Drag and Drop for Selecting

Another technique for loading files is native drag and drop from local machine to browser and we will not use another plugin. Now all of the major browsers actually have a native support for Drag & Drop.

To start working with Drag and Drop, we need to add dragover and drop events on drop_zone element.

var dropZone = document.getElementById('drop_zone');
    dropZone.addEventListener('dragover', handleDragOver, false);
    dropZone.addEventListener('drop', MultiplefileSelected, false);
    dropZone.addEventListener('dragenter', dragenterHandler, false);
    dropZone.addEventListener('dragleave', dragleaveHandler, false);

The dragover event will fire when the file dragged over the drop target, in the handler of this method, we just prevent defaults of the browser and change the dropEffect property of the datatransfer object to copy, see code below:

function handleDragOver(evt) {
    evt.preventDefault();
    evt.dataTransfer.effectAllowed = 'copy';
    evt.dataTransfer.dropEffect = 'copy';
}

Then we will add the drop event and assign it with our MultiplefileSelected method to handle dropped files. It is good practice to add dragenter and dragLeave events. With these events, you can change the styles of Drop zone by adding CSS class to Dropzone element when Dragenter event fired and remove this class when dragleave event fired.

Now, we are ready to hit the start upload button to send files to the server. We just add onclick event that will call UploadMultipleFiles method when button is clicked.

This method is similar to our previous method Uploadfile except one thing we create the formdata object manually to validate/prevent sending non image files.

function UploadMultipleFiles() {

    // here we will create FormData manually to prevent sending mon image files
    var dataString = new FormData();
    //var files = document.getElementById("UploadedFiles").files;
    for (var i = 0; i < selectedFiles.length; i++) {
        if (!selectedFiles[i].type.match('image.*')) {
            continue;
        }
       }
// AJAX Request code here
}

Now the last thing we have to do is the server side code and it also similar to our previous single upload server side code. The only thing is we will receive a list of files, so our action signature should be like that:

public JsonResult UplodMultiple(HttpPostedFileBase[] uploadedFiles)

And make sure that HttpPostedFileBase array name is the same as the object name in the append method of formdata object. By this way, the MVC can map the files array.

public JsonResult UplodMultiple(HttpPostedFileBase[] uploadedFiles)
dataString.append("uploadedFiles", selectedFiles[i]);

Upload Big Size Files

To allow uploading big size files. if you use IIS 7 or greater. you should modify the web.config file and add the two sections below:

<system.webServer>
       <security>
                <requestFiltering>
                           <requestLimits maxAllowedContentLength="2147483648" />
                </requestFiltering>
       </security>
</system.webServer>

<httpRuntime targetFramework="4.5"  maxRequestLength="2097152"/>

This will allow you to upload file up to 2 giga.

Note: maxAllowedContentLength is measured in bytes while maxRequestLength is measured in kilobytes which is why the values are different (both are equivalent to 2 GB).

Browsers Support

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