5

Hi and thanks for looking!

Background

I am using the Rotativa pdf tool to read a view (html) into a PDF. It works great, but it does not natively offer a way to save the PDF to a file system. Rather, it only returns the file to the user's browser as a result of the action.

Here is what that code looks like:

public ActionResult PrintQuote(FormCollection fc)
        {
            int revisionId = Int32.Parse(Request.QueryString["RevisionId"]);

            var pdf = new ActionAsPdf(
                 "Quote",
                 new { revisionId = revisionId })
                       {
                           FileName = "Quote--" + revisionId.ToString() + ".pdf",
                           PageSize = Rotativa.Options.Size.Letter
                       };

            return pdf;

        } 

This code is calling up another actionresult ("Quote"), converting it's view to a PDF, and then returning the PDF as a file download to the user.

Question

How do I intercept the file stream and save the PDF to my file system. It is perfect that the PDF is sent to the user, but my client also wants the PDF saved to the file system simultaneously.

Any ideas?

Thanks!

Matt

Community
  • 1
  • 1
Matt Cashatt
  • 23,490
  • 28
  • 78
  • 111

6 Answers6

3

I have the same problem, here's my solution:

You need to basically make an HTTP request to your own URL and save the output as a binary file. Simple, no overload, helper classes, and bloated code.

You'll need this method:

    // Returns the results of fetching the requested HTML page.
    public static void SaveHttpResponseAsFile(string RequestUrl, string FilePath)
    {
        try
        {
            HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(RequestUrl);
            httpRequest.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";
            httpRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
            HttpWebResponse response = null;
            try
            {
                response = (HttpWebResponse)httpRequest.GetResponse();
            }
            catch (System.Net.WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError)
                    response = (HttpWebResponse)ex.Response;
            }

            using (Stream responseStream = response.GetResponseStream())
            {
                Stream FinalStream = responseStream;
                if (response.ContentEncoding.ToLower().Contains("gzip"))
                    FinalStream = new GZipStream(FinalStream, CompressionMode.Decompress);
                else if (response.ContentEncoding.ToLower().Contains("deflate"))
                    FinalStream = new DeflateStream(FinalStream, CompressionMode.Decompress);

                using (var fileStream = System.IO.File.Create(FilePath))
                {
                    FinalStream.CopyTo(fileStream);
                }

                response.Close();
                FinalStream.Close();
            }
        }
        catch
        { }
    }

Then inside your controller, you call it like this:

SaveHttpResponseAsFile("http://localhost:52515/Management/ViewPDFInvoice/" + ID.ToString(), "C:\\temp\\test.pdf");

And voilà! The file is there on your file system and you can double click and open the PDF, or email it to your users, or whatever you need.

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Aaron
  • 1,802
  • 3
  • 23
  • 50
1
 return new Rotativa.ActionAsPdf("ConvertIntoPdf") 
 { 
     FileName = "Test.pdf", PageSize = Rotativa.Options.Size.Letter
 };
Manjunath Ballur
  • 6,287
  • 3
  • 37
  • 48
0

Take a look at the MVC pipeline diagram here: http://www.simple-talk.com/content/file.ashx?file=6068

The method OnResultExecuted() is called after the ActionResult is rendered.

You can override this method or use an ActionFilter to apply and OnResultExecuted interceptor using an attribute.

Edit: At the end of this forum thread you will find a reply which gives an example of an ActionFilter which reads (and changes) the response stream of an action. You can then copy the stream to a file, in addition to returning it to your client.

Roman
  • 957
  • 8
  • 17
  • The problem is that the library is writing directly to the OutputStream, I don't think you can capture that? – Steve Czetty Aug 07 '12 at 22:25
  • I am not sure, I would expect the OutputStream not to be sent directly to the client, but rather given to the ActionFilters. I edited the answer to add an example code which intercepts the response as a Stream using the above-mentioned ActionFilter. You can easily check if the Stream contains the data you expect. – Roman Aug 07 '12 at 22:32
  • In the following SO question, the answer provides a simpler example: http://stackoverflow.com/questions/5424346/how-can-i-log-both-the-request-inputstream-and-response-outputstream-traffic-in – Roman Aug 07 '12 at 22:36
  • Thank you Roman. I am going to have to figure out how to implement this code, but you have given me a good start. Thanks again. – Matt Cashatt Aug 07 '12 at 23:09
  • Thanks again Roman, but I am having some trouble. Where were you thinking that the "OnResultExecuted" override should go? I placed it in my controller, but it is not being called. Thanks! – Matt Cashatt Aug 07 '12 at 23:35
  • You must write your action filter and add it to your controller method as attribute – Kirill Bestemyanov Aug 08 '12 at 04:54
0

I successfully used Aaron's 'SaveHttpResponseAsFile' method, but I had to alter it, as the currently logged in user's credentials weren't applied (and so it was forwarding to MVC4's login url).

public static void SaveHttpResponseAsFile(System.Web.HttpRequestBase requestBase, string requestUrl, string saveFilePath)
{
    try
    {
        *snip*
        httpRequest.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
        httpRequest.Headers.Add(HttpRequestHeader.Cookie, requestBase.Headers["Cookie"]);
        *snip*</pre></code>

Then in your calling Controller method, simply add 'Request' into the SaveHttpResponseAsFile call.

David
  • 86
  • 1
  • 3
0

You can also do it using Rotativa, which is actually quite easy.

Using Rotativa;

...

byte[] pdfByteArray = Rotativa.WkhtmltopdfDriver.ConvertHtml( "Rotativa", "-q", stringHtmlResult );

File.WriteAllBytes( outputPath, pdfByteArray );

I'm using this in a winforms app, to generate and save the PDFs from Razor Views we also use in our web apps.

Eric Brown - Cal
  • 14,135
  • 12
  • 58
  • 97
  • Please Be aware, if you display the PDF and then do this, you'll be rendering it twice. – Eric Brown - Cal Apr 15 '14 at 16:40
  • 1
    Why the down vote? I can't get better if you don't tell me why. I know the code works, it's in production now. – Eric Brown - Cal Jul 02 '14 at 18:04
  • Brown Thank you sir for this answer, I want to know what is parameters that ConvertHtml take, because I can't figure it out, or you just link me to its documentation – Ibrahim Amer Aug 24 '15 at 11:48
  • I pasted the name (Rotativa.WkhtmltopdfDriver.ConvertHtml) into Google, this is the first result: (Maybe you should try Googling?) https://github.com/webgio/Rotativa/blob/master/Rotativa/WkhtmltopdfDriver.cs – Eric Brown - Cal Aug 25 '15 at 14:57
  • Still couldn't get an intuition about it. What does the third parameter refer to ? – Ibrahim Amer Aug 25 '15 at 15:04
0

I was able to get Eric Brown - Cal 's solution to work, but I needed a small tweak to prevent an error I was getting about the directory not being found.

(Also, looking at the Rotativa code, it looks like the -q switch is already being passed by default, so that might not be necessary, but I didn't change it.)

var bytes = Rotativa.WkhtmltopdfDriver.ConvertHtml(Server.MapPath(@"/Rotativa"), "-q", html);
Greg
  • 2,410
  • 21
  • 26