A single page web application is a great user experience. There is just one page in the browser window and everything is handled via Ajax requests in the background.
But what if you want to upload a file? Due to security reasons, file upload is only possible with a <Input type=”file”/>. This element is typically present in a html form with a submit button. But that’s no option for us. Submitting a form the regular way will require the controller to redirect and this will cause a page refresh.
There are several solutions available. If you want to do it without external libraries, you can post to a hidden IFrame. But this is a crude way to do it anyway, because there is no feedback to the user about what’s going on.
You can also use the excellent library PlUpload which supports different plugins/systems such as Flash, Silverlight and HTML5 and falls back to them according to configuration.
Or, you could use the XMLHttpRequest object in javascript. You’ll need version 2 which is available in all latest browsers (except IE9, but it will be in IE10). Together with this object there is the FormData object. This encapsulates well.. form data :-) You can assign any data to it which is normally part of a form, including a file. Then use the XMLHttpRequest object to post it to the server.
We just need this HTML (don’t need a form):
<input type="file" id="fileUpload" name="fileUpload" />
<progress max="100" value="0">0%</progress>
And now for the javascript:
upload: function (uploadElement) {
var file = uploadElement.files[0];
if (file) {
var formData = new window.FormData();
formData.append(file.name, file);
formData.append("id",someId);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) //done
{
var response = $.parseJSON(xhr.responseText);
if (response.message) {
$('#uploadMessage').text(response.message);
}
if (response.success) {
$('#uploadMessage').text("");
}
}
};
xhr.open('POST', SomeController/FileUpload, true);
// Listen to the upload progress.
var progressBar = document.querySelector('progress');
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
progressBar.value = (e.loaded / e.total) * 100;
progressBar.textContent = progressBar.value; // Fallback for unsupported browsers.
}
};
xhr.send(formData);
}
}
The function accepts an uploadElement, which is the <input type=”file”>. Just select it with jQuery or regular javascript and send it as a parameter to this function.
You can see the FormData object being created and the values assigned to it. “someId” could be an ID for an entity this document will be saved with or whatever.
I’ll come to the onreadystatechange event later on. Supply the url to your controller method with the open call to the XMLHttpRequest object. You can also see XMLHttpRequest also supports upload progress with an convenient event.
Now, for the server side:
[HttpPost]
public ActionResult FileUpload(Guid id)
{
var file = Request.Files[0];
try
{
if (file != null && file.ContentLength > 0)
{
var user = SessionStore.GetUser();
if (user.DocumentUploadLimitMb < file.ContentLength / 1024 / 1024)
return Json(new { success = false, message = "Upload limit exceeded" });
var entity= repository.GetOne(id);
entity.DocumentFileName = file.FileName;
var fileData = new byte[file.ContentLength];
file.InputStream.Read(fileData, 0, file.ContentLength);
entity.Document = fileData;
repository.Update();
}
}
catch (Exception)
{
return Json(new {success = false});
}
return Json(new {success = true});
}
You’ll see I’m using a JsonResult to send back a JSON object indicating success or failure and optionally a message. Now look back to the Javascript function. I’m parsing the Json to an object and use the data to give the user feedback.
In this case I’m storing the document to a database. I’m using EF code first here and the Document property of entity is a byte[].