前言:
最近在做老师交代的一个在线写实验报告的小项目中,有这么个需求:把学生提交的实验报告(HTML形式)直接转成PDF,方便下载和打印。
以前都是直接用rdlc报表实现的,可这次牵扯到图片,并且更为重要的一点是 PDF的格式得跟学生提交的HMTL页面一样。经过网上查阅资料,
找到了ITextSharp插件。
ITextSharp很强大,但是在处理HMTL中的 img标签时,src中只能是绝对路径。
解决方法我写在了另一篇文章中
正文:
ITextSharp就不多介绍了。项目的链接下载链接为http://files.cnblogs.com/files/zuochengsi-9/H%E8%BD%ACPDF.zip
下开始项目之前得添加 ITextSharp.dll和ITextSharp.xmlworker.dll 后者是解决中文用的
可以从NuGet中下载引用,具体方法就不介绍了。网上很多解决方案。
项目结构图:
下面先说下主要操作:
步骤:1、将本地的某个视图转成字符串。
2、将字符串整合成PDF的文档,并返回byte数组。
3、讲比特流写到HTTP内容主体的二进制流中去。
视图转字符串代码:
首先新建两个类,转字符串的逻辑主要在RenderViewToString方法中。
public class HtmlViewRenderer { public string RenderViewToString(Controller controller, string viewName, object viewData) { var renderedView = new StringBuilder(); using (var responseWriter = new StringWriter(renderedView)) { var fakeResponse = new HttpResponse(responseWriter); var fakeContext = new HttpContext(HttpContext.Current.Request, fakeResponse); var fakeControllerContext = new ControllerContext(new HttpContextWrapper(fakeContext), controller.ControllerContext.RouteData, controller.ControllerContext.Controller); var oldContext = HttpContext.Current; HttpContext.Current = fakeContext; using (var viewPage = new ViewPage()) { var html = new HtmlHelper(CreateViewContext(responseWriter, fakeControllerContext), viewPage); html.RenderPartial(viewName,viewData); HttpContext.Current = oldContext; } } return renderedView.ToString(); } private static ViewContext CreateViewContext(TextWriter responseWriter, ControllerContext fakeControllerContext) { return new ViewContext(fakeControllerContext, new FakeView(), new ViewDataDictionary(), new TempDataDictionary(), responseWriter); } }
public class FakeView : IView { public void Render(ViewContext viewContext, TextWriter writer) { throw new NotImplementedException(); } }
再新建一个控制器,调用刚刚写好的RenderViewToString方法。(后面会再新建一个HomeController,继承这个PdfViewController,再在HomeController的Action里调用ViewPdf就行)
public class PdfViewController : Controller { private readonly HtmlViewRenderer htmlViewRenderer; public PdfViewController() { this.htmlViewRenderer = new HtmlViewRenderer(); } protected string ViewPdf(string viewName,object model) { // Render the view html to a string. string htmlText = this.htmlViewRenderer.RenderViewToString(this, viewName,model); return htmlText; } }
"字符串转byte[]" (这个方法放在后面写的HomeController中)
public byte[] ConvertHtmlTextToPDF(string htmlText) { if (string.IsNullOrEmpty(htmlText)) { return null; } //避免當htmlText無任何html tag標籤的純文字時,轉PDF時會掛掉,所以一律加上<p>標籤 htmlText = "<p>" + htmlText + "</p>"; MemoryStream outputStream = new MemoryStream();//要把PDF寫到哪個串流 byte[] data = Encoding.UTF8.GetBytes(htmlText);//字串轉成byte[] MemoryStream msInput = new MemoryStream(data); Document doc = new Document();//要寫PDF的文件,建構子沒填的話預設直式A4 PdfWriter writer = PdfWriter.GetInstance(doc, outputStream); //指定文件預設開檔時的縮放為100% PdfDestination pdfDest = new PdfDestination(PdfDestination.XYZ, 0, doc.PageSize.Height, 1f); //開啟Document文件 doc.Open(); //使用XMLWorkerHelper把Html parse到PDF檔裡 XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msInput, null, Encoding.UTF8, new UnicodeFontFactory()); //將pdfDest設定的資料寫到PDF檔 PdfAction action = PdfAction.GotoLocalPage(1, pdfDest, writer); writer.SetOpenAction(action); doc.Close(); msInput.Close(); outputStream.Close(); //回傳PDF檔案 return outputStream.ToArray(); }
其中XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msInput, null, Encoding.UTF8, new UnicodeFontFactory());这段代码中的“UnicodeFontFactory”类,封装了中文字体的设置。代码如下:
public class UnicodeFontFactory : FontFactoryImp { private static readonly string arialFontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "arialuni.ttf");//arial unicode MS是完整的unicode字型。 private static readonly string 標楷體Path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "KAIU.TTF");//標楷體 public override Font GetFont(string fontname, string encoding, bool embedded, float size, int style, BaseColor color, bool cached) { //可用Arial或標楷體,自己選一個 BaseFont baseFont = BaseFont.CreateFont(標楷體Path, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); return new Font(baseFont, size, style, color); } }
再新建一个类,用来将比特流输出到response.OutputStream中:
public class BinaryContentResult : ActionResult { private readonly string contentType; private readonly byte[] contentBytes; public BinaryContentResult(byte[] contentBytes, string contentType) { this.contentBytes = contentBytes; this.contentType = contentType; } public override void ExecuteResult(ControllerContext context) { var response = context.HttpContext.Response; response.Clear(); response.Cache.SetCacheability(HttpCacheability.Public); response.ContentType = this.contentType; //下面这段加上就是一个下载页面 response.AppendHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode("文件名.pdf", System.Text.Encoding.UTF8)); using (var stream = new MemoryStream(this.contentBytes)) { stream.WriteTo(response.OutputStream); stream.Flush(); } } }
现在来看一下HomeController
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Web; using System.Web.Mvc; using HTML转PDF.Models; using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.tool.xml; using ReportManagement; namespace HTML转PDF.Controllers { public class HomeController :PdfViewController { // // GET: /Home/ public ActionResult Index() { return View(); } /// <summary> /// 執行此Url,下載PDF檔案 /// </summary> /// <returns></returns> public ActionResult DownloadPdf() { var person = new People("左成"); string htmlText = this.ViewPdf("Preview",person); byte[] pdfFile = this.ConvertHtmlTextToPDF(htmlText); return new BinaryContentResult(pdfFile, "application/pdf"); } /// <summary> /// 將Html文字 輸出到PDF檔裡 /// </summary> /// <param name="htmlText"></param> /// <returns></returns> public byte[] ConvertHtmlTextToPDF(string htmlText) { if (string.IsNullOrEmpty(htmlText)) { return null; } //避免當htmlText無任何html tag標籤的純文字時,轉PDF時會掛掉,所以一律加上<p>標籤 htmlText = "<p>" + htmlText + "</p>"; MemoryStream outputStream = new MemoryStream();//要把PDF寫到哪個串流 byte[] data = Encoding.UTF8.GetBytes(htmlText);//字串轉成byte[] MemoryStream msInput = new MemoryStream(data); Document doc = new Document();//要寫PDF的文件,建構子沒填的話預設直式A4 PdfWriter writer = PdfWriter.GetInstance(doc, outputStream); //指定文件預設開檔時的縮放為100% PdfDestination pdfDest = new PdfDestination(PdfDestination.XYZ, 0, doc.PageSize.Height, 1f); //開啟Document文件 doc.Open(); //使用XMLWorkerHelper把Html parse到PDF檔裡 XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msInput, null, Encoding.UTF8, new UnicodeFontFactory()); //將pdfDest設定的資料寫到PDF檔 PdfAction action = PdfAction.GotoLocalPage(1, pdfDest, writer); writer.SetOpenAction(action); doc.Close(); msInput.Close(); outputStream.Close(); //回傳PDF檔案 return outputStream.ToArray(); } } }
步骤二的ConvertHtmlTextToPDF方法,我就直接放在了这里面,没管那些设计原则了。上面代码中“Preview”是本地的一个视图,最好和HomeController在一个区域里面,不然得改步骤一的代码。
最后是我的前台部分的代码,写的很简单(Preview.cshtml)。
1 2 3 4 |
|
特别注意:关于跳转到我们写的“DownloadPdf”Action时,千万不要用ajax.ActionLink(坑了我好久)。不然会出现乱码,我也没找到解决方案。
参考资料:http://www.tuicool.com/articles/aUfqeu
http://dwtedx.com/itshare_233.html
转载请注明出处:http://www.cnblogs.com/zuochengsi-9/p/5483808.html