How to return a PDF in an action result in MVC

2019-06-25 19:15发布

问题:

I have a little problem getting my head around this problem. I have an ajax call that should render an iframe which loads a PDF. The PDF is generated using Apache FOP hosted in another environment. What I have so far is:

in the controller action (where the src element of the iFrame points), the code snippet is:

var targetStream = new MemoryStream();    
using (var response = FOPrequest.GetResponse()) // response from FOP
                {
                    using (var stream = response.GetResponseStream())
                    {
                        stream.CopyTo(targetStream);

                    }
                }
 return new FileStreamResult(targetStream, "application/pdf");

However, this does not work as expected. The stream is populated as expected, but the PDF does not render in the iFrame. I get a Http response code of 200 (OK).

I'd be grateful of any help.

回答1:

You use MVC FileContentResult to return ActionResult For example:

return File(fileArray, contentType, fileName)

another stack Answer



回答2:

You can make use of the return type as FileResult here like this :

public FileResult getFile(string CsvName)
{
   //Add businees logic here
   byte[] fileBytes = System.IO.File.ReadAllBytes(System.Web.Hosting.HostingEnvironment.MapPath("~/Content/Uploads/records.csv"));
   string fileName = CsvName;
   return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

Might not be the exact solution, you can manipulate and develop your own.

Hope this helps.



回答3:

Install MvcPdfActionResult via nuget

PM> Install-Package MvcPdfActionResult

Simply use return type as PdfActionResult in the controller will output PDF document instead of HTML. This converts HTML to PDF using the iTextXmlWorker Library.

  ...
  return PdfActionResult(model);
}

Generates pdf documents from your razor views within an asp.net 5 MVC project. https://www.nuget.org/packages/MvcPdfActionResult/



回答4:

For FileStreamResult your targetStream needs to be still OPEN. "using" section closes your response stream and it looks like your targetStream is also closed then...

When I use MemoryStream that is not closed - your code works! I got it thanks to Natan Cooper comment and link.



回答5:

Although standard action results FileContentResult or FileStreamResult may be used for downloading files, for reusability, creating a custom action result might be the best solution. Create a custom action result PDFResult.

PDFResult class inherits abstract ActionResult class and overrides the ExecuteResult method.

public class PDFResult : ActionResult
{
    private readonly byte[] fileContents;
    private string fileName;

    public PDFResult(byte[] contents, string filename)
    {
        this.fileContents = contents;
        this.fileName = filename;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.Clear();
        response.ContentType = "application/pdf";
        response.AddHeader("content-disposition", string.Format(@"attachment;filename=""{0}""", fileName));
        response.BinaryWrite(fileContents);
    }
}

In the Controller use the custom PDFResult action result as follows

[HttpGet]
public async Task<PDFResult> ExportToPDF()
{
    var model = new Models.MyDataModel();
    byte[] fileContents = await model.GetFileContents();
    string filename = "myfile.pdf";
    return new PDFResult(fileContents, filename);
}

Since we are downloading the file using HttpGet, create an empty View without model and empty layout.

Blog post about custom action result for downloading files that are created on the fly:

https://acanozturk.blogspot.com/2019/03/custom-actionresult-for-files-in-aspnet.html