Reputation: 3702
I'm working on a Durandal application based in a .Net MVC5 project. I have a Web API 2 controller that generates a PDF by taking some data and merging it into existing PDF template using FDFToolkit. This works just fine, and I can save the new PDF to disk. My problem is that I want to stream the new PDF to the browser so the user can download it - but it's not working. I've tried many solutions to no avail.
Ideally, I'd rather not even save the generated PDF to disk as it doesn't seem necessary. That's the first way I attempted this - by keeping it all in memory and writing the new PDF to a byte array and sending that back to the browser. After trying every solution I could find, I settled on saving the new file to disk and trying to stream that new file to the browser. I figured that would help narrow down the problem.
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 169729
Content-Type: application/octet-stream
Expires: -1
Server: Microsoft-IIS/8.0
Content-Disposition: attachment; filename=myEticket.pdf
[RoutePrefix("api/tickets")]
[AllowAnonymous] // for debugging only
public class EticketsController : ApiController
{
[HttpGet,HttpPost, Route("download")]
public HttpResponseMessage DownloadPdf([FromBody] Eticket model)
{
const string templateName = "eTicketFinal.pdf";
const string outputname = "eTicket_new.pdf";
var templatePdfPath = Path.Combine(HttpContext.Current.Server.MapPath("~/eTickets/"), templateName);
var outputpath = Path.Combine(HttpContext.Current.Server.MapPath("~/eTickets/"), outputname);
var eticket = _unitOfWork.EticketRepository.GetById(model.EticketId);
var pdfticket = Mapper.Map<EticketPdf>(eticket);
using (var fdfApp = new FDFApp_Class())
{
using (var fdfDoc = fdfApp.FDFCreate())
{
var properties = pdfticket.GetType().GetProperties();
foreach (var prop in properties)
{
var name = prop.Name;
var propvalue = prop.GetValue(pdfticket, null);
var value = propvalue == null ? string.Empty : propvalue.ToString();
fdfDoc.FDFSetValue(name, value);
}
// this is working and creating the file on disk
fdfDoc.PDFMergeFDF2File(outputpath, templatePdfPath);
fdfDoc.FDFClose();
}
}
var stream = new FileStream(outputpath, FileMode.Open);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
//use attachment to force download
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "myEticket.pdf"
};
return result;
}
}
$.ajax({
type: 'POST',
url: '/api/tickets/download',
data: postdata,
datatype: 'json',
contentType: 'application/json; charset=utf-8'
});
I'm using Durandal/Knockout/Breeze on the client side, and I'm using EF6, Breeze, and Web API2 server side. I've made sure that the folder containing the PDFs has full security rights, and I put the runAllManagedModulesForAllRequests="true"
attribute on the modules
element in my web.config.
Any ideas?
Upvotes: 3
Views: 9575
Reputation: 142014
You should be able to do this with a byte array no problem. Your server code looks fine to me.
I would guess that the problem is that you can't use XHR/ajax type requests to trigger the file download workflow in the browser. All you need to do is render a <a>
link in the HTML with the href pointing to your API endpoint. You also need to change the server action method to be a GET.
When the user clicks on the link, it will do a GET and the resulting response will trigger the File Download dialog. If you put the media type application/pdf
then browsers like Chrome will render it directly.
Upvotes: 2