Generate PDF from HTML string - javascript

So, I am trying to generate PDF file by passing the HTML string to the server to generate the byte streams using NReco library http://www.nrecosite.com/pdf_generator_net.aspx and return it back to the client side however, after I converted to blob format and save it with FileSaver library https://github.com/eligrey/FileSaver.js/, the saved file is unable to open.
Below are my code by far:
controller
string HtmlContent = model.HtmlContent;
string PageType = gradeReportPdfBindingModel.PageType;
string FileName = gradeReportPdfBindingModel.FileName;
var pdfDoc = new HtmlToPdfConverter();
if (string.IsNullOrEmpty(PageType))
pdfDoc.Orientation = PageOrientation.Default;
else
{
if (PageType == "Landscape")
pdfDoc.Orientation = PageOrientation.Landscape;
else
pdfDoc.Orientation = PageOrientation.Portrait;
}
var pdfBytes = pdfDoc.GeneratePdf(HtmlContent);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new ByteArrayContent(pdfBytes);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = FileName;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return result;
Customer.js
Customer.Export(exportModel).then(function (response) {
var file = new Blob([response.data], { type: 'application/pdf' });
saveAs(file, fileName);
});
HTTP POST call
Export: function (model) {
return $http({
method: 'POST',
headers: {
accept: 'application/pdf'
},
url: appSettings.serverPath + '/customer/export',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: $.param(model),
});
},
Response log from browser console:
Object { data: "%PDF-1.4 1 0 obj << /Title (��) /Cr…", status: 200, headers: ed/<(), config: Object, statusText: "OK" }
Edit 1:
I tried to return the result as HttpResponseMessage. How can I consume it on javascript side?
Edit 2:
I figured it out. This is how I do it.
Controller:
[HttpPost]
[Route("export")]
public IHttpActionResult GeneratePdf(model)
{
var pdfBytes = pdfDoc.GeneratePdf(HtmlContent);
var policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(300), Priority = CacheItemPriority.NotRemovable };
var cacheId = Guid.NewGuid().ToString();
MemoryCache.Default.Add("pdfBytes_" + cacheId, pdfBytes, policy);
return Ok(cacheId);
}
[Authorize]
[HttpGet]
[Route("getPdf/{cacheId}/{fileName}")]
public HttpResponseMessage DownloadPdf(Guid CacheId, string FileName)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
try
{
var pdfBytes = (Byte[])MemoryCache.Default.Get("pdfBytes_" + CacheId);
MemoryCache.Default.Remove("pdfBytes_" + CacheId);
using (MemoryStream memorystream = new MemoryStream(pdfBytes))
{
response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new ByteArrayContent(memorystream.ToArray());
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = FileName;
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return response;
}
}
customer.js:
Customer.Export(model).then(function (response) {
Customer.DownloadPdf(response.data, fileName).then(function (response) {
var file = new Blob([response.data], { type: "application/pdf" });
saveAs(file, fileName);
}).catch(function (response) {
console.error('Error', response.status, response.data);
});
});
Http calls:
Export: function (model) {
return $http({
method: 'POST',
url: appSettings.serverPath + '/customer/export',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: $.param(model),
});
},
DownloadPdf: function (cacheId, fileName) {
return $http({
method: 'GET',
headers: {
accept: 'application/pdf'
},
responseType: 'arraybuffer',
url: appSettings.serverPath + '/customer/downloadPdf/' + cacheId + '/' + fileName,
headers: { 'Content-Type': 'application/pdf' }
});
}
I basically tested it on my local dev machine and it works, but when I published it Azure Web App, I was unable to get through the POST call and it returns status 500 after some time. Not sure why, but I am suspecting the MemoryCache I am using. Any idea will be appreaciated.
Thanks.

Related

How can we download an XML file through ajax in ASP mvc?

This is my Controller code for returning the XDocument file. I turn it into a MemoryStream then return it as base 64 string.
[HttpPost]
public ActionResult ImportAcesFileAjax(TransactionViewModel transactionViewModel)
{
XDocument xDocument = new XDocument();
try
{
if (transactionViewModel.File.IsNotNullObject())
{
ImportService importService = new ImportService();
xDocument = importService.ProcessExcelFileForTransformation(transactionViewModel);
_logger.Information($"Finished processing {transactionViewModel.File.FileName}");
}
}
catch (Exception e)
{
_logger.Error($"Error: {e.Message}");
_logger.Error(e.StackTrace);
throw;
}
return ReturnImportXmlAsByte(xDocument, "import.xml");
}
protected ActionResult ReturnImportXmlAsByte(XDocument xDocument, string xmlFilename)
{
using (MemoryStream stream = new MemoryStream())
using(XmlWriter xmlWriter = XmlWriter.Create(stream))
{
xDocument.WriteTo(xmlWriter);
var byteLength = stream.ToArray().Length;
var base64String = Convert.ToBase64String(stream.ToArray(), 0, byteLength);
return Json(new {base64String = base64String, xmlFilename = xmlFilename});
}
}
I use the following code in the front end:
$.ajax({
url: requestUrl,
type: "POST",
data: formData,
contentType: false,
processData: false,
success: function (excelByteResult) {
var bytes = base64ToBytes(excelByteResult.base64String);
var blob = new Blob([bytes], { type: "text/xml" });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = excelByteResult.xmlFilename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log("Xhr: " + jqXHR.responseText);
console.log("Status: " + status);
console.log("Error Thrown: " + errorThrown);
}
});
The ajax call is successful but I can't seem to access the file. Are there any alternatives to my approach we may work? The requirement is to download an xml file via ajax

Getting pdf file from api response

I am calling an api and getting pdf in return.
fetch(`api` + guid, {
method: "GET",
headers: {
"Accept": "application/octet-stream",
"Authorization": "Bearer " + token,
},
responseType: 'arraybuffer',
})
.then((res) => res.text())
.then((data) => {
fs.writeFileSync('file.pdf', data);
});
I get the pdf file but the issue is the pdf file is always empty. But when I accept response as json, it works fine.
I found similar problems like this but none of the solution worked for me yet.
It would be great if someone can point out the issue.
I found the issue.
As I am using fetch not Axios.
We cannot pass responseType as Fetch's option.
fetch(`api` + guid, {
method: "GET",
headers: {
"Accept": "application/octet-stream",
"Authorization": "Bearer " + token,
},
// responseType: 'arraybuffer' //#1 remove this,
})
Instead the response in itself can be passed as arraybuffer as below.
.then((res) => res.arraybuffer())
instead of
.then((res) => res.text())
Now instead of directly using the response to write our pdf file. We can change the data to base64 string and decode it back again to create our pdf file. I used base64ToPdf npm package to handle that.
.then(data => {
var base64Str = Buffer.from(data).toString('base64');
base64.base64Decode(base64Str, "file.pdf");
})
I hope this help others. :)
Change res.arraybuffer() to res.arrayBuffer()
Below is the working code with webdriverio-
var headers = {
Authorization: "Bearer " + accessToken,
Accept: 'application/pdf'
}
fetch(
apiurl,
{
headers: {
Accept: "application/octet-stream",
Authorization: "Bearer " + accessToken
},
},
)
.then((res) => {
if (!res.ok) {
return res.status.toString()
}
return res.arrayBuffer()
})
.then((data) => {
var base64Str = Buffer.from(data).toString('base64');
base64.base64Decode(base64Str, filename);
})
.catch(
(err) => {
return err.Message;
})
Here's example which works for me:
async createPdf(context, data) {
let url = new URL(baseURL + '/invoice/createPdf');
url.search = new URLSearchParams({
id: data
})
await fetch(url, {
method: 'GET',
headers: {
'Authorization': "Bearer " + localStorage.getItem("jwt"),
'Accept': 'application/octet-stream'
},
}).then((res) => res.arrayBuffer())
.then(data => {
var base64Str = Buffer.from(data).toString('base64');
var binaryString = window.atob(base64Str);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
var arrBuffer = bytes;
var newBlob = new Blob([arrBuffer], { type: "application/pdf" });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(newBlob);
return;
}
data = window.URL.createObjectURL(newBlob);
var link = document.createElement('a');
document.body.appendChild(link);
link.href = data;
link.download = "Faktura.pdf";
link.click();
window.URL.revokeObjectURL(data);
link.remove();
})
}
In my case, the response is same as yours and I'm trying to convert it to a pdf file so that I can preview it on the UI.
For this, I need to fetch the URL already present in the response which is of type blob... to fetch the URL I did URL.createObjectURL(myblob)
const [url,seturl] = useState('');
response
.then((resp) => resp.blob())
.then((myBlob) => {
seturl(URL.createObjectURL(myBlob)); //<-- use this for fetching url from your response
console.log(myBlob);
})
.catch((err) => {
console.log(err.message());
});

download file with blob from asp.net mvc

I want to download an excel from my angular controller using blob.
the server code of the mvc application is
[HttpGet]
public HttpResponseMessage Download(SearchModel search){
string fileName = "test.xls"
byte[] file = GetFile(search)
HttpResponseMessage httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.Content = new ByteArrayContent(file.ToArray());
httpResponseMessage.Content.Headers.Add("x-filename", fileName);
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = fileName;
return httpResponseMessage;
}
and the js
$http({ method: 'GET', url: 'Download', params: {...some params}, responseType: 'arrayBuffer' })
.success(function (data, status, headers) {
headers = headers();
var filename = headers['x-filename'];
var contentType = headers['content-type'];
var linkElement = document.createElement('a');
try {
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(new Blob([data],{type: "application/octet-stream"}));
return;
}
var a = $("<a style='display: none;'/>");
var url = window.URL.createObjectURL(new Blob([data], {type: "application/octet-stream"}));
a.attr("href", url);
a.attr("download", "name");
$("body").append(a);
a[0].click();
window.URL.revokeObjectURL(url);
a.remove();
}
catch (ex) {
console.log(ex);
}
})
.error(function () { });
but there are some problems:
the filename in the javascript side is undefined and not test.xls as in the server side
the contentType is "text/html; charset=utf-8" and not "application/octet-stream" as in the server side
the file that is downloaded is this
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.ByteArrayContent, Headers:
{
x-filename: test.pdf
Content-Type: application/octet-stream
Content-Disposition: attachment; filename=test.pdf
}
The file is a JSON with header information .
Why is this file wrong?

Node/Request Error: "Processing POST Request: No Content-Type"

I have a front end Canvas that I transform into a png file that I need to POST to a third party vendor's api. It passes back to node as a base64 file and I decode it, but when I attempt the upload, it gives me the following error:
Problem processing POST request: no Content-Type specified
However, I am clearly specifying the content type in my POST call. My end goal is to upload the file to my vendor's API.
Here are the key front end aspects:
var canvasImage = document.getElementById("c");
var img = canvas.toDataURL({
multiplier: canvasMultiplier
});
var fileTime = Date.now();
var myFileName = $scope.productCode + fileTime;
$scope.filenameForVendor = myFileName;
var filename = $scope.filenameForVendor;
$http.post('/postVendor', { filename: filename, file: img }).success(function (data) {
console.log("Uploaded to Vendor");
Here is the backend POST:
app.post('/postVendor', function (req, res, next) {
var filename = req.body.filename;
var file = req.body.file;
fileBuffer = decodeBase64Image(file);
request({
url: "http://myvendorapi/ws/endpoint",
method: "POST",
headers: {
'contentType': fileBuffer.type
},
body: fileBuffer.data
}, function (error, response, body) {
console.log(response);
});
})
// Decode file for upload
function decodeBase64Image(dataString) {
var matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),
response = {};
if (matches.length !== 3) {
return new Error('Invalid input string');
}
response.type = matches[1];
response.data = new Buffer(matches[2], 'base64');
return response;
}
I can POST using AJAX on the front end, but because of CORS and the vendor blocking all but server side calls to the endpoints (and they don't have JSONP), I can't use this. They are allowing my IP through for testing purposes so only I can make this work from my machine:
var send = function (blob) {
var fileTime = Date.now();
var myFileName = $scope.productCode + fileTime;
$scope.filenameForVendor = myFileName;
var filename = $scope.filenameForVendor;
var formdata = new FormData();
formdata.append('File1', blob, filename);
$.ajax({
url: 'http://myvendorapi/ws/endpoint',
type: "POST",
data: formdata,
mimeType: "multipart/form-data",
processData: false,
contentType: false,
crossDomain: true,
success: function (result) {
console.log("Upload to Vendor complete!");
// rest of code here/including error close out
}
var bytes = atob(dataURL.split(',')[1])
var arr = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arr[i] = bytes.charCodeAt(i);
}
send(new Blob([arr], { type: 'image/png' }));
Update:
I realized that contentType should be 'content-type'. When I did this, it creates an error of no boundary specified as I am trying multipart-form data (which I did all wrong). How can I pass formData to Node for uploading?
Update 2:
Per the advice offered, I tried using multer but am getting an ReferenceError: XMLHttpRequest is not defined.
Client side:
var fileTime = Date.now();
var myFileName = $scope.productCode + fileTime;
$scope.filenameForVendor = myFileName;
var filename = $scope.filenameForVendor;
var formdata = new FormData();
formdata.append('File1', blob, filename);
$http.post('/postVendor', formdata, { transformRequest: angular.identity, headers: { 'Content-Type': undefined } }).success(function (data) {
Server side:
app.post('/postVendor', function (req, res, next) {
var request = new XMLHttpRequest();
request.open("POST", "http://myvendorapi.net/ws/endpoint");
request.send(formData);
})
Why do you base64 encode the file?
You can upload raw file to your Node using FormData and you will not have to decode anything.
Front end
...
var request = new XMLHttpRequest();
request.open('POST', 'http://node.js/method');
request.send(formData); // vanilla
--- or ---
...
$http.post('http://node.js/method', formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}); // angular
Back end
Just install request.
...
var request = require('request');
app.post('/method', function (req, res, next) {
// if you just want to push request you don't need to parse anything
req.pipe(request('http://vendor.net')).pipe(res);
}) // express

Saving a application/octet-stream to disk from javascript in IE9

I provide a method on my WebApi to download a file:
public HttpResponseMessage Get(int id)
{
MemoryStream file = RetrieveFile(id);
if (file != null)
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(file);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "dummy.txt"
};
return response;
}
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
This works fine and i can consume it with a WebClient.
I also tied to implement the javascript front-end using FileSaver.js :
$http({
url: myUrl,
method: 'GET',
params: {
id: 1
},
headers: {
'Content-type': 'application/json'
}
}).success(function (data, status, headers, config) {
var file = new Blob([data], {
type: 'application/octet-stream'
});
saveAs(file, "test.txt");
}).error(function (data, status, headers, config) {
});
This works great but it fails on IE9 since FileSaver and Blop is not supported.
I have tried opening the url in a new window but can't find a decent working solution for IE9

Categories