VS 2017 C Sharp and PDF Generator

 VS 2017 C# with PDF generator

In a real application system, most of time, you have to generate pdf for your users. For example, in the ERP, you may create invoice pdf and sales report pdf.  This tutorial will instruct you how to create a pdf with MVC framework in VS 2017 C# platform.

I will illustrate on implementation from simple pdf to complex pdf

Create the PDF generate Environment:

  1. Add the ITextSharp package into your project

You have to install the iTextSharp-LGPL 4.1.6 version to avoid the license issue. This is the latest version that free for the community. For more detail, you can check the web page here, https://www.nuget.org/packages/iTextSharp-LGPL/.

You can install it via command line below in the Package Manager Console:

Install-Package iTextSharp-LGPL -Version 4.1.6

  1. In your test controller, import the pdf API. Here we will name our test controller InvoicesController

using iTextSharp.text;

using iTextSharp.text.pdf;

Generate a Simple PDF for a glimpse test:

  1. A Very Simple PDF demo

  1. Create the model to store the simple pdf data

In the MVC world, you need to have the model data before you can view the data. PDF is actually a kind of view. As this is a simple data for view without store in db, we will create view model instead of entity model for this test.

 namespace GigHub.ViewModels

{

    public class PostTest

    {

        public String Title;

        public DateTime PublishedOn;

        public String Abstract;

        public String Description;

    }

}

The PostTest instance will use to keep the data which show in the pdf.

  1. Create the view content of the PDF with cshtml file testPDFView1.cshtml. You can see that all fields shown in the ViewModels.PostTest are used in the view below.

@model GigHub.ViewModels.PostTest

<html>

<head>

    <title>@Model.Title</title>

</head>

<body>

    <h2>@Model.Title</h2>

    @Model.PublishedOn.ToString()

    <h3>@Model.Abstract</h3>

@{

    var Description = Model.Description;

}

    @Description

    <div>

        <div>

            that is greate

        </div>

        <a href=”hellow”>hello</a>

        <input type=”text” width=”20px” height=”30px” />

        <textarea rows=”10″>hasdfasdfl;ajsdl;f</textarea>

    </div>

</body>

</html>

  1. Create a helper class PDFGenerator to generate PDF

namespace GigHub.helper

{

    public class PDFGenerator

    {

        private readonly PostTest _post;

        private readonly InvoicePDFModel _ipost;

        private readonly string _file;

        public PDFGenerator(PostTest post, string file)

        {

            _post = post;

            _file = file;

        }

        public PDFGenerator(InvoicePDFModel post, string file)

        {

            _ipost = post;

            _file = file;

        }

        public FileContentResult GetPdf()

        {

            var html = GetHtml();

            Byte[] bytes;

            using (var ms = new MemoryStream())

            {

                using (var doc = new Document())

                {

                    using (var writer = PdfWriter.GetInstance(doc, ms))

                    {

                        doc.Open();

                        try

                        {

                            using (var msHtml = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(html)))

                            {

                                iTextSharp.tool.xml.XMLWorkerHelper.GetInstance()

                                    .ParseXHtml(writer, doc, msHtml, System.Text.Encoding.UTF8);

                            }

                        }

                        finally

                        {

                            doc.Close();

                        }

                    }

                }

                bytes = ms.ToArray();

            }

            return new FileContentResult(bytes, “application/pdf”);

        }

      }

},

  1. In the Controller, set the view model data and generate the pdf

        public ActionResult GetPdf(string code)

        {

            PostTest post = new PostTest();

            post.Title = “Test PDF from form”;

            post.PublishedOn = DateTime.Now;

            post.Abstract = “good to see”;

            post.Description = “hi, first pdf from form”;

           

            var builder = new PDFGenerator(post, Server.MapPath(“~/Views/Invoices/testPDFView1.cshtml”));

            return builder.GetPdf();

        }

  1. That is it. Run http://localhost:50299/Invoices/GetPdf in the browser. You will see the pdf file generated and shown in the browser directly.
  1. Ok, if you want to download the file instead of view it online.

First, you may add below function in the controller.

        public FileResult GetPdfSave(string fileName = null)

        {

            PostTest post = new PostTest();

            post.Title = “Test PDF from form”;

            post.PublishedOn = DateTime.Now;

            post.Abstract = “good to see”;

            post.Description = “hi, first pdf from form”;

            var builder = new PDFGenerator(post, Server.MapPath(“~/Views/Invoices/testPDFView1.cshtml”));

            string physicalFile = Server.MapPath(“~/Documents/Invoice/” + fileName + “.pdf”);

            return File(builder.SavePdf(physicalFile), “application/pdf”, fileName + “.pdf”);

        }

 Then, you need to add below function in the helper.PDFGenerator class.

  public MemoryStream SavePdf(String fileFullPath)

        {

            MemoryStream ms = new MemoryStream();

            var html = GetHtml();

            using (var file = new FileStream(fileFullPath, FileMode.Create))

            {

                    using (var doc = new Document())

                    {

                        using (var writer = PdfWriter.GetInstance(doc, file))

                        {

                            doc.Open();

                            try

                            {

                                using (var msHtml = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(html)))

                                {

                                    iTextSharp.tool.xml.XMLWorkerHelper.GetInstance()

                                        .ParseXHtml(writer, doc, msHtml, System.Text.Encoding.UTF8);

                                }

                            }

                            finally

                            {

                                if (doc.IsOpen())

                                    doc.Close();

                            }

                        }

                    }

            }

            var file2 = new FileStream(fileFullPath, FileMode.Open);

            file2.CopyTo(ms);

            file2.Close();

            ms.Position = 0;

            return ms;

        }

Finally, run the http://localhost:50299/Invoices/SavePdf?fileName=test in browser, it will download the generated pdf.

Generate PDF with simple Master/Slave layout and CSS

In many times, you need to create a pdf to show the master/slave information. For example, a pdf displays the order info (such as order #, order date, etc) and the order line items detail. So, the info is the master data and the line items are the slave data. You may have complex label color and size defined in a CSS file for the pdf, sample pdf as below

  1. create the pdf.css file. Those style will be used in the view cshtml file.

.container1 {

    box-sizing: border-box;

    color: rgb(51, 51, 51);

    display: block;

    font-family: Lato, “Helvetica Neue”, Helvetica, Arial, sans-serif;

    font-size: 14px;

    height: 100%;

    line-height: 20px;

    margin-left: 0px;

    margin-right: 0px;

    max-width: 970px;

    padding-left: 0px;

    padding-right: 0px;

    text-size-adjust: 100%;

    width: 100%;

    -webkit-tap-highlight-color: rgba(0, 0, 0, 0)

}

label, dt {

    font-size: 12px;

    font-weight: bold;

}

.labelColon::after {

    content: “: “;

}

.tdValue {

    padding-left: 10px;

}

a.navbar-brand span {

    color: black;

    font-size: larger;

    margin: 5px;

}

.copyrightLabel {

    color: navy !important;

}

.navbar-brand {

    font-weight: 700;

    padding: 10px 5px;

}

a {

    color: #428bca;

    text-decoration: none;

}

.copyrightSymbol {

    font-size: smaller !important;

    color: navy !important;

}

h2 {

    font-size: 25px;

    font-weight: bold;

}

  1. create the view cshtml file. This will be similar as previous, but with style added and slave list item. Part of the cshtml is as below

<body>

        <table>

            <tr>

                <td>

                    <a class=”navbar-brand” href=”/”><img width=”76px” height=”70px” src=”http://localhost:50299/Content/images/logo.png” /><span class=”copyrightLabel”>xxxx<span class=”copyrightSymbol”>®</span></span></a>

…..

</td>

               <td colspan=”3″>

                    <table>

                        <tr>

                            <td style=”width:250px”>

                                <dt>

                                    From:

                                </dt>

                                <dd>

                                    <Label>

                                       @Model.From1<br />@Model.From2<br />@Model.From3<br />@Model.From4<br />@Model.From5<br />@Model.From6

                                    </Label>

                                </dd>

                            </td>

                            <td style=”width:230px”>

                                <table>

                                    <tr><td style=”height:20px”><label>EIN:</label></td><td><label>   @Model.EIN</label></td></tr>

                                    <tr><td style=”height:20px”><label>Contract#: </label></td><td><label>   @Model.ContractNumber</label></td></tr>

                                    <tr><td style=”height:20px”><label>Invoice#:</label></td><td><label>   @Model.InvoiceNumber</label></td></tr>

                                    <tr><td style=”height:20px”><label>Invoice Date:</label></td><td><label>   @Model.InvoiceDate</label></td></tr>

                                    <tr><td style=”height:20px”><label>Terms:</label></td><td><label>   @Model.Terms</label></td></tr>

                                </table>

                            </td>

                            <td style=”width:100px”></td>

                            <td style=”width:250px”>

                                <dt>

                                    TO:

                                </dt>

                                <dd>

                                    <Label>

                                        @Model.TO1<br />@Model.TO2<br />@Model.TO3<br />@Model.TO4<br />@Model.TO5

                                    </Label>

                                </dd>

                            </td>

                        </tr>

                    </table>

                </td>

            </tr>

….

<tr><td colspan=”3″>

               

                    <table sstyle=”font-size:17px”>

                        <tr>

                            <th style=”height:20px”>

                                <label>Date</label>

                            </th>

                            <th>

                                <label>Code</label>

                            </th>

                            <th>

                                <label>Description</label>

                            </th>

                            <th>

                                <label> Rate</label>

                            </th>

                            <th>

                                <label>Units</label>

                            </th>

                            <th>

                                <label>Cost</label>

                            </th>

                        </tr>

                        @foreach (var item in Model.items)

                        {

                            var aDate = item.ServiceDate;

                            char[] c = { ‘ ‘ };

                            var aDateStr = aDate.ToString().Split(c);

                            <tr>

                                <td style=”height:20px”>

                                    @aDateStr[0]

                                </td>

                                <td>

                                    @item.Service.Code

                                </td>

                                <td style=”width:150px”>

                                    @item.Service.Description

                                </td>

                                <td>

                                    $@item.Service.Rate

                                </td>

                                <td>

                                    @item.Hours

                                </td>

                                <td>

                                    @item.Cost

                                </td>

                            </tr>

                        }

                        <tr>

                            <td colspan=”3″  style=”height:60px”></td>

                            <td colspan=”2″ style=”text-align: right;“><label>Invoice Amount:</label></td>

                            <td colspan=”3″>@Model.Total</td>

                        </tr>

                    </table>

….

  1. create the controller, add below path function

        public FileResult InvoicePDF(int? id = 1) //gnote:Test for pdf generate from cshtml

        {

            var invoice = db.Invoices.Find(id);

            var invoicePDFModel = getInvoicePDFModel(invoice);

            var builder = new PDFGenerator(invoicePDFModel, Server.MapPath(“~/Views/Invoices/InvoicePDFView2.cshtml”));

            string physicalFile = Server.MapPath(“~/Documents/Invoice/” + invoice.InvoiceNumber + “.pdf”);

            return File(builder.SavePdfcss(physicalFile), “application/pdf”, invoice.InvoiceNumber + “.pdf”);

        }

  1. In the help.PDFGenerator, add below css pdf feature function

     public MemoryStream SavePdfcss(String fileFullPath,bool saveOnly=false)    

        {

            List<string> cssFiles = new List<string>();

            cssFiles.Add(@”/Content/pdf.css”);

            var htmlContext = new HtmlPipelineContext(null);

            htmlContext.SetTagFactory(iTextSharp.tool.xml.html.Tags.GetHtmlTagProcessorFactory());

            ICSSResolver cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);

            cssFiles.ForEach(i => cssResolver.AddCssFile(System.Web.HttpContext.Current.Server.MapPath(i), true));

            MemoryStream ms = new MemoryStream();

            var html = GetHtml();

            PdfTemplate headerTemplate, footerTemplate;

            PdfContentByte cb;

            var cf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);

            var bf = BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);

            using (var file = new FileStream(fileFullPath, FileMode.Create))

            {

                using (var doc = new Document(PageSize.A4))

                {

                    using (var writer = PdfWriter.GetInstance(doc, file))

                    {

                        doc.Open();

                        try

                        {

                            using (var msHtml = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(html)))

                            {

                                var pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(doc, writer)));

                                var worker = new XMLWorker(pipeline, true);

                                var p = new XMLParser(worker);

                                p.Parse(msHtml);

                            }

                            cb = writer.DirectContent;

                            cb.AddTemplate(PdfFooter(cb, cf,bf), 30, 1);

                        }

                        finally

                        {

                            if (doc.IsOpen())

                                doc.Close();

                        }

                    }

                }

            }

            if (saveOnly) return null;

            var file2 = new FileStream(fileFullPath, FileMode.Open);

            file2.CopyTo(ms);

            file2.Close();

            ms.Position = 0;

            return ms;

        }

  1. That is it. Run http://localhost:50299/Invoices/InvoicePDF in the browser. You will see the pdf file generated and downloaded.

But remember, complex css does not work. I tried the whole bootstrap.css. It does not show well.

Generate PDF with more decoration

  1. Show the page number and total number of pages.

This requires PageEvent handler defined in the pdf writer.

                  using (var writer = PdfWriter.GetInstance(doc, file))

                    {

                        var pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(doc, writer)));

                        var worker = new XMLWorker(pipeline, true);

                        var p = new XMLParser(worker);

                        writer.PageEvent = new MainTextEventsHandler( p, msHtmlHeader, cssResolver, htmlContext);

You need to define your self pageevent, here I create class MainTextEventsHandler derived from PdfPageEventHelper

      internal class MainTextEventsHandler : PdfPageEventHelper

        {

            PdfTemplate total;

            private PdfTemplate PdfFooter(PdfContentByte cb, BaseFont f_cn, BaseFont f_cb, int page)

            {…your footer with page number defined here…

}

           public override void OnOpenDocument(PdfWriter writer, Document doc)

            {

                total = writer.DirectContent.CreateTemplate(30, 16);

            }

            public MainTextEventsHandler( XMLParser p,  MemoryStream msHtmlHeader, ICSSResolver cssResolver, HtmlPipelineContext htmlContext)

            {

                _p = p;

                _msHtmlHeader = msHtmlHeader;

                _htmlContext = htmlContext;

                _cssResolver = cssResolver;

                _msHtmlHeaderCopy = new MemoryStream();

            }

            public override void OnStartPage(PdfWriter writer, Document doc)

            {

                var pipeline = new CssResolverPipeline(_cssResolver, new HtmlPipeline(_htmlContext, new PdfWriterPipeline(doc, writer)));

                var worker = new XMLWorker(pipeline, true);

                var p = new XMLParser(worker);

                _msHtmlHeaderCopy = CopyAndClose(_msHtmlHeader);

                p.Parse(_msHtmlHeaderCopy);

            }

       

            public override void OnEndPage(PdfWriter writer, Document doc)

            {

                PdfContentByte cb = writer.DirectContent;

                cb.AddTemplate(PdfFooter(cb, cf, bf, doc.PageNumber), 30, 1);

            }

            public override void OnCloseDocument(PdfWriter writer, Document document)

            {

                string pageTotal = writer.PageNumber.ToString();

                ColumnText.ShowTextAligned(total, Element.ALIGN_LEFT,

                        new Phrase(pageTotal, FontFactory.GetFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED,9)), 2, 2, 0);

            }

From the code above, it is easy to find out that the page number is written OnEndPage, and the total number of pages is written OnCloseDocument. This makes sense.

You can construct your footer layout in the PdfFooter method.

2. Same fixed header for each page in the PDF

You need to provide two cshtml files, one is head.cshtml and another is data.cshtml to generate such pdf format. Below htmlHeader stores the content of header of each page, and htmlData stores the full set of dynamic content of each page (it will add pages if htmlData is not able to store in one page of pdf.)

            var htmlHeader = GetHeaderHtml();

            var msHtmlHeader = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(htmlHeader));

….

                       writer.PageEvent = new MainTextEventsHandler( p, msHtmlHeader, cssResolver, htmlContext);

….

                        try

                        {

                                //set list data

                                string htmlData =GetDataHtml();

                                var msHtmlData = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(htmlData));

                                p.Parse(msHtmlData);

                        }

Some lessons from the PDF generation

  1. The free version of ITextSharp package does not support too complex css, for example the bootstrap style.
  2. You need to use pure html <table> to create table instead of fabric bootstrap style
  3. Every html tag needs to have an end tag in the cshtml. If not, you will get error.
  4. If you have error when generate PDF, mostly, it is caused by your error view of cshtml format. Using Binary-Sort Cut approach to locate your error html error element.
  5. Do not include complex function in the cshtml. It will show error if the function is not found. Do not take it grant that the function in the normal cshtml view can be used in the pdf cshtml view.  Below is some function I used.

var to = new System.Web.HtmlString(“<Label>” + toStr + “</label>”);

var from1 = System.Net.WebUtility.HtmlDecode(from.ToString());

data.ToString().Split(c);



« (Previous News)



Leave a Reply