ASP.Net MVC——使用 ITextSharp 完美解决HTML转PDF(中文也可以)

前言:

最近在做老师交代的一个在线写实验报告的小项目中,有这么个需求:把学生提交的实验报告(HTML形式)直接转成PDF,方便下载和打印。

以前都是直接用rdlc报表实现的,可这次牵扯到图片,并且更为重要的一点是 PDF的格式得跟学生提交的HMTL页面一样。经过网上查阅资料,

找到了ITextSharp插件。

ITextSharp很强大,但是在处理HMTL中的 img标签时,src中只能是绝对路径。 

解决方法我写在了另一篇文章中

正文:

ITextSharp就不多介绍了。项目的链接下载链接为https://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)。

@model HTML转PDF.Models.People
  <!-- 下面是我机器上的绝对路径  -->         
<img src="E:\12.bmp" width="64" height="64" /> 
<p>大家好,我叫"@Model.Name"</p>

  

特别注意:关于跳转到我们写的“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

 

posted @ 2016-05-11 22:19  leftcity  阅读(21227)  评论(6编辑  收藏  举报