Uploading large files using HTML5, Microsoft MVC 3 and FineUploader

Standard

image_thumb.png

File sizes are getting larger (eg media files such as video) and increasingly web applications must be able to progressively go beyond the simple file input field.

Since the HTML5 spec supported the file api it is now possible to slice up very large files and send these progressively.

The FineUploader javascript library allows this to be done in a nice way.  Although the clientside example is complete, currently the MVC serverside example does not support file chunking.  (Although the Java example does)

Here’s an example I’ve created, based on the original that does.

I know that it would be better to update github (and perhaps I will) but for the time being It’s quicker to present it here.  I hope it helps someone and you are welcome to integrate it with FineUploaders example yourself.

Clientside code:

<script>
$(document).ready(function () {
$(‘#fine-uploader’).fineUploader({
request: {
endpoint: ‘Upload/UploadFile’
},
chunking: {
enabled: true,
chunksize: 100
},
debug: true,
autoupload: true
});
});
</script>

MVC Method:

[HttpPost]
public FineUploaderResult UploadFile(FineUpload upload)
{

// asp.net mvc will set extraParam1 and extraParam2 from the params object passed by Fine-Uploader
var dir = Server.MapPath(“~/App_Data/uploads”);

try
{

if (upload.IsMultipart())
{
// Save the part
var filePath = Path.Combine(dir, upload.PartUuid + “.” + upload.PartIndex);
upload.SaveAs(filePath,true);

// If last part do the merge
if(upload.PartIndex == (upload.TotalParts -1))
{
ulong bytesWritten = 0;
using (
var output = System.IO.File.OpenWrite(Path.Combine(dir, upload.OriginalFilename))
)
{
for (var i = 0; i < upload.TotalParts; i++) { // Open part file using ( var input = System.IO.File.OpenRead(Path.Combine(dir, upload.PartUuid + “.” + i)) ) { var buff = new byte[1]; while (input.Read(buff, 0, 1) > 0)
{
output.WriteByte(buff[0]);
bytesWritten++;
}
input.Close();
}
output.Flush();
}
output.Close();

if (bytesWritten != upload.FileSize)
{
throw new ApplicationException(“Upload Error. Please try again”);
}

// Clean up part files
for (var i = 0; i < upload.TotalParts; i++)
{
System.IO.File.Delete(Path.Combine(dir, upload.PartUuid + “.” + i));
}

}

}

}
else
{
var filePath = Path.Combine(dir, upload.Filename);
upload.SaveAs(filePath,true);
}

}
catch (Exception ex)
{
return new FineUploaderResult(false, error: ex.Message);
}

// the anonymous object in the result below will be convert to json and set back to the browser
return new FineUploaderResult(true, new { extraInformation = 12345 });
}
FineUploader.cs:

using System.IO;
using System.Web.Mvc;

namespace FineUploader
{
[ModelBinder(typeof(ModelBinder))]
public class FineUpload
{

private const string FILENAME_PARAM = “qqfile”;
private const string PART_INDEX_PARAM = “qqpartindex”;
private const string FILE_SIZE_PARAM = “qqtotalfilesize”;
private const string TOTAL_PARTS_PARAM = “qqtotalparts”;
private const string UUID_PARAM = “qquuid”;
private const string PART_FILENAME_PARAM = “qqfilename”;
private const string BLOB_NAME_PARAM = “qqblobname”;

public string Filename { get; set; }
public Stream InputStream { get; set; }
public int PartIndex { get; set; }
public int TotalParts { get; set; }
public string OriginalFilename { get; set; }
public string PartUuid { get; set; }
public ulong FileSize { get; set; }
public bool IsMultipart()
{
return (TotalParts > 0);
}
public void SaveAs(string destination, bool overwrite = false, bool autoCreateDirectory = true)
{
if (autoCreateDirectory)
{
var directory = new FileInfo(destination).Directory;
if (directory != null) directory.Create();
}

using (var file = new FileStream(destination, overwrite ? FileMode.Create : FileMode.CreateNew))
InputStream.CopyTo(file);
}

public class ModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.RequestContext.HttpContext.Request;
var formUpload = request.Files.Count > 0;

// find filename
var xFileName = request.Headers[“X-File-Name”];
var qqFile = request[FILENAME_PARAM];
var qqpartindex = request[PART_INDEX_PARAM];

var formFilename = formUpload ? request.Files[0].FileName : null;

var upload = new FineUpload
{
Filename = xFileName ?? qqFile ?? formFilename,
InputStream = formUpload ? request.Files[0].InputStream : request.InputStream,

};
int someval;
if (int.TryParse(qqpartindex, out someval))
{
upload.PartIndex = someval;
upload.OriginalFilename = request[PART_FILENAME_PARAM];
upload.TotalParts = int.Parse(request[TOTAL_PARTS_PARAM]);
upload.PartUuid = request[UUID_PARAM];
upload.FileSize = ulong.Parse(request[FILE_SIZE_PARAM]);
}

return upload;
}
}

}
}

This code is supplied as is, for informational purposes only with no warranties or statements about being fit for purpose and stuff.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s