65.9K
CodeProject is changing. Read more.
Home

ResumableJS with ASP.NET MVC

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (4 votes)

Dec 17, 2012

CPOL
viewsIcon

20987

downloadIcon

649

Asp.net implementation for the resumable.js

Introduction

I searched a lot for a resumable, chunks based uploader, and finally I found this one: https://github.com/23/resumable.js but the problem was that there is no source code available in ASP.NET. So I decided to implement my own code in order to use it. 

Background 

This uploader method is very safe and fast, because it uses a chunks method (needs html5 in javascript's side), and it upload chunks in parallel.

Using the code

The uploader uses three main Actions (get, post, options):  

The Get method will check if the chunk exists, and you can disable it in javascript by setting the option "Test" to false. 

[HttpGet]
public void UploadFile()
{
    var queryString = Request.QueryString;
    if (queryString.Count == 0) return;

    try
    {
        // Read parameters
        var uploadToken = queryString.Get("upload_Token");
        int resumableChunkNumber = int.Parse(queryString.Get("resumableChunkNumber"));
        //var resumableIdentifier = queryString.Get("resumableIdentifier");
        //var resumableChunkSize = queryString.Get("resumableChunkSize");
        //var resumableTotalSize = queryString.Get("resumableTotalSize");
        var resumableFilename = queryString.Get("resumableFilename");
    

        // Check for existance
        var filePath = string.Format("{0}/{1}/{1}.part{2}", TEMP_URL, 
             uploadToken, resumableChunkNumber.ToString("0000"));
        var localFilePath = Server.MapPath(filePath);
        bool fileExists = System.IO.File.Exists(localFilePath);

        // Create Response
        if (fileExists)
        {
            Response.Status = "200 OK";
            Response.StatusCode = 200;
        }
        else
        {
            Response.Status = "404 Not Found";
            Response.StatusCode = 404;
        }
    }
    catch (Exception exception)
    {
        Logger.Error(this, "Failed to process uploading Get request.", exception);
    }
}  

The options method is used in some browsers (Chrome, Firefox):

[HttpOptions]
[ActionName("UploadFile")]
public void UploadFileOptions()
{
    Response.StatusCode = (int)HttpStatusCode.OK;
}

The post method will save the chunks on the disk: 

[HttpPost]
[ActionName("UploadFile")]
public async void UploadFilePost()
{
    var queryString = Request.Form;
    if (queryString.Count == 0) return;

    try
    {
        // Read parameters
        var uploadToken = queryString.Get("upload_Token");
        int resumableChunkNumber = int.Parse(queryString.Get("resumableChunkNumber"));
        var resumableFilename = queryString.Get("resumableFilename");
       
        // Save File
        if (Request.Files.Count == 0)
        {
            Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
            Logger.Warn("", "Request sent whithout any files !!");
        }
        else
        {
            var filePath = string.Format("{0}/{1}/{1}.part{2}", 
                 TEMP_URL, uploadToken, resumableChunkNumber.ToString("0000"));
            var localFilePath = Server.MapPath(filePath);
            
            if (!System.IO.File.Exists(localFilePath))
            {
                Request.Files[0].SaveAs(localFilePath);
            }

        }
    }
    catch (Exception exception)
    {
        return null;
    }
} 

Finally the Commit Action, which will merge the chunks into one file: 

[HttpPost]
public JsonResult CommitUpload()
{
    var results = new FileUploadResult();

    try
    {
        var queryString = Request.Form;
        if (queryString.Count == 0) return null;

        // Read parameters
        var uploadToken = queryString.Get("upload_Token");
        var resumableFilename = queryString.Get("resumableFilename");
        int resumableChunkSize = int.Parse(queryString.Get("resumableChunkSize"));
        long resumableTotalSize = long.Parse(queryString.Get("resumableTotalSize"));
       
        string fileUrl = String.Empty;

        var filePath = string.Format("{0}/{1}/", TEMP_URL, uploadToken);
        var directory = WebDirectory.MapPath(filePath);

        lock (_lock)
        {
            if (Directory.Exists(directory))
            {
                var files = Directory.GetFiles(directory);
                if (GetTotalSize(files) == resumableTotalSize)
                {
                    var fileRelativePath = this.SaveUploadedFile(files, resumableFilename);
                    if (!string.IsNullOrEmpty(fileRelativePath))
                    {
                        fileUrl = fileRelativePath.Replace("~/", Site.MediaServerUrl);
                        Directory.Delete(directory);
                    }
                }
            }
        }

        // Generate Result
        results = new FileUploadResult(resumableFilename, fileUrl, true, 
          string.Format(Resources.Global.FileUploadSuccess, resumableFilename));

        Response.Charset = System.Text.Encoding.UTF8.WebName;
        Response.ContentType = "text/html;charset=utf-8";

    }
    catch (Exception exception)
    {
        return null;
    }
 
    return Json(results);
}

And this is the Result Model:

[DataContract]
public class FileUploadResult
{
    [DataMember]
    public string FileName { get; set; }
    [DataMember]
    public string FileUrl { get; set; }
    [DataMember]
    public bool Succeed { get; set; }
    [DataMember]
    public string Message { get; set; }

    public FileUploadResult()
    {
    }

    public FileUploadResult(string fileName, string fileUrl, bool succeed, string message)
    {
        FileName = fileName;
        FileUrl = fileUrl;
        Succeed = succeed;
        Message = message;
    }
}  

You will find the JavaScript use method in the original site.  

Notes 

I modified something in the original JavaScript code, so you cannot use the original file with my implementation.