如何在.NET MVC3中拦截当前ActionResult的输出流?

5

你好,感谢查看!

背景

我正在使用Rotativa PDF工具将视图(HTML)读取为PDF。它运行良好,但是它并没有本地提供一种将PDF保存到文件系统的方法。相反,它只将文件作为操作结果返回给用户的浏览器。

这是代码示例:

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;

        } 

这段代码调用了另一个ActionResult("Quote"),将其视图转换为PDF格式并将生成的PDF文件作为下载文件返回给用户。
问题:如何拦截文件流并将PDF保存到我的文件系统中?虽然将PDF发送给用户很完美,但我的客户同时也希望将PDF保存到文件系统中。
有什么想法吗?
谢谢!
Matt
6个回答

3

我有同样的问题,这是我的解决方案:

你需要基本上向自己的URL发出HTTP请求,并将输出保存为二进制文件。简单明了,没有过载、辅助类和臃肿的代码。

你需要这个方法:

    // 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
        { }
    }

然后在你的控制器中,你可以这样调用它:

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

看,文件已经出现在你的文件系统中了,你可以双击打开PDF文件,或者发邮件给用户,或者进行其他需要操作。


1
文件保存后损坏了,你有什么想法是为什么吗? - Ibrahim Amer

1
 return new Rotativa.ActionAsPdf("ConvertIntoPdf") 
 { 
     FileName = "Test.pdf", PageSize = Rotativa.Options.Size.Letter
 };

0
我成功地使用了Eric Brown - Cal的解决方案,但我需要进行一些小调整以防止出现“找不到目录”的错误。
(此外,查看Rotativa代码,似乎-q开关已经默认传递,因此可能不是必需的,但我没有更改。)
var bytes = Rotativa.WkhtmltopdfDriver.ConvertHtml(Server.MapPath(@"/Rotativa"), "-q", html);

0

你也可以使用Rotativa来完成,这实际上非常容易。

Using Rotativa;

...

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

File.WriteAllBytes( outputPath, pdfByteArray );

我正在一个WinForms应用程序中使用它,以从我们在Web应用程序中使用的Razor视图生成和保存PDF文件。

请注意,如果您先显示PDF,然后再执行此操作,将会渲染两次。 - Eric Brown - Cal
1
为什么要点踩?如果你不告诉我原因,我就无法改进。我知道这段代码是可行的,它已经在生产环境中了。 - Eric Brown - Cal
感谢您的回答,Brown先生。我想知道ConvertHtml函数需要哪些参数,因为我无法弄清楚,或者您可以直接链接给我它的文档。 - Ibrahim Amer
我把名字(Rotativa.WkhtmltopdfDriver.ConvertHtml)粘贴到了谷歌上,这是第一个结果:(也许你应该试试谷歌搜索?) https://github.com/webgio/Rotativa/blob/master/Rotativa/WkhtmltopdfDriver.cs - Eric Brown - Cal
还是无法理解它。第三个参数指的是什么? - Ibrahim Amer

0

我成功地使用了Aaron的“SaveHttpResponseAsFile”方法,但是我不得不对其进行修改,因为当前登录用户的凭据未应用(因此它会转发到MVC4的登录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>

然后在您的调用控制器方法中,只需将'Request'添加到SaveHttpResponseAsFile调用中。


0

问题在于该库直接将内容写入 OutputStream,我认为你无法捕获它? - Steve Czetty
我不确定,我希望OutputStream不会直接发送到客户端,而是交给ActionFilters处理。我编辑了答案,添加了一个示例代码,使用上述ActionFilter拦截响应作为Stream。您可以轻松检查Stream是否包含您期望的数据。 - Roman
在下面的SO问题中,答案提供了一个更简单的例子: http://stackoverflow.com/questions/5424346/how-can-i-log-both-the-request-inputstream-and-response-outputstream-traffic-in - Roman
谢谢你,Roman。我得想办法实现这段代码,但你已经给了我一个很好的开端。再次感谢。 - Matt Cashatt
再次感谢你Roman,但我遇到了一些麻烦。你认为"OnResultExecuted"重写应该放在哪里?我把它放在了我的控制器中,但它没有被调用。谢谢! - Matt Cashatt
你必须编写你的动作过滤器,并将其作为属性添加到你的控制器方法中。 - Kirill Bestemyanov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接