Search code examples
c#pdf-generationhttpresponseazure-web-rolesprincexml

PDF files generated in Azure VM downloaded as 0 byte files


I'm generating PDF files using Prince XML in an Azure VM (by a web role) using C#, MVC3 and .NET 3.5. Action methods that are tagged with a PdfFilter() attribute forward the HTML to Prince XML; once the PDF has been created, the new file is written to the client using the following code:

public class PdfFilterAttribute : ActionFilterAttribute
{
    private HtmlTextWriter tw;
    private StringWriter sw;
    private StringBuilder sb;
    private HttpWriter output;

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Hijack the HttpWriter and write it to a StringBuilder instead of the normal response (http://goo.gl/RCNey).
        sb = new StringBuilder();
        sw = new StringWriter(sb);
        tw = new HtmlTextWriter(sw);
        output = (HttpWriter)context.RequestContext.HttpContext.Response.Output;
        context.RequestContext.HttpContext.Response.Output = tw;
    }

    public override void OnResultExecuted(ResultExecutedContext context)
    {
        // Get the HTML from the request.
        string html = sb.ToString();

        // PdfController is where the PDF generation logic lives; instantiate it.
        var pdfController = new PdfController();

        // Generate a user-friendly filename for the PDF.
        string filename = pdfController.GetPdfFilename(html);

        // Generate the PDF and convert it to a byte array.
        FileInfo pdfInfo = pdfController.HtmlToPdf(html);

        // If the PDF or a user-friendly filename could not be generated, return the raw HTML instead.    
        if (pdfInfo == null || !pdfInfo.Exists || pdfInfo.Length == 0 || String.IsNullOrWhitespace(filename))
        {
            output.Write(html);
            return;
        }

        // If a PDF was generated, stream it to the browser for downloading.
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.AddHeader("Content-Type", "application/pdf");
        context.HttpContext.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + filename + "\";");
        context.HttpContext.Response.WriteFile(pdfInfo.FullName);
        context.HttpContext.Response.Flush();
        context.HttpContext.Response.Close();
        context.HttpContext.Response.End();
    }
}

I've confirmed that the PDFs are successfully created on the server. But when I attempt to send it back to the client by calling Response.WriteFile(), the client only sees the download as a 0-byte PDF -- it's unusable.

There aren't any exceptions being thrown, and the Prince XML log files indicate the files are all successfully generated. I've verified via C# and by remote desktop'ing into the Azure VMs that the PDFs -are- really being created and are readable there via a PDF reader.

Is there anything else I might be missing? Thanks in advance!


Solution

  • I solved my own problem -- seems like treating the PDF as an array of bytes, using a different method to write the bytes to the response, and adding an extra header to define the size of the PDF worked. It's still not 100% clean, but it's functioning as expected... anyway, here's the updated code in case anyone else finds this useful.

    public override void OnResultExecuted(ResultExecutedContext context)
    {
        // Get the HTML from the request.
        string html = sb.ToString();
    
        // PdfController is where the PDF generation logic lives; instantiate it.
        var pdfController = new PdfController();
    
        // Generate a user-friendly filename for the PDF.
        string filename = pdfController.GetPdfFilename(html);
    
        // Generate the PDF and convert it to a byte array.
        FileInfo pdfInfo = pdfController.HtmlToPdf(html);
    
        // Render the PDF as a byte array; if it can't be rendered, use an empty byte array instead.
        byte[] pdfBytes = (pdfInfo.Exists && pdfInfo.Length > 0 ? File.ReadAllBytes(pdfInfo.FullName) : new byte[]{});
    
        // If the PDF or a user-friendly filename could not be generated, return the raw HTML instead.    
        if (pdfBytes.Length == 0 || String.IsNullOrWhitespace(filename))
        {
            output.Write(html);
            return;
        }
    
        // If a PDF was generated, stream it to the browser for downloading.
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.AddHeader("Content-Type", "application/pdf");
        context.HttpContext.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + filename + "\";");
        context.HttpContext.Response.AddHeader("Content-Length", pdfBytes.Length.ToString());
        output.WriteBytes(pdfBytes, 0, pdfBytes.Length);
        context.HttpContext.Response.Flush();
        context.HttpContext.Response.Close();
        context.HttpContext.Response.End();
    }