How do I intercept the output stream of the current actionresult in .NET MVC3?
Asked Answered
I

6

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

Inflate answered 7/8, 2012 at 21:52 Comment(0)
S
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.

Squabble answered 3/3, 2013 at 1:39 Comment(1)
The file corrupted after saving it, do you have any idea why is that ?Lignify
L
1
 return new Rotativa.ActionAsPdf("ConvertIntoPdf") 
 { 
     FileName = "Test.pdf", PageSize = Rotativa.Options.Size.Letter
 };
Lineate answered 18/1, 2016 at 7:29 Comment(0)
A
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.

Amazonas answered 7/8, 2012 at 22:22 Comment(6)
The problem is that the library is writing directly to the OutputStream, I don't think you can capture that?Intent
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.Amazonas
In the following SO question, the answer provides a simpler example: #5424846Amazonas
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.Inflate
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!Inflate
You must write your action filter and add it to your controller method as attributeSaltandpepper
A
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.

Assessor answered 30/10, 2013 at 14:23 Comment(0)
N
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.

Nonagenarian answered 15/4, 2014 at 15:33 Comment(5)
Please Be aware, if you display the PDF and then do this, you'll be rendering it twice.Nonagenarian
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.Nonagenarian
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 documentationLignify
I pasted the name (Rotativa.WkhtmltopdfDriver.ConvertHtml) into Google, this is the first result: (Maybe you should try Googling?) github.com/webgio/Rotativa/blob/master/Rotativa/…Nonagenarian
Still couldn't get an intuition about it. What does the third parameter refer to ?Lignify
E
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);
Edda answered 2/11, 2015 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.