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
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
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());
});
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?
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
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