如何将ASP.NET MVC视图呈现为PDF格式

27

我正在使用ExpertPDF的Html-to-PDF转换工具来完成这个问题(尽管如果有足够的文档,我也可以考虑其他库)。

简而言之,我有一个以特定方式格式化的视图,并希望将其呈现为用户可以保存到磁盘的PDF文档。

到目前为止,我已经拥有了一个PrintService(它实现了IPrintService接口),这个实现有两个PrintToPDF()重载方法,一个只需要URL,另一个需要HTML字符串,两者都返回byte[]。 我只解决了需要HTML字符串的第二个重载的细节。

我想从我的控制器中做的是:

public FileStreamResult Print(int id)
{
    var model = _CustomRepository.Get(id);
    string renderedView = SomethingThatRendersMyViewAsAString(model);
    Stream byteStream = _PrintService.PrintToPdf(renderedView);
    HttpContext.Response.AddHeader("content-disposition", 
        "attachment; filename=report.pdf");
    return new FileStreamResult(byteStream, "application/pdf");  
}

理论上可以将PDF呈现在页面中,但我需要帮助的是"SomethingThatRendersMyViewAsAString"。是否有一种快速获取View字符串表示形式的方法?或者我应该坚持使用URL重载,并将URL传递给View...还有其他想法吗?

谢谢!


6
曼尼什 - 你有没有在任何地方发布你的解决方案?这将非常有用。谢谢提前。 - jim tollan
4个回答

103

我将我的解决方案打包成了一个Nuget软件包:Rotativa http://nuget.org/packages/Rotativa。它基于wkhtmltopdf。

使用非常简单。

如果你想要将一个Action服务作为PDF而不是HTML页面,你可以定义一个返回ActionResult类型的ActionAsPdf(也可使用RouteAsPdf)的操作。 因此,代码仅仅是:

public ActionResult PrintIndex()
{
    return new ActionAsPdf("Index", new { name = "Giorgio" }) { FileName = "Test.pdf" };
}

假设路由参数名为"name=Giorgio"。

即使打印的操作受Web表单身份验证保护([Authorize]属性),它也能正常工作。


5
这绝对是我在asp.net mvc上遇到的最容易的解决方案,非常感谢... - Hector Minaya
2
太棒了 - 谢谢!我要注意一下,我必须指定表单身份验证 cookie 名称(否则登录页面将被呈现),并更改渲染器以使用打印媒体样式(FormsAuthenticationCookieName =“MyCookie”,CustomSwitches =“--print-media-type”)。非常容易! - Mark Carpenter
1
Rotativa 在 MVC4 中似乎无法正常工作。当我返回一个 ActionAsPdf 时,屏幕上显示的是一个字符串,而不是 PDF 文件。 - detay
3
在 Azure Web 角色中运行正常,但在 Azure Web 站点上无法正常工作,因为受信任级别的限制。 - Giorgio Bozio
4
@GuruprasadRao,这导致PDF上出现了登录页面。 - SAR
显示剩余21条评论

10

你可以在OnResultExecuting期间访问响应并替换过滤器属性,将结果HTML存储在MemoryStream中的某些内容。然后你可以在OnResultExecuted期间清空响应,并用PDF转换的结果替换它。但我不确定这是否比仅从URL获取HTML更好。

 public FileStreamResult Print(int id)
 {
     var model = _CustomRepository.Get(id);
     this.ConvertToPDF = true;
     return View( "HtmlView" );
 }

 public override OnResultExecuting( ResultExecutingContext context )
 {
      if (this.ConvertToPDF)
      {
          this.PDFStream = new MemoryStream();
          context.HttpContext.Response.Filter = new PDFStreamFilter( this.PDFStream );
      }
 }

 public override OnResultExecuted( ResultExecutedContext context )
 {
      if (this.ConvertToPDF)
      {
          context.HttpContext.Response.Clear();
          this.PDFStream.Seek( 0, SeekOrigin.Begin );
          Stream byteStream = _PrintService.PrintToPDF( this.PDFStream );
          StreamReader reader = new StreamReader( byteStream );
          context.HttpContext.Response.AddHeader( "content-disposition",
                 "attachment; filename=report.pdf" );
          context.HttpContext.Response.AddHeader( "content-type",
                 "application/pdf" );
          context.HttpContext.Response.Write( reader.ReadToEnd() );
      }
}
PDFStreamFilter需要重写“Write”方法,并将数据发送到内存流中。

我已经想出了一个解决方案,代码更少,但你提供的答案也是一个潜在的解决方案。拦截结果执行是个好主意... 我很快会发布我的解决方案。 - nkirkes

1

这听起来像是我曾经遇到的一个类似的问题,我想要使用Views作为电子邮件模板。我发现获取View的字符串表示的最佳答案在这里:将视图呈现为字符串


我看到了那篇帖子。最终,我采用了一个类似的解决方案,基于brightmix帖子上的评论。我很快就会发布解决方案。 - nkirkes

0

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