Displaying a File() in the View from the controller - javascript

Is there a convenient way to display/Download a PDF file that is returned from the controller, the Controller retrieves a complex model and i am therefore using an Ajax call in order to call the ActionResult , the result received from the ActionResult is returned in the following format :
"%PDF-1.3
1 0 obj
[/PDF /Text /ImageB /ImageC /ImageI]
endobj
6 0 obj
....EOF"
Is there a way to assign this to a button to download , it is preferable that no Controller changes are required due to deployment issues with a client
i have tried to use a blob , however opening that blob returns a completely blank PDF
$.ajax({
type: "POST",
url: "Reporting/RenderRemoteReport",
data: { reportSetup: reportSetup },
success: function(response, status, xhr) {
debugger;
var type = xhr.getResponseHeader('Content-Type');
var blob = new Blob([response], { type: type });
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
$("#DLButton").attr("href",downloadUrl);
}
});

If you are getting the blog data well then follow this might be helpful.
How to convert Blob to File in JavaScript

Related

Is it possible to send a list of files and a whole json object in a FormData object to a ASP.NET CORE MVC Controller

Just like the title says, how do I send a formdata object to a mvc controller with both a json object (including nested objects) and list of files.
I have already tried to stringify the object to a json object but the controller can not read the property, it reads the file list without problems.
Here is the controller method:
[HttpPost]
public IActionResult CreateTask(Task task, IEnumerable<IFormFile> files)
{
//some code
}
here is my javascript:
function createTask() {
var formData = new FormData();
var files = //some file objects
var obj = {
//some parameters
};
var task = JSON.stringify(task);
formData.append("task", task);
formData.append("files", files);
console.log(task);
$.ajax({
type: "POST",
url: "/Task/CreateTask",
processData: false,
contentType: false,
data: formData,
success: function (data) {
},
error: function (data) {
}
})
}
I need the controller method to read both the task and the file list at the same time if this is possible.
The only way to do this would be to bind the JSON sent as task to a string server-side. Then, you'd have to manually deserialize it into an object. In case it's not obvious, that also means you won't get any validation on any of the members of that JSON object. It will just be a string as far as ASP.NET Core and the modelbinder is concerned.
That said, I think the issue here is that you're needing to upload files and think that that necessitates posting as multipart/form-data. You can actually post as JSON and yet still include file uploads. That requires two changes, though:
You must bind the file "uploads" to byte[]s, instead of IFormFiles, server-side.
Client-side, you must add them to the JSON object you're posting as either Base64-encoded strings or uint8 arrays.
The first part is relatively straight-forward. The JSON deserializer invoked by the modelbinder will automatically convert Base64-encoded strings to byte array, and of course a JS unint8 array is essentially just a byte array, anyways.
The second part probably bears a bit more discussion. You'll need to need to use the File API to read the upload file data, and then convert that into either a Base64-encoded string or uint8 array:
Base64
var reader = new FileReader();
reader.onload = function(e) {
let base64 = btoa(reader.result);
myJsonObject.files.push(base64);
}
reader.readAsBinaryString(file);
Byte Array
var reader = new FileReader();
reader.onload = function(e) {
let bytes = Array.from(new Uint8Array(reader.result));
myJsonObject.files.push(bytes);
}
reader.readAsArrayBuffer(file);
You could try to convert object to form-data like below:
View
<script type="text/javascript">
$(document).ready(function () {
$("input").change(function () {
var formData = new FormData();
var files = $("#files")[0].files;
var obj = {
id: 1,
name: "jack"
};
for (var key in obj) {
formData.append(key, obj[key]);
}
for (var key in files) {
formData.append("files", files[key]);
}
$.ajax({
type: "POST",
url: "/api/values/CreateTask",
processData: false,
contentType: false,
data: formData,
success: function (data) {
},
error: function (data) {
}
})
});
});
</script>
Controller
[HttpPost]
public IActionResult CreateTask([FromForm]Task task, [FromForm]IEnumerable<IFormFile> files)
{
return Ok("Success");
}

jQuery .ajax() calls the error function instead of success if the XHR response is an ArrayBuffer representing a JSON object with an error property

Context: I'm trying to implement an ajax download with a progress bar and error handling. The front end makes a request to a backend which responds with either a file to download, or an error in the form of a JSON object.
Problem: I'm using a custom xhr, where the responseType is set to arraybuffer. This allows me to create a Blob for the file which I then use to present the download. If the backend responds with a JSON object, I can detect it properly, convert it to a string, and parse it into a native Object. However, if that Object contains a property called error, which I use throughout my application, jQuery calls error() rather than success(), and passes one parameter, the value of the error property.
What the heck is going on? I've set dataType: false to avoid any processing of data, but it seems that jQuery is parsing the arraybuffer, despite the dataType setting and the response header being set to application/octet-stream, and deciding that the object, having an error property, requires the error() function to be called.
How can I avoid this behavior without changing my backend? An alternate implementation could be to leave the responseType as text, and then turn the text into a Blob when its a file, but I could never get that to work properly.
$.ajax({
url: "?download",
method: "POST",
data: {path: this.path, names: names},
dataType: false,
xhr: function() {
var myXhr = $.ajaxSettings.xhr();
myXhr.responseType = "arraybuffer"
myXhr.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percent = Math.round(e.loaded / e.total * 100);
var text = dir.formatSize(e.loaded, true) + "/" + dir.formatSize(e.total, true)
$("#dl-progress-bar").css("width", percent + "%");
$("#dl-progress-text").text(text)
}
})
return myXhr;
},
success: function(data) {
$("#dl-progress").hide()
$("#dl-progress-bar").css("width", "0")
$("#dl-progress-text").html(" ")
var bufView = new Uint8Array(data);
//Check if the arraybuffer is actually JSON, starting with {"
if (bufView[0] == 123 && bufView[1] == 34 && bufView.length < 200) {
var string = decodeURIComponent(escape(String.fromCharCode.apply(null, Array.prototype.slice.apply(bufView))))
try {
var json = JSON.parse(string)
if (json.success === false && json.error) {
this.error(json.error)
return
}
}
catch (e) {}
}
if (data != null && navigator.msSaveBlob)
return navigator.msSaveBlob(new Blob([data], { type: type }), name)
var a = $("<a style='display: none;'/>")
var blob = new Blob([data], {type: "application/octet-stream"})
var url = window.URL.createObjectURL(blob)
a.attr("href", url)
a.attr("download", filename)
$("body").append(a)
a[0].click()
setTimeout(function() {
window.URL.revokeObjectURL(url);
a.remove()
}, 0)
},
error: function(req, status, error) {
debugger
$("#dl-progress").hide()
$("#dl-progress-bar").css("width", "0")
$("#dl-progress-text").html(" ")
this.ajaxError(req, status, error)
}.bind(this)
})
The problem was that I didn't bind() my success function, so it was calling this.error() of the xhr object.

jQuery using $.post to upload file

I need to send POST data by jQuery to my PHP server. And this is what I'm currently receiving (which is what I want):
client_id: 55
client_name: 'Josh'
age: '35'
extra: array(0) -> array(['preview'] -> true, ['lookup'] -> false)
And to achieve this I have the following code:
var elements = [];
var listOfExtra = []
listOfExtra.push({'preview': true, 'lookup': false});
elements['client_id'] = 55;
elements['client_name'] = 'Josh';
elements['age'] = 35;
elements['extra'] = listOfExtra;
$.post(url, $.extend({}, elements));
But now I also need to send a file the user has upload, so by doing this:
elements['file'] = event.target.files[0];
I receive the message Illegal invocation in javascript
Since this didn't work I tried to implement a formData():
var formData = new FormData();
formData.append('client_id', 55);
formData.append('client_name', 'Josh');
formData.append('age', 35);
formData.append('extra', listOfExtra);
formData.append('file', event.target.files[0]);
$.ajax(
{
url: url
type: 'POST',
data: formData,
processData: false,
contentType: false
});
What happens is that now the extra is an [object][object], which is OK, so I just need to stringify it.
formData.append('extra', JSON.stringify(listOfExtra));
The problem now is that I have lots of code that uses $_POST['extra'][0]['preview'] (for example) and now I need to decode all of it before and use lots of conditions, like:
$extra = isset(json_decode($_POST['extra'])[0]['preview']);
if($extra != null)
$extraContent = json_decode($_POST['extra'])[0];
$preview = (isset($extraContent) ? $extraContent->preview : $extra[$_POST['extra'][0]['preview']);
Is there any way by using formData() or $.post I can keep the values sent from javascript to PHP like I want and send the file?
An easy way is, to not use jQuery for that but simply
var xhr = new XMLHttpRequest();
xhr.send(formData);
It should do all the right things, but works only in modern browsers (don't know if you need the older ones too). For compatibility look at http://caniuse.com/#feat=xhr2
Solved.
I converted the JSON values decoded into a normal $_POST again.
$extraContent = json_decode($_POST['extra'])[0];
foreach($extraContent as $key => $extra)
$_POST[$key] = $extra;

Struts2 Action Form Data Maximum Size for Image Upload using Base64

I have a Struts2 action that receives a string containing an image in Base64 and another string for the image name.
Everything works well for small sized images. But when I try to send a larger image, the Base64 and the image name strings are set to null in the action implementation.
In search for a solution I found that the default maximum size for the file upload is 2 MB. This limit can be increased with the following properties:
<constant name="struts.multipart.maxSize" value="104857600" />
<param name="maximumSize">52428800</param>
<param name="fileUpload.maximumSize">52428800</param>
However this is not working. Probably this implementation is not a file upload but a two string POST request.
Is there a way I can increase the size of the post request? Or the problem is something else?
public class GalleryAction extends BaseAction {
private String imageBase64;
private String imageName;
...
public final String uploadOperation() throws Exception {
if (this.imageBase64 == null || this.imageBase64.length() == 0
|| this.imageName == null || this.imageName.length() == 0) {
throw new Exception("Invalid argument");
}
byte[] decodedBytes = Base64Decoder.decode2bytes(this.imageBase64);
InputStream is = new ByteArrayInputStream(decodedBytes);
Graphics graphics = MGraphics.insertImageToDataBase(this.imageName, is);
// Issue server to sync image.
RestUtils.syncImage(graphics.getId());
JSONObject response = new JSONObject();
response.put("statusCode", "0");
jsonString = response.toString();
return JSON_RESPONSE;
}
...
}
EDIT:
I forgot to publish the code for the image upload.
Gallery.prototype.upload = function(base64, imageName) {
var galleryObject = this;
galleryObject.loadingCallback(true);
$.post(this.urlBase + "/upload", {
"imageBase64" : base64.match(/,(.*)$/)[1],
"imageName" : imageName
}, function(data) {
galleryObject.handlerErrorMessageCallback();
galleryObject.loadingCallback(false);
galleryObject.refreshImageGalleryCallback();
}, "json")
.fail(function() {
galleryObject.handlerErrorMessageCallback("error.upload.image");
galleryObject.loadingCallback(false);
});
}
The HTTP protocol specifications don't set a limit to the size of a POST message. However, your application server does it (mainly to prevent DDoS attacks).
Usually this threshold is 10 MegaBytes, and it is the one you are hitting. You should then be able to customize this setting according to your AS specs.
That said, this is not encouraged, and could lead to security vulnerabilities.
The best thing would be to:
use multipart/form-data over application/x-www-form-urlencoded;
use File instead of String since what you're uploading are files, and not strings.
I changed the upload method to Form data.
Gallery.prototype.upload = function(base64, imageName) {
var galleryObject = this;
galleryObject.loadingCallback(true);
var request = new FormData();
request.append("imageBase64", base64.match(/,(.*)$/)[1]);
request.append("imageName", imageName);
$.ajax({url:this.urlBase + "/upload",
data: request,
type: "POST",
processData: false,
contentType: false,
success: function(result)
{
galleryObject.handlerErrorMessageCallback();
galleryObject.loadingCallback(false);
galleryObject.refreshImageGalleryCallback();
}
}).fail(function() {
galleryObject.handlerErrorMessageCallback("error.upload.image");
galleryObject.loadingCallback(false);
});
}
Using this post method the following property on strus.xml must exists
<constant name="struts.multipart.maxSize" value="104857600" />

Add data to Jquery.Ajax post when uploading file?

I'm building a drag n drop upload feature for my MVC 4 webapp using this tutorial:
http://hayageek.com/drag-and-drop-file-upload-jquery/
However, besides the file, I also want to send info regarding where it came from (some ID for instance) and the type (domain-type).
In my ajax post below, how can I add extra data and read it server side?
In the tutorial code a line with 'extra data' is seen, however it is not actually implemented.
function sendFileToServer(formData, status) {
var uploadURL = '/home/upload';
var extraData = {}; //Extra Data.
var jqXHR = $.ajax({
xhr: function() {
var xhrobj = $.ajaxSettings.xhr();
if (xhrobj.upload) {
xhrobj.upload.addEventListener('progress', function(event) {
var percent = 0;
var position = event.loaded || event.position;
var total = event.total;
if (event.lengthComputable) {
percent = Math.ceil(position / total * 100);
}
//Set progress
status.setProgress(percent);
}, false);
}
return xhrobj;
},
url: uploadURL,
type: "POST",
contentType: false,
processData: false,
cache: false,
data: formData,
success: function (data) {
status.setProgress(100);
//$("#status1").append("File upload Done<br>");
}
});
status.setAbort(jqXHR);
}
Thanks!
To pass additional data to your controller via Ajax Upload File, it just needs to append your additional data to formData in your Ajax call function(sendFileToServer). i.e. you'd like to pass an upload target folder:
formData.append('targetFolder', 'User File Upload Folder');
and in your action get access to your additional parameter:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file, string targetFolder)
I fixed the problem by creating a workaround, I'm passing the extra data (id etc) via the URL which I receive as parameters on the controller method.

Categories