Angular download PDF from .net core web api - c#

I am struggeling with this already a few days. I found many threads on Stackoverflow but nothing could solve my issue.
I have the following Web API which should return a simple pdf file:
[HttpGet("pdf/{id}")]
public async Task<IActionResult> GetOrderAsPDF([FromRoute] int id)
{
MemoryStream ms = new MemoryStream();
PdfWriter writer = new PdfWriter(ms);
PdfDocument pdf = new PdfDocument(writer);
writer.SetCloseStream(false);
Document document = new Document(pdf);
Paragraph header = new Paragraph("HEADER")
.SetTextAlignment(TextAlignment.CENTER)
.SetFontSize(20);
document.Add(header);
document.Close();
ms.Position = 0;
return File(ms, "application/pdf", "test.pdf");
}
service.ts
async getOrderPDF(id):Promise<Blob>{
const url = `${this.baseUrl}/orders/pdf/${id}`;
const key: string = await this.getKey();
console.log('key:', key);
let headers = new HttpHeaders();
headers = headers.append('Authorization', [key]);
return this.http.get(url, {responseType: 'blob', headers: headers}).toPromise();
}
component.ts
exportPDF(div_id: string){
this.backhausService.getOrderPDF(2).then(x => {
var newBlob = new Blob([x], { type: "application/pdf" });
console.log(newBlob);
const data = window.URL.createObjectURL(newBlob);
var link = document.createElement('a');
link.href = data;
link.download = "Test.pdf";
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
});
}
This code saves the file "Test.pdf" but I am not able to open it. It says "Failed to Load PDF Document".
When I open the file with Notepad++ it shows me the PDF file as Base64 encoded string.
Content of Test.pdf
JVBERi0xLjcKJeLjz9MKNSAwIG9iago8PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDU2Pj5zdHJlYW0KeJwr5HIK4dJ3M1QwMlAISeMyMjXVszBWMLcw0LOwUAhJ4dLwcHV0cQ3SDMnicg3hCuQCAPm7ClwKZW5kc3RyZWFtCmVuZG9iago0IDAgb2JqCjw8L0NvbnRlbnRzIDUgMCBSL01lZGlhQm94WzAgMCA1OTUgODQyXS9QYXJlbnQgMiAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDYgMCBSPj4+Pi9UcmltQm94WzAgMCA1OTUgODQyXS9UeXBlL1BhZ2U+PgplbmRvYmoKMSAwIG9iago8PC9QYWdlcyAyIDAgUi9UeXBlL0NhdGFsb2c+PgplbmRvYmoKMyAwIG9iago8PC9DcmVhdGlvbkRhdGUoRDoyMDIwMTAyNjA5Mzg1NiswMCcwMCcpL01vZERhdGUoRDoyMDIwMTAyNjA5Mzg1NiswMCcwMCcpL1Byb2R1Y2VyKGlUZXh0riA3LjEuMTMgqTIwMDAtMjAyMCBpVGV4dCBHcm91cCBOViBcKEFHUEwtdmVyc2lvblwpKT4+CmVuZG9iago2IDAgb2JqCjw8L0Jhc2VGb250L0hlbHZldGljYS9FbmNvZGluZy9XaW5BbnNpRW5jb2RpbmcvU3VidHlwZS9UeXBlMS9UeXBlL0ZvbnQ+PgplbmRvYmoKMiAwIG9iago8PC9Db3VudCAxL0tpZHNbNCAwIFJdL1R5cGUvUGFnZXM+PgplbmRvYmoKeHJlZgowIDcKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMjcwIDAwMDAwIG4gCjAwMDAwMDA1NjEgMDAwMDAgbiAKMDAwMDAwMDMxNSAwMDAwMCBuIAowMDAwMDAwMTM3IDAwMDAwIG4gCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDQ3MyAwMDAwMCBuIAp0cmFpbGVyCjw8L0lEIFs8MDY0YjIxMjQwNmY5Y2FlMmNiMWVjNzgyM2ZmNzkwNjQ+PDA2NGIyMTI0MDZmOWNhZTJjYjFlYzc4MjNmZjc5MDY0Pl0vSW5mbyAzIDAgUi9Sb290IDEgMCBSL1NpemUgNz4+CiVpVGV4dC03LjEuMTMgZm9yIC5ORVQKc3RhcnR4cmVmCjYxMgolJUVPRgo=
So what am I doing wrong?
Thanks for your contribution in advance.
UPDATE
Here a screenshot of my api call response from the chrome dev tools.

Can you try the #joseph answer with little bit of change.
I have verified it with your data. and it is working fine.
import FileSaver
import * as FileSaver from 'file-saver';
and then
exportPDF(div_id: string){
this.backhausService.getOrderPDF(2).then(data => {
const byteCharacters = atob(data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], {type: "application/pdf"});
FileSaver.saveAs(blob, 'test.pdf');
});
}
Hopefully it will work for you also.
Here is working example with your data. https://stackblitz.com/edit/angular-7-master-v7ehpv?file=src/app/app.component.html
For more on Base64 to blob https://stackoverflow.com/a/16245768/6310485

Thanks for all your help. I finally got it solved.
What I didn't write into my question as I didn't know it was relevant, is that I am using an AWS API Gateway. That's where the problem was because API Gateway needs to be configured to support binay. See the aws docs.
As I wanted to have the api as simple as possible I've changed the code as follows to return the base64 encoded file in quotes.
API endpoint
[HttpGet("pdf/{id}")]
public async Task<IActionResult> GetOrderAsPDF([FromRoute] int id)
{
MemoryStream ms = new MemoryStream();
PdfWriter writer = new PdfWriter(ms);
PdfDocument pdf = new PdfDocument(writer);
writer.SetCloseStream(false);
Document document = new Document(pdf);
Paragraph header = new Paragraph("HEADER")
.SetTextAlignment(TextAlignment.CENTER)
.SetFontSize(20);
document.Add(header);
document.Close();
ms.Position = 0;
return Ok(ms.GetBuffer());
}
Service.ts
async getOrderPDF(id):Promise<string>{
const url = `${this.baseUrl}/orders/pdf/${id}`;
const key: string = await this.getKey();
let headers = new HttpHeaders();
headers = headers.append('Authorization', [key]);
return this.http.get<string>(url, {headers}).pipe(catchError(this.handleError<string>('getOrdersCustomer'))).toPromise();
}
Component.ts
exportPDF(div_id: string){
this.backhausService.getOrderPDF(2).then(x => {
console.log(x);
const data = `data:application/pdf;base64,${x}`;
var link = document.createElement('a');
link.href = data;
link.download = "test.pdf";
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
});
}
Through the change to the backend code the api call response looks as follows.

Related

How to download edited PDF using ITextSharp and C# Blazor

I have a Blazor application in which I'm trying to download a PDF I have edited using ITextSharp. All resources I search tend to use the Response class which I don't have (I believe because I am using .NET 6). I found some solutions that that send the byte array to a javascript function but I'm having no such luck. I get the error
System.ArgumentNullException: 'Value cannot be null.' When I Envoke the JS function.
I've posted my code below. Though I'm not passing any null objects, I do see some potential errors in my memory stream and I'm not sure why (See Image). Any information on where I'm going wrong or any easy approach would be appreciated. Thanks.
MemoryStream output = new MemoryStream();
PdfReader pdfReader = new PdfReader("MY_SOURCE_PATH_CHANGED_FOR_POST");
PdfStamper pdfStamper = new PdfStamper(pdfReader, output);
// Make Edits
//editPdf(ref pdfStamper.AcroFields);
pdfStamper.FormFlattening = false;
pdfStamper.Close();
await JSRuntime.InvokeVoidAsync("saveAsFile", "test.pdf", output.ToArray());
And the JS
function saveAsFile(filename, bytesBase64)
{
if (navigator.msSaveBlob) {
var data = window.atob(bytesBase64);
var bytes = new Unit8Array(data.length);
for (var i = 0; i < data.length; i++) {
bytes[i] = data.charCodeAt(i);
}
var blob = new Blob([bytes.buffer], { type: "application/octet-stream" });
navigator.msSaveBlob(blob, filename);
}
else
{
var link = document.createElement("a");
link.download = filename;
link.href = "data:application/octet-stream;base64," + bytesBase64;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
*Note for the sake of the post I changed the source path but I have verified the path does return a PDF, from a direct URL from azure blob storage.
For anyone who needs it, I solved this issue by creating a controller.
[HttpGet("{appId}/{formCode}")]
public FileContentResult DownloadForm(int appId, int formCode)
{
byte[] PdfData = Repo.GetFormBytes(appId, (Enums.FormType)formCode);
if (PdfData != null)
{
FileContentResult file = new FileContentResult(PdfData, "application/pdf");
file.FileDownloadName = Repo.MostRecentFileName;
return file;
}
else
return null;
}
Where GetFormBytes returns byte array output.ToArray() after it was edited. (See question).

angular 4 download pdf comes out blank

Hello I am trying to download a pdf from a report that i create on the server. When I create it I save it on the server for a moment then I make a second call and download it.
The following is the code that is on the web API:
var rootDirectory = HostingEnvironment.MapPath("~");
var filePath = $#"{rootDirectory}\Temp\{file}.pdf";
var bytes = File.ReadAllBytes(filePath);
File.Delete(filePath);
var memoryStream = new MemoryStream(bytes);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(memoryStream.ToArray())
};
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "report.pdf"
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return result;
then I get back to the typescript and here is the code that I use do download it:
this.reportsService
.download(id)
.subscribe(r => {
let pdf =r._body;
var pdfName = `${this.selectedReport.name}.pdf`;
let blob = new Blob([pdf], { type: 'application/pdf' });
window.open(URL.createObjectURL(blob));
});
});
I should mention that when I open the pdf from where it is saved on the server it is correct.
Can someone please tell me what I'm missing?

Angular/Web API 2 returns invalid or corrupt file with StreamContent or ByteArrayContent

I'm trying to return a file in a ASP.NET Web API Controller. This file is a dynamically-generated PDF saved in a MemoryStream.
The client (browser) receives the file successfully, but when I open the file, I see that all the pages are totally blank.
The thing is that if I take the same MemoryStream and write it to a file, this disk file is displayed correctly, so I assume that the problem is related to the file transfer via Web.
My controller looks like this:
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
memStream.Position = 0;
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(memStream.ToArray()); //OR: new StreamContent(memStream);
return result;
}
Just to try, if I write the stream to disk, it's displayed correctly:
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
memStream.Position = 0;
using (var fs = new FileStream("C:\\Temp\\test.pdf", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
memStream.CopyTo(fs);
}
return null;
}
The differences are:
PDF saved on disk: 34KB
PDF transferred via web: 60KB (!)
If I compare both files contents, the main differences are:
File Differences
On the left is the PDF transferred via web; on the right, the PDF saved to disk.
Is there something wrong with my code?
Maybe something related to encodings?
Thanks!
Well, it turned out to be a client (browser) problem, not a server problem. I'm using AngularJS in the frontend, so when the respose was received, Angular automatically converted it to a Javascript string. In that conversion, the binary contents of the file were somehow altered...
Basically it was solved by telling Angular not to convert the response to a string:
$http.get(url, { responseType: 'arraybuffer' })
.then(function(response) {
var dataBlob = new Blob([response.data], { type: 'application/pdf'});
FileSaver.saveAs(dataBlob, 'myFile.pdf');
});
And then saving the response as a file, helped by the Angular File Saver service.
I guess you should set ContentDisposition and ContentType like this:
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(memStream.ToArray())
};
//this line
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "YourName.pdf"
};
//and this line
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return result;
}
Try this
[HttpGet][Route("export/pdf")]
public HttpResponseMessage ExportAsPdf()
{
MemoryStream memStream = new MemoryStream();
PdfExporter.Instance.Generate(memStream);
//get buffer
var buffer = memStream.GetBuffer();
//content length for header
var contentLength = buffer.Length;
var statuscode = HttpStatusCode.OK;
var response = Request.CreateResponse(statuscode);
response.Content = new StreamContent(new MemoryStream(buffer));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentLength = contentLength;
ContentDispositionHeaderValue contentDisposition = null;
if (ContentDispositionHeaderValue.TryParse("inline; filename=my_filename.pdf", out contentDisposition)) {
response.Content.Headers.ContentDisposition = contentDisposition;
}
return response;
}

ASP.NET MVC EPPlus Download Excel File

So I'm using the fancy EPPlus library to write an Excel file and output it to the user to download. For the following method I'm just using some test data to minimize on the code, then I'll add the code I'm using to connect to database later. Now I can download a file all fine, but when I go to open the file, Excel complains that it's not a valid file and might be corrupted. When I go to look at the file, it says it's 0KB big. So my question is, where am I going wrong? I'm assuming it's with the MemoryStream. Haven't done much work with streams before so I'm not exactly sure what to use here. Any help would be appreciated!
[Authorize]
public ActionResult Download_PERS936AB()
{
ExcelPackage pck = new ExcelPackage();
var ws = pck.Workbook.Worksheets.Add("Sample1");
ws.Cells["A1"].Value = "Sample 1";
ws.Cells["A1"].Style.Font.Bold = true;
var shape = ws.Drawings.AddShape("Shape1", eShapeStyle.Rect);
shape.SetPosition(50, 200);
shape.SetSize(200, 100);
shape.Text = "Sample 1 text text text";
var memorystream = new MemoryStream();
pck.SaveAs(memorystream);
return new FileStreamResult(memorystream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { FileDownloadName = "PERS936AB.xlsx" };
}
Here's what I'm using - I've been using this for several months now and haven't had an issue:
public ActionResult ChargeSummaryData(ChargeSummaryRptParams rptParams)
{
var fileDownloadName = "sample.xlsx";
var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var package = CreatePivotTable(rptParams);
var fileStream = new MemoryStream();
package.SaveAs(fileStream);
fileStream.Position = 0;
var fsr = new FileStreamResult(fileStream, contentType);
fsr.FileDownloadName = fileDownloadName;
return fsr;
}
One thing I noticed right off the bat is that you don't reset your file stream position back to 0.

How do I convert several web pages into one pdf document?

public FileResult Download()
{
var doc = new EO.Pdf.PdfDocument();
EO.Pdf.HtmlToPdf.ConvertUrl("http://www.google.com/", doc);
var ms = new MemoryStream();
doc.Save(ms);
ms.Position = 0;
return new FileStreamResult(ms, "application/pdf")
{
FileDownloadName = "download.pdf"
};
}
Can you please show if possible, how to extend the code above to be able to convert several web pages into one pdf document?
The tricky part is that we don't know what pages a user is likely to attempt to convert.
So, hardcoding the webpages as the code above shows isn't helping us.
Any help is greatly appreciated.
//Create a new PdfDocument object
var doc = new EO.Pdf.PdfDocument();
//Convert two ore more different pages into the same PdfDocument
EO.Pdf.HtmlToPdf.ConvertUrl("c:\\1.html", doc);
EO.Pdf.HtmlToPdf.ConvertUrl("c:\\2.html", doc);
Latest code:
public FileResult Download()
{
var doc = new EO.Pdf.PdfDocument();
foreach(var url in passedUrls)
{
EO.Pdf.HtmlToPdf.ConvertUrl(url, doc);
doc.Save(ms);
}
ms.Position = 0;
return new FileStreamResult(ms, "application/pdf")
{
FileDownloadName = "download.pdf"
};
}
Latest from Adam (thank you sir)
public FileResult Download()
{
var documents = new List<EO.Pdf.PdfDocument>();
foreach(var url in passedUrls)
{
var doc = new EO.Pdf.PdfDocument();
EO.Pdf.HtmlToPdf.ConvertUrl(url, doc);
documents.Add(doc);
}
EO.Pdf.PdfDocument mergedDocument = EO.Pdf.PdfDocument.Merge(documents.ToArray());
}
Hopefully, others find these codes useful.
Based on the Help documentation I would recommend the following:
public FileResult Download()
{
var urls = new List<string>
{ // Populate list with urls
"C:\\1.html",
"C:\\2.html"
};
var documents = new List<EO.Pdf.PdfDocument>();
foreach(var url in urls)
{
var doc = new EO.Pdf.PdfDocument();
EO.Pdf.HtmlToPdf.ConvertUrl(url, doc);
documents.Add(doc);
}
EO.Pdf.PdfDocument mergedDocument = EO.Pdf.PdfDocument.Merge(documents.ToArray());
var ms = new MemoryStream();
mergedDocument.Save(ms);
ms.Position = 0;
return new FileStreamResult(ms, "application/pdf") { FileDownloadName = "download.pdf" };
}
Pass an array of url strings to the function
Then
foreach(var url in passedUrls)
{
EO.Pdf.HtmlToPdf.ConvertUrl(url, doc);
doc.Save(ms);
}

Categories