Web API: Upload files via Ajax post - javascript

I have this following snippet for uploading files via Ajax post (using Knockout js library)
ko.bindingHandlers.fileUpload = {
init: function (element, valueAccessor) {
$(element).after('<div class="progress"><div class="bar"></div><div class="percent">0%</div></div><div class="progressError"></div>');
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var options = ko.utils.unwrapObservable(valueAccessor()),
property = ko.utils.unwrapObservable(options.property),
url = ko.utils.unwrapObservable(options.url);
if (property && url) {
$(element).change(function () {
if (element.files.length) {
var $this = $(this),
fileName = $this.val();
alert(fileName);
// this uses jquery.form.js plugin
$(element.form).ajaxSubmit({
url: url, //WEB API URL
type: "POST",
dataType: "text", //I want to upload .doc / excell files
headers: { "Content-Disposition": "attachment; filename=" + fileName },
beforeSubmit: function () {
$(".progress").show();
$(".progressError").hide();
$(".bar").width("0%")
$(".percent").html("0%");
},
uploadProgress: function (event, position, total, percentComplete) {
var percentVal = percentComplete + "%";
$(".bar").width(percentVal)
$(".percent").html(percentVal);
},
success: function (data) {
$(".progress").hide();
$(".progressError").hide();
// set viewModel property to filename
bindingContext.$data[property](data);
},
error: function (jqXHR, textStatus, errorThrown) {
$(".progress").hide();
$("div.progressError").html(jqXHR.responseText);
}
});
}
});
}
}
}
now my problem is creating the WEB API for this.
$(element.form).ajaxSubmit({
url: url, //WEB API URL
type: "POST",
dataType: "text", //I want to upload .doc / excell files
headers: { "Content-Disposition": "attachment; filename=" + fileName },
I want to upload Word/Excell document. Can someone give me an idea how to get this done in ASP.NET WEB API?
UPDATE:
I finally made an WEB API for this
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
//return new HttpResponseMessage()
//{
// Content = new StringContent("File uploaded.")
//};
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
It works ok (data successfully uploaded) but the problem is, it seems like in doesnt hit this section after the data is succesfully uploaded.
success: function (data) {
$(".progress").hide();
$(".progressError").hide();
// set viewModel property to filename
bindingContext.$data[property](data);
},
I have to inform the client of what the status of the upload.

Related

How to convert base64String to blob and send to controller via ajax in ASP.NET MVC?

I am new to ajax, currently I am using ajax trying to send base64string from view to controller and get a JsonResult as following:
$.ajax({
url: "#Url.Action("UploadSignature", "JobList")",
type: "POST",
dataType: "json",
data: { photoByte: base64String, eventRecordID: "123456" },
success: function (result) {
if (result.success === true) {
alert("Signature uploaded !");
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(XMLHttpRequest);
}
});
And my controller method is as following:
[HttpPost]
public async Task<ActionResult> UploadSignature(string photoByte, string eventRecordID)
{
byte[] photoAfterConvert = Convert.FromBase64String(photoByte);
...
//Upload photoAfterConvert to server
...
return Json(new { success = true });
}
However, the code above sometimes not working especially when the base64String is too long. Once I call the ajax, it hang and never go inside the controller method. Approximately 1 minute later, it will trigger the error callback function and the message is shown as below which is meaningless.
{"readyState":0,"status":0,"statusText":"error"}
Therefore, I was thinking is there any alternative way that I can send large string data from view to controller via ajax? Is sending blob a good choice? If yes, how can I achieve it by sending blob? What is the datatype that I need to put in the controller parameter to accept blob?
Thanks in advance for any help.
try this:
function Base62ToBlob (dataURI) {
'use strict'
var byteString,
mimestring
if (dataURI.split(',')[0].indexOf('base64') !== -1) {
byteString = atob(dataURI.split(',')[1])
} else {
byteString = decodeURI(dataURI.split(',')[1])
}
mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]
var content = new Array();
for (var i = 0; i < byteString.length; i++) {
content[i] = byteString.charCodeAt(i)
}
return new Blob([new Uint8Array(content)], { type: mimestring });
}
call action metthod from client side:
function FileUploadServer (blob) {
var fd = new FormData();
fd.append("file", blob, "FileName.png");
$http.post("URL", fd, {
headers: { 'Content-Type': undefined }
})
.success(function (response, status, headers, config) {
});
}
server side call receiver method:
public ContentResult Upload()
{
var files = System.Web.HttpContext.Current.Request.Files;
return CustJson( null);
}

Javascript Callback function after download Excel

I need to send mail to user after downloading .csv file. I am not sure how to set callback function after file gets downloaded.
$.ajax({
url: '#Url.Action("GetAllCustomer", "Customers")',
type: 'POST',
data: { 'customerIds': strCustomerId },
success: function (result) {
if (result == "Success") {
location.href = '#Url.Action("DownloadFile", "Customers", new { extension = "csv"})';
} else {
toastLast = toastr.error("No data found", "Generating File");
}
}
});
In above code in first call i am getting all the customers. On success callback i am calling DownloadFile method to download csv file. i have requirement to send mail after downloading file but i am not sure how will i know that file is downloaded. Or Can I achieve with some other way.
Please find my DownloadFile method of controller as below.
public ActionResult DownloadFile(string extension)
{
var dateTime = DateTime.Now.ToString("M.dd.yy");
var fileName = dateTime + " Application File." + extension;
var array = TempData["Output"] as byte[];
if (array != null)
{
var file = File(array, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
return file;
}
else
{
return new EmptyResult();
}
}
Don't use this line
location.href = '#Url.Action("DownloadFile", "Customers", new { extension = "csv"})';
Instead use a ajax request to the Action method
$.ajax({
type: "POST",
url: '#Url.Action("DownloadFile", "Customers")',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(){
//add your sccuess handlings
}
error: function(){
//handle errors
}
});

Ask user to open or download file

I have a C# method that exports a database table and a JS function that starts a download. The JS is this:
function Export()
{
$.ajax({
cache: false,
type: "POST",
url: '#Url.Action("exportDatabaseTable", "MyController")',
data:
{
'type': "xls"
},
dataType: "text",
beforeSend: function () //Display a waiting message
{
$('#wait').show();
},
success: function (result) //Result is "Message|FilePath"
{
var res = result.split('|');
if(res[0]!="Nothing found")
window.location = "DownloadDatabaseTable?fileName=" + res[1];
alert(res[0]);
},
error: function ()
{
alert("Export failed");
},
complete: function () //Hide the waiting message
{
$('#wait').hide();
}
});
}
While the C# one, called in the success function, is this:
[HttpGet]
public ActionResult DownloadDatabaseTable(string fileName)
{
System.IO.FileInfo file = new System.IO.FileInfo(fileName);
byte[] fileBytes = System.IO.File.ReadAllBytes(fileName);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, System.IO.Path.GetFileName(fileName));
}
Everything works, but the download starts automatically. Is there a way to show the download windows and let the user choose to download (and where to download it) or to open the file?

Spring MVC: Not able to send xml DOM to controller

Trying to build xml out of form values. Earlier I was building it with the help of jQuery. jQuery has a bug and then I have to build XML with plain javascript. But Now when I submit the form, browser hangs and request does not reach the controller.
Controller
#RequestMapping(value="/save",method=RequestMethod.POST,consumes={"application/json", "application/xml", "text/xml", "text/plain"})
#ResponseBody public String handleSave(#RequestBody String formData)
{
System.out.println("comes here");
System.out.println(formData);
return "SUCCESS";
}
jQuery
$('form').submit(function () {
$.ajax({
url: $(this).attr('action'),
type: 'POST',
data: collectFormData(),
headers: {
"Content-Type":"application/xml"
},
dataType: 'application/xml',
success: function (data) {
alert('Success:'+data)
},
error: function (jqXHR, textStatus, errorThrown) {
console.log('jqXHR:'+jqXHR+'\n'+'textStatus:'+'\n'+textStatus+'errorThrown:'+errorThrown);
}
});
return false;
});
CollectFormData
function collectFormData()
{
$rootElement = $('<FormXMLDoxument/>');
xmlDoc = document.implementation.createDocument("", "", null);
root = xmlDoc.createElement($('form').attr('name'));
$('form').find('div.section').each(function(index, section) {
sectionElement = xmlDoc.createElement($(section).attr('name'));
$(section).find('input').each(function(i, field) {
fieldElement = xmlDoc.createElement($(field).attr('name'));
fieldText=xmlDoc.createTextNode($(field).val());
fieldElement.appendChild(fieldText);
sectionElement.appendChild(fieldElement);
});
root.appendChild(sectionElement);
});
console.log((new XMLSerializer()).serializeToString(root));
return root;
}
Everything works fine if I implement collectFormData with jQuery but then I loose camel casing on element names.
Old collectFormData
function collectFormData()
{
$rootElement = $('<FormXMLDoxument/>');
$formElement = $.createElement($('form').attr('name'));
$('form').find('div.section').each(function(index, section) {
var $sectionElement = $.createElement($(section).attr('name'));
$(section).find('input').each(function(i, field) {
console.log($(field).attr('name'));
var $fieldName = $.createElement($(field).attr('name'));
$fieldName.text($(field).val());
$sectionElement.append($fieldName);
});
$formElement.append($sectionElement);
});
$rootElement.append($formElement);
console.log('Form XML is '+$rootElement.html());
return $rootElement.html();
}
My question related to jQuery bug

Knockout JS: Fileupload event

I have this knockout js script for uploading file
This code triggers the upload event when the user selects a file in the upload control
Upload.html
$(function() {
var viewModel = {
filename: ko.observable(""),
};
ko.applyBindings(viewModel);
});
<form>
<input id="upload" name="upload"
data-bind="fileUpload: { property: 'filename', url: 'http://localhost/api/upload/PostFormData' }"
type="file" />
<button id="submitUpload">Upload</button>
</form>
FileUpload.js
ko.bindingHandlers.fileUpload = {
init: function (element, valueAccessor) {
$(element).after('<div class="progress"><div class="bar"></div><div class="percent">0%</div></div><div class="progressError"></div>');
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var options = ko.utils.unwrapObservable(valueAccessor()),
property = ko.utils.unwrapObservable(options.property),
url = ko.utils.unwrapObservable(options.url);
if (property && url) {
$(element).change(function() {
if (element.files.length) {
var $this = $(this),
fileName = $this.val();
// this uses jquery.form.js plugin
$(element.form).ajaxSubmit({
url: url,
type: "POST",
dataType: "text",
headers: { "Content-Disposition": "attachment; filename=" + fileName },
beforeSubmit: function() {
$(".progress").show();
$(".progressError").hide();
$(".bar").width("0%")
$(".percent").html("0%");
},
uploadProgress: function(event, position, total, percentComplete) {
var percentVal = percentComplete + "%";
$(".bar").width(percentVal)
$(".percent").html(percentVal);
},
success: function(data) {
//$(".progress").hide();
//$(".progressError").hide();
// set viewModel property to filename
$("label[for='upload']").text(data);
bindingContext.$data[property](data);
},
error: function(jqXHR, errorThrown) {
$(".progress").hide();
$("div.progressError").html(jqXHR.responseText);
}
});
}
});
}
}
}
Now, I want to move the triggering of upload event to the submit button
<button id="submitUpload">Upload</button>
How to do this? Right now this is where I'm at, I just move the upload event inside the click event of the button. But it's not working, and it doesn't call the ajax request to the API.
$('#submitUpload').click(function () {
if (element.files.length) {
var $this = $(element),
fileName = $this.val();
//alert(element.form);
// this uses jquery.form.js plugin
$(element.form).ajaxSubmit({
url: url,
type: "POST",
dataType: "text",
headers: { "Content-Disposition": "attachment; filename=" + fileName },
beforeSubmit: function() {
$(".progress").show();
$(".progressError").hide();
$(".bar").width("0%")
$(".percent").html("0%");
},
uploadProgress: function(event, position, total, percentComplete) {
var percentVal = percentComplete + "%";
$(".bar").width(percentVal)
$(".percent").html(percentVal);
},
success: function(data) {
//$(".progress").hide();
//$(".progressError").hide();
// set viewModel property to filename
$("label[for='upload']").text(data);
bindingContext.$data[property](data);
},
error: function(jqXHR, errorThrown) {
$(".progress").hide();
$("div.progressError").html(jqXHR.responseText);
}
});
}
});
Instead of passing only name, URL to the bindinghandler pass third parameter (fileBinaryData) from your ViewModel Object then read the file Content in KO BindingHandler's Update method then update third observable (fileBinaryData) in update method.
Then you can use this filebinary data in you viewmodel
so for the button bind click event and access fileBinaryData observable which will have the file content.
BindingHandler:
ko.bindingHandlers.FileUpload = {
init: function (element, valueAccessor) {
$(element).change(function () {
var file = this.files[0];
if (ko.isObservable(valueAccessor())) {
valueAccessor()(file);
}
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
var file = ko.utils.unwrapObservable(valueAccessor());
var bindings = allBindingsAccessor();
if (bindings.fileBinaryData && ko.isObservable(bindings.fileBinaryData)) {
if (!file) {
bindings.fileBinaryData(null);
} else {
var reader = new window.FileReader();
reader.onload = function (e) {
bindings.fileBinaryData(e.target.result);
};
reader.readAsBinaryString(file);
}
}
}
}
HTML:
<input type="file" id="fileUpload" class="file_input_hidden" data-bind="FileUpload: spFile, fileObjectURL: spFileObjectURL, fileBinaryData: spFileBinary" />
ViewModel:
var viewModel = {
filename: ko.observable(""),
url: ko.observable(),
spFileBinary:ko.observable(),
//Write your CLICK EVENTS
};
Hope This Helps :)
element is unknown at the moment of click. you need to find it on the form.
Start the first line of your click function with
element = $('#upload').get(0);
and replace your button tag with the following
<input type="button" id="submitUpload" value="Upload"></input>
because the button tag automatically submits the form.

Categories

Resources