MemoryStream to HttpResponseMessage - javascript

I have generated a Pdf on the server, and need to return it as a response to my web client, so that I get a 'Save As' dialog.
The pdf is generated, and saved to a Memory stream... which is then returned to my method which will return the HttpResponseMessage.
The is the method:
[Route("GeneratePdf"), HttpPost]
public HttpResponseMessage GeneratePdf(PlateTemplateExtendedDto data)
{
var doc = GeneratePdf(DataForThePdf);
//using (var file = File.OpenWrite("c:\\temp\\test.pdf"))
// doc.CopyTo(file); // no need for manual stream copy and buffers
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
byte[] buffer = new byte[0];
//get buffer
buffer = doc.GetBuffer();
//content length for use in header
var contentLength = buffer.Length;
response.Headers.AcceptRanges.Add("bytes");
response.StatusCode = HttpStatusCode.OK;
response.Content = new StreamContent(doc);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("render");
response.Content.Headers.ContentDisposition.FileName = "yes.pdf";
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentLength = doc.Length;
return response;
}
However, the document renders as a blank file, and although it has a file size, and properties of the document I created (pdf information if File Properties is all right, as well as page width and height), the document displays as blank.
If I un-comment the code that is commented out, to save locally, the file is perfect. File size is 228,889 bytes. However, when I let it go to my web page and save it, it's 405,153 bytes and the filename is 'undefined'.
If I breakpoint, I see these results:
On the front end script, I handle the downloaded object like this:
$.post("/api/PlateTemplate/GeneratePdf", data).done(function (data, status, headers) {
// headers = headers();
var filename = headers['x-filename'];
var contentType = headers['content-type'];
//Create a url to the blob
var blob = new Blob([data], { type: contentType });
var url = window.URL.createObjectURL(blob);
var linkElement = document.createElement('a');
linkElement.setAttribute('href', url);
linkElement.setAttribute("download", filename);
//Force a download
var clickEvent = new MouseEvent("click", {
"view": window,
"bubbles": true,
"cancelable": false
});
linkElement.dispatchEvent(clickEvent);
});
I'm unsure where the file is being corrupted. What am I doing wrong?
Edit: Using the following code as suggested:
$.post("/api/PlateTemplate/GeneratePdf", data).done(function (data, status, headers) {
alert(data.length);
var xhr = new XMLHttpRequest();
$("#pdfviewer").attr("src", URL.createObjectURL(new Blob([data], {
type: "application/pdf"
})))
.Net code:
var doc = GeneratePdf(pdfParams);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
byte[] buffer = new byte[0];
//get buffer
buffer = doc.ToArray();
//content length for use in header
var contentLength = buffer.Length;
response.Headers.AcceptRanges.Add("bytes");
response.StatusCode = HttpStatusCode.OK;
response.Content = new StreamContent(doc);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("render");
response.Content.Headers.ContentDisposition.FileName = "yes.pdf";
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentLength = doc.Length;
return response;
It seems I am losing data.
The alert is the length of the 'data.length' in my javascript, after I get data back from the call.
The file properties is the original pdf file info.
File sends from api, size is 227,564, which matches the byte size on disk if I save it. So it SEEMS the sending is OK. But on the javascript size, when I read in the file, it's 424946, when I do: var file = new Blob([data], { type: 'application/pdf' }); (Where data is the response from the server).

The ContentLength setting looks somewhat suspicious (not consequent):
//content length for use in header
var contentLength = buffer.Length;
response.Content.Headers.ContentLength = doc.Length;

I 'fixed' this by using base64 encoded string from the .Net controller to the javascript web api call result, and then allowed the browser to convert it into binary by specifying the type ('application/pdf').

Related

sending string from C# to client and converting into Uint8Array type byte array and then into blob to open excel file. Corgi Involved

So here in C# code i am sending corgi to client which has corgiBabies. Using ClosedXml here.
var wbCorgiBabiesTemplate = new XLWorkbook();
var wsCoriBabiesAmendementTemplate = wbCorgiBabiesTemplate.Worksheets.Add(" Work Sheet Corgi baby Template");
wsCoriBabiesAmendementTemplate.Cell("A1").Value = "Corgi Parent";
wsCoriBabiesAmendementTemplate.Cell("B1").Value = "Corgi Child";
wsCoriBabiesAmendementTemplate.Cell("A2").Value = "Petunia";
wsCoriBabiesAmendementTemplate.Cell("B2").Value = "Khaleesi";
using (var ms = new MemoryStream())
{
wbCorgiBabiesTemplate.SaveAs(ms);
byte[] Corgibabies = ms.ToArray();
}
corgi.Corgibabies = System.Text.Encoding.UTF8.GetString(Corgibabies);
return corgi;
After that in Client i want to open corgibabies in excel sheet but the conversion here is wrong somewhere i think that excel sheet doesn't open correctly.
var fileName = 'CorgiBabies.xlsx';
dataAccessService.get('corgi')
.then(function(response) {
let utf8Encode = new TextEncoder();
var strBytes = utf8Encode.encode(response.corgiBabies);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
var file = new Blob([strBytes], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
var fileURL = window.URL.createObjectURL(file);
a.href = fileURL;
a.download = fileName;
a.click();
})
Below what excel sheet gives me error in image
Assuming you're on .Net Core+ (otherwise you can find the System.Buffers Nuget package for .Net standard or framework), on server side try
using System.Buffers;
using System.Buffers.Text;
and insert
var outputBuffer = new Span<byte>();
var status = Base64.EncodeToUtf8(Corgibabies, outputBuffer, out var consumed, out var written);
// sanity check
// if (status != OperationStatus.Done) throw new Exception();`
// do the above just before replacing
// System.Text.Encoding.UTF8.GetString(Corgibabies);
// with
System.Text.Encoding.UTF8.GetString(outputBuffer);
Now I'm pretty certain that will ensure that the server responds with what the client should expect but I'm not set up to test the Javascript side of things (yet). In the meantime let me know if this helps you make progress.
PS1: the error in your original code was the implicit assumption that Corgibabies is an array containing the bytes of a UTF8 encoded string. It actually contains the raw bytes of what would normally be an .xlsx file on disk. What is needed is to make that into text (Base64 encoding) and ensure that text is UTF8. Obviously in the Javascript you need to do the reverse - UTF8 Base64 to binary, save to disk, open in Excel...
Instead of returning string as the Content, you can make it work with File.
public ActionResult Get()
{
var wbCorgiBabiesTemplate = new XLWorkbook();
var wsCoriBabiesAmendementTemplate = wbCorgiBabiesTemplate.Worksheets.Add(" Work Sheet Corgi baby Template");
wsCoriBabiesAmendementTemplate.Cell("A1").Value = "Corgi Parent";
wsCoriBabiesAmendementTemplate.Cell("B1").Value = "Corgi Child";
wsCoriBabiesAmendementTemplate.Cell("A2").Value = "Petunia";
wsCoriBabiesAmendementTemplate.Cell("B2").Value = "Khaleesi";
wbCorgiBabiesTemplate.SaveAs("new.xlsx");
var ms = new MemoryStream();
wbCorgiBabiesTemplate.SaveAs(ms);
ms.Position = 0;
var fileName = "CorgiBabies.xlsx";
return File(ms, "application/octet-stream", fileName);
}
Api call:
or
fetch('https://localhost:7135/api/downloadExcel')
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = 'CorgiBabies.xlsx';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch(() => alert('oh no!'));
Ref: git
ClosedXML has several extensions that will help you acheive what you need :
ClosedXML.Extensions.AspNet
ClosedXML.Extensions.Mvc
ClosedXML.Extensions.WebApi
You can install the appropriate extension for your project, to help give you a fast access to download the workbook. You can also save the file on disk, and pass the file link (path) to JavaScript, and continue your work on the file from JavaScript.
if you need to know how you would let the user download the file from ASP.NET,
then you can do this :
Simple workbook :
C#: ASP.NET MVC
[HttpGet]
public ActionResult Download(string fileName)
{
// create workbook
var workbook = new XLWorkbook();
var sheet = workbook.Worksheets.Add("Worksheet 1");
sheet.Cell("A1").Value = "A1";
sheet.Cell("B1").Value = "B1";
sheet.Cell("A2").Value = "A2";
sheet.Cell("B2").Value = "B2";
// get workbook bytes
byte[] workbookBytes;
using (var memoryStream = new MemoryStream())
{
workbook.SaveAs(memoryStream);
workbookBytes = memoryStream.ToArray();
}
return File(workbookBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
}
C#: ASP.NET Web Forms
public void Export(HttpResponse response, string fileName)
{
// create workbook
var workbook = new XLWorkbook();
var sheet = workbook.Worksheets.Add("Worksheet 1");
sheet.Cell("A1").Value = "A1";
sheet.Cell("B1").Value = "B1";
sheet.Cell("A2").Value = "A2";
sheet.Cell("B2").Value = "B2";
HttpResponse httpResponse = response;
httpResponse.Clear();
httpResponse.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
httpResponse.AddHeader("content-disposition", $"attachment;filename=\"{fileName}.xlsx\"");
using (var memoryStream = new MemoryStream())
{
workbook.SaveAs(memoryStream);
memoryStream.WriteTo(httpResponse.OutputStream);
}
httpResponse.End();
}
the above examples will directly download the file into the client device. However, if you want to pass the workbook bytes to the JavaScript, you will need to convert it to base64 string and pass it to the JavaScript like so :
var base64String = Convert.ToBase64String(workbookBytes);
Then from JavaScript decode it to Uint8Array :
/*
JavaScript
*/
// get base64 string array and decoded it
var data = atob(serverSideResult);
var array = new Array(data.length);
for (var i = 0; i < data.length; i++) {
array[i] = data.charCodeAt(i);
}
// final result
var dataUint8Array = new Uint8Array(array);
now you can work with dataUint8Array as normal Uint8Array.
if you want to pass it back to the server-side, you can convert the array to base64 string, and pass it to the server-side like so :
/*
JavaScript
*/
let binaryString = ''
for (var i = 0; i < dataUint8Array.byteLength; i++) {
binaryString += String.fromCharCode(dataUint8Array[i]);
}
//pass base64Result to the server-side (C#)
var base64Result = window.btoa(binaryString);
then from C# you just need to convert it back to array from base64 string like so :
var bytes = Convert.FromBase64String(dataReceivedFromJavaScript);
where bytes would be byte[].

Use the base64 preview of the binary data response (zip file) in angularjs

I always get this error in the downloaded zip file C:\Users\me\Downloads\test.zip: Unexpected end of archive
My current code is:
var blob = new Blob([data], { // data here is the binary content
type: 'octet/stream',
});
var zipUrl = window.URL.createObjectURL(blob);
var fileName = orderNo;
fileName += '.zip';
downloadFile(null, fileName, null, zipUrl, null); // just creates a hidden anchor tag and triggers the download
The response of the call is a binary (I think). Binary Content Here
But the preview is a base64. Base64 Content. And it is the correct one. The way I verify it is by using this fiddle.
You can refer to the screenshot of the network here
I put the base64 content in this line var sampleBytes = base64ToArrayBuffer(''); And the zip downloaded just opens fine.
Things I have tried so far.
Adding this headers to the GET call
var headers = {
Accept: 'application/octet-stream',
responseType: 'blob',
};
But I get Request header field responseType is not allowed by Access-Control-Allow-Headers in preflight response.
We're using an already ajax.service.js in our AngularJS project.
From this answer
var blob = new Blob([yourBinaryDataAsAnArrayOrAsAString], {type: "application/octet-stream"});
var fileName = "myFileName.myExtension";
saveAs(blob, fileName);
There are other things that I have tried that I have not listed. I will edit the questions once I find them again
But where I'm current at right now. The preview is correct base64 of the binary file. Is it possible to use that instead of the binary? (If it is I will not find the other methods that I've tested) I tried some binary to base64 converters but they don't work.
So I just went and ditched using the ajax.service.js, that we have, for this specific call.
I used the xhr snippet from this answer. I just added the headers necessary for our call: tokens and auth stuff.
And I used this code snippet for the conversion thing.
And the code looks like this:
fetchBlob(url, function (blob) {
// Array buffer to Base64:
var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(blob)));
var blob = new Blob([base64ToArrayBuffer(base64)], {
type: 'octet/stream',
});
var zipUrl = window.URL.createObjectURL(blob);
var fileName = orderNo;
fileName += ' Attachments ';
fileName += moment().format('DD-MMM-YYYY');
fileName += '.zip';
downloadFile(null, fileName, null, zipUrl, null); // create a hidden anchor tag and trigger download
});
function fetchBlob(uri, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', uri, true);
xhr.responseType = 'arraybuffer';
var x = AjaxService.getAuthHeaders();
xhr.setRequestHeader('auth_stuff', x['auth_stuff']);
xhr.setRequestHeader('token_stuff', x['token_stuff']);
xhr.setRequestHeader('Accept', 'application/octet-stream');
xhr.onload = function (e) {
if (this.status == 200) {
var blob = this.response;
if (callback) {
callback(blob);
}
}
};
return xhr.send();
};
function base64ToArrayBuffer(base64) {
var binaryString = window.atob(base64);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
};
return bytes;
}

How can I send the POST request to the other server binding file into formdata

I have a pdf file which is generated into my local server with my server side code. I want to send a request to the another server requesting POST. The post method take parameter as FormData where formdata types
one is string and another is file type.
content-type
form-data
Body
 PDF file (file type)
string value
 
Is it possible to make the POST request without browsing the file location?
Doing some R&D I have overcome this problem with following some steps, as there is no way to get the file object from the physical location automatically in client side (basically in js) except browsing for security reason.
In my local server I have created a REST service. which response base64 string of the desired file.
Than I call the REST api from my javaScript and as a response I receive the base64 string. And than I convert it into bytes array and than Blob object and than File object.
base64 string==>bytes array==>Blob object==>File object
var base64 = this.getpdfFromLocal() //get the base64 string
var byteArray= this.base64ToByte(base64 );
var file = this.getFileFromByteArray(byteArray);
//return the byte array form the base64 string
MyApi.prototype.base64ToByte= function(base64) {
var binaryString = window.atob(base64);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes;
};
MyApi.prototype.getFileFromByteArray=function(byteArray) {
var blob = new Blob([byteArray]);
var file = new File([blob], "resource.pdf");
return file;
};
Lastly I make from data using file object and send request the another server REST web services.
var formdata = new FormData();
formdata.append("some_value", "Some String");
formdata.append("file", file);
var url = "http://yoururl.com";
var result =$.ajax({
url : url ,
type : 'POST',
data : formdata,
contentType : false,
cache : false,
processData : false,
scriptCharset : 'utf-8',
async : false
}).responseText;

Javascript formdata: encrypt files before appending

I need to modify existing frontend (angular) code that involves uploading files to a server. Now the files need to be encrypted before being uploaded.
The current approach uses FormData to append a number of files and send them in a single request as shown below:
function uploadFiles(wrappers){
var data = new FormData();
// Add each file
for(var i = 0; i < wrappers.length; i++){
var wrapper = wrappers[i];
var file = wrapper.file;
data.append('file_' + i, file);
}
$http.post(uri, data, requestCfg).then(
/*...*
I have been using Forge in other projects, but never in this sort of context and don't really see how to encrypt files on the fly and still append them as FormData contents.
Forge provides an easy API:
var key = forge.random.getBytesSync(16);
var iv = forge.random.getBytesSync(8);
// encrypt some bytes
var cipher = forge.rc2.createEncryptionCipher(key);
cipher.start(iv);
cipher.update(forge.util.createBuffer(someBytes));
cipher.finish();
var encrypted = cipher.output;
The backend recieves files using Formidable and all the file hanlding is already wired. I would thus like to stick to using the existing front-end logic but simply insert the encryption logic. In that, it's not the entire formdata that must be encrypted... I haven't found a good lead yet to approach this.
Suggestions are very welcome!
Ok, found a solution and added the decrypt code as well. This adds a layer of async code.
function appendFile(aFile, idx){
// Encrypt if a key was provided for this protocol test
if(!key){
data.append('dicomfile_' + idx, file);
appendedCount++;
onFileAppended();
}
else{
var reader = new FileReader();
reader.onload = function(){
// 1. Read bytes
var arrayBuffer = reader.result;
var bytes = new Uint8Array(arrayBuffer); // byte array aka uint8
// 2. Encrypt
var cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(bytes));
cipher.finish();
// 3. To blob (file extends blob)
var encryptedByteCharacters = cipher.output.getBytes(); // encryptedByteCharacters is similar to an ATOB(b64) output
// var asB64 = forge.util.encode64(encryptedBytes);
// var encryptedByteCharacters = atob(asB64);
// Convert to Blob object
var blob = byteCharsToBlob(encryptedByteCharacters, "application/octet-stream", 512);
// 4. Append blob
data.append('dicomfile_' + idx, blob, file.name);
// Decrypt for the sake of testing
if(true){
var fileReader = new FileReader();
fileReader.onload = function() {
arrayBuffer = this.result;
var bytez = new Uint8Array(arrayBuffer);
var decipher = forge.cipher.createDecipher('AES-CBC', key);
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(bytez));
decipher.finish();
var decryptedByteCharacters = decipher.output.getBytes();
var truz = bytes === decryptedByteCharacters;
var blob = byteCharsToBlob(decryptedByteCharacters, "application/octet-stream", 512);
data.append('decrypted_' + idx, blob, file.name + '.decrypted');
appendedCount++;
onFileAppended();
};
fileReader.readAsArrayBuffer(blob);
}
else{
// z. Resume processing
appendedCount++;
onFileAppended();
}
}
// Read file
reader.readAsArrayBuffer(aFile);
}
}
function onFileAppended(){
// Only proceed when all files were appended and optionally encrypted (async)
if(appendedCount !== wrappers.length) return;
/* resume processing, upload or do whathever */

Reduce size of image when converted to Base64

I am using the File reader in JavaScript,i need to Post my image to WebApi and convert it into byte Array and save it in server,Its working fine,Now my problem is base64 string increasing the size of image, Let say if i upload image of 30Kb, it is storing has 389Kb in server,How i can save in same size or reduce size of image need help
//File Reader
function OnFileEditImageEntry(file) {
var reader = new FileReader();
reader.onloadend = function (evt) {
var ImageBase64 = evt.target.result;
return ImageBase64 ;
};
reader.readAsDataURL(file);
}
//WEB API//
public IHttpActionResult UpdateUserDetails(ImageModel model)
{
try
{
if (model.ImageBase64 != "")
{
var PicDataUrl = "";
string ftpurl = "ftp://xxx.xxxxx.xxxx/";
var username = "xxx";
var password = "xxxxx";
string UploadDirectory = "xxxx/xx";
string FileName =model.ImageFileName;
String uploadUrl = String.Format("{0}{1}/{2}", ftpurl, UploadDirectory,FileName);
FtpWebRequest req = (FtpWebRequest)FtpWebRequest.Create(uploadUrl);
req.Proxy = null;
req.Method = WebRequestMethods.Ftp.UploadFile;
req.Credentials = new NetworkCredential(username, password);
req.EnableSsl = false;
req.UseBinary = true;
req.UsePassive = true;
byte[] data =Convert.FromBase64String(model.ImageBase64);
req.ContentLength = data.Length;
Stream stream = req.GetRequestStream();
stream.Write(data, 0, data.Length);
stream.Close();
}
}
}
Send the raw binary instead of increasing the size ~30% with base64/FileReader
with fetch
// sends the raw binary
fetch('http://example.com/upload', {method: 'post', body: file})
// Append the blob/file to a FormData and send it
var fd = new FormData()
fd.append('file', file, file.name)
fetch('http://example.com/upload', {method: 'post', body: fd})
With XHR
// xhr = new ...
// xhr.open(...)
xhr.send(file) // or
xhr.send(fd) // send the FormData
Normally when uploading files, try to avoid sending a json as many developers tends to to wrong. Binary data in json is equal to bad practice (and larger size) eg:
$.post(url, {
name: '',
data: base64
})
Use the FormData#append as much as possible or if you feel like it:
fd.append('json', json)

Categories

Resources