A simple Pdf ActionResult in MVC

Recently, I needed an ActionResult implementation to return the Pdf documents from my Controller Action to MVC views and it tooks few minutes to build the functionality on the existing FileResult.

I decided to build a base class PdfResult to abstract the ContentType implementation as well as it will also serve as a common return type from the action method irrespective of derived implementation like Content, Path etc.

PdfResult Base class

///
/// PdfResult
/// Represents a base class that is used to send PDF file content to the response.
public abstract class PdfResult : FileResult
{
    #region Constructor
     ///
     /// Initializes a new instance of the FileResult class.
     ///

    public PdfResult()
      : base(System.Net.Mime.MediaTypeNames.Application.Pdf)
    {
    }

    #endregion
}

PdfStreamResult It’s one of the implementation class for PdfResult to return the Pdf content as stream on HttpResponse Stream.

///
/// PdfStreamResult
/// Sends binary content to the response by using a Stream instance.
///
public class PdfStreamResult : PdfResult
{
    // Fields
    private const int _bufferSize = 0x1000;

    #region Constructor

    ///
    /// PdfContentResult
    /// Initializes a new instance of the FileStreamResult class.
    ///
    public PdfStreamResult(Stream fileStream)
       : base()
    {
         if (fileStream == null)
         {
             throw new ArgumentNullException("fileStream");
         }
         this.FileStream = fileStream;
    }

    #endregion

    #region Overriden Methods

    ///
    /// WriteFile
    /// Writes the file to the response. (Overrides FileResult.WriteFile(HttpResponseBase).)
    protected override void WriteFile(HttpResponseBase response)
    {
         Stream outputStream = response.OutputStream;
         using (this.FileStream)
         {
              byte[] buffer = new byte[0x1000];
              while (true)
              {
                   int count = this.FileStream.Read(buffer, 0, 0x1000);
                   if (count == 0)
                   {
                       return;
                   }
                   outputStream.Write(buffer, 0, count);
              }
         }
     }

    #endregion

     ///
     /// FileContents
     /// Gets the stream that will be sent to the response.
     public Stream FileStream { get; private set; }
}

PdfPathResult

This implementation class return the Pdf document from a physical Pdf file on the server.


///
/// PdfPathResult
/// Sends the contents of a file to the response.
public class PdfPathResult : PdfResult
{
    #region Constructor

    ///
    /// PdfContentResult
    /// Initializes a new instance of the FilePathResult class by using the specified file name and content type.
    ///
    public PdfPathResult(string fileName)
         : base()
    {
        if (string.IsNullOrEmpty(fileName))
        {
            throw new ArgumentException("Value cannot be null or empty.", "fileName");
        }
        this.FileName = fileName;
    }

    #endregion

    #region Overriden Methods

     ///
     /// WriteFile
     /// Writes the file to the response. (Overrides FileResult.WriteFile(HttpResponseBase).)
     protected override void WriteFile(HttpResponseBase response)
     {
          response.TransmitFile(this.FileName);
     }

     #endregion

     ///
     /// FileContents
     /// Gets the path of the file that is sent to the response.
     public string FileName { get; private set; }
  }

PdfContentResult

This implementation returns the Pdf document out of binary content of Pdf document.


///
/// PdfContentResult
/// Sends the contents of a binary file to the response.
///
public class PdfContentResult : PdfResult
{
    #region Constructor

    ///
    /// PdfContentResult
    /// Initializes a new instance of the PdfContentResult class by using the specified file contents and content type.
    ///
    public PdfContentResult(byte[] fileContents)
        : base()
    {
        if (fileContents == null)
        {
            throw new ArgumentNullException("fileContents");
        }
        this.FileContents = fileContents;
    }

    #endregion

    #region Overriden Methods

    ///
    /// WriteFile
    /// Writes the file content to the response. (Overrides FileResult.WriteFile(HttpResponseBase).)
    protected override void WriteFile(HttpResponseBase response)
    {
      response.OutputStream.Write(this.FileContents, 0, this.FileContents.Length);
    }

    #endregion

    ///
    /// FileContents
    /// The binary content to send to the response.
    public byte[] FileContents { get; private set; }
  }

Controller Extensions

I needed to write these extension to have the calling code look similar to ViewResult or other result types.


///
/// Defines extensions for controllers
public static class ControllerExtensions
{
   #region Pdf Extension method for Controller

   ///
   /// Pdf
   /// Creates a PdfContentResult object by using the file contents, content type, and the destination file name.
   /// The Controller
   /// The binary content to send to the response.
   /// The file name to use in the file-download dialog box that is displayed in the browser.
   /// The file-content result object.
   public static PdfResult Pdf(this Controller controller, byte[] fileContents, string fileDownloadName)
   {
      return new PdfContentResult(fileContents) { FileDownloadName = fileDownloadName };
   }

   ///
   /// Pdf
   /// Creates a PdfStreamResult object using the Stream object, the content type, and the target file name.
   /// The Controller
   /// The stream to send to the response.
   /// The file name to use in the file-download dialog box that is displayed in the browser.
   /// The file-stream result object.
   public static PdfResult Pdf(this Controller controller, Stream fileStream, string fileDownloadName)
   {
       return new PdfStreamResult(fileStream) { FileDownloadName = fileDownloadName };
   }

   ///
   /// Pdf
   /// Creates a PdfPathResult object by using the file name, the content type, and the file download name.
   /// The Controller
   /// The path of the file to send to the response.
   /// The file name to use in the file-download dialog box that is displayed in the browser.
   /// The file-stream result object.
   public static PdfResult Pdf(this Controller controller, string fileName, string fileDownloadName)
   {
        return new PdfPathResult(fileName) { FileDownloadName = fileDownloadName };
   }

   #endregion
}

Example

PdfStreamResult

public PdfResult DownloadPdf()
{
   Stream fileStream = ...; //initialization code to initialize the stream with Pdf File.
   return this.Pdf(fileStream, "Sample.pdf");
}

PdfPathResult


public PdfResult DownloadPdf()
{
    string filePath = ; //initialization code to initialize the string with Pdf File Path.
    return this.Pdf(filePath, "Sample.pdf");
}

PdfContentResult


public PdfResult DownloadPdfContent()
{
    string filePath = ; //initialization code to initialize the string with Pdf File Path.
    byte[] pdfBytes = System.IO.File.ReadAllBytes(filePath);
    return this.Pdf(pdfBytes, "sample.pdf");
    return pdfContent;
}
Advertisements

About cprakash

A developer, passionate about .Net, Architecture and Security.
This entry was posted in MVC and tagged , . Bookmark the permalink.

13 Responses to A simple Pdf ActionResult in MVC

  1. benny says:

    excellent! thanks a lot

  2. Jeremy Noble says:

    After over a year, still an excellent post – high quality code that works in MVC 5 to pull pdfs as a stream from an Azure cloud service.

    Thanks!

  3. Pascal Euroedi says:

    Thank you, it works perfectly!

    How can you open the PDF file directly in the browser with the binary content sent to the response?

    • cprakash says:

      It can be achieved by setting the response header Content-Disposition to inline.

      Response.AppendHeader("Content-Disposition", "inline; filename=file.pdf");

      The best place to write this line would be ExecuteResult method but my implementation is using the default, feel free to override and extend it.

      • Robert Benyi says:

        More exactly you need to overwrite the FileResult class ExecuteResult method in your PdfResult class:

        public override void ExecuteResult(ControllerContext context)
        {
        if (context == null)
        {
        throw new ArgumentNullException(“context”);
        }
        HttpResponseBase response = context.HttpContext.Response;
        response.ContentType = this.ContentType;
        if (!string.IsNullOrEmpty(this.FileDownloadName))
        {
        //string headerValue = ContentDispositionUtil.GetHeaderValue(this.FileDownloadName);
        //context.HttpContext.Response.AddHeader(“Content-Disposition”, headerValue);
        context.HttpContext.Response.AppendHeader(“Content-Disposition”, “inline; filename=” + this.FileDownloadName);
        }
        this.WriteFile(response);
        }

    • Pascal Euroedi says:

      Thanks to all 🙂

  4. shankar says:

    blank page open when use response.addheader

  5. Tiago says:

    You can open the PDF in a new window or tab? I searched all over the internet, tested several methods, but did not get a way that works directly return to view, only using a second call of a button or link.

    • cprakash says:

      Opening in new window/tab is client functionality and any of the server side code will not have control on that. You can use target attribute on the anchor tag to invoke the Url in new window and that should work. Look at the previous comment to open the PDF in as inline window rather than downloading as file.

  6. Puneet says:

    I get this issue (Output stream is not avaialble when the text writer is used) in PdfStreamResults.cs. The actual line that throws the error is Stream outputStream = response.OutputStream;

    Have you seen this issue?

    Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s