EvoPDF/Winnovative – Adding Header, Footer, and Page Number in Existing PDF without using HtmlToPdfConverter

Adding to my previous blog post on using EvoPDF/Winnovative to convert Html to PDF, How we can add Header, Footer, and Page Number in existing PDFs.

When you look examples given by EvoPDF/Winnovative for adding Header/Footer/Page Number in existing PDF, Most of examples are with HtmlToPdfConverter. Which means example of existing PDFs and also converting URL/Html to PDF at a time.

But if we do not want to do any conversion, Just add Header/Footer/Page Number in existing PDF document without using HtmlToPdfConverter than there isn’t any solution documented. So, here we will see how we can achieve this functionality:

Merge Existing PDFs & Add Header/Footer/Page Numbers

 //Create a document to merge all selected PDFs
 Document MergedDocument = new Document();

 //Get individual existing PDFs to merge and add header/footer/page number
 Document PDFDoc1 = new Document(Server.MapPath("~/PDF1.pdf"));
 Document PDFDoc2 = new Document(Server.MapPath("~/PDF2.pdf"));

 //Merge existing PDFs into New Merge Document
 MergedDocument.AppendDocument(PDFDoc1, true, true, true);
 MergedDocument.AppendDocument(PDFDoc2, true, true, true);
 //Appending document with this syntax MergedDocument.AppendDocument(PDFDoc1);
 //will not add the header/footer/page number on the PDF. To add it properly,
 //you need to append document in above syntax Doc.AppendDocument(PDFDoc,
 //enableHeaderAndFooter, drawHeaderOnFirstPage, drawFooterOnFirstPage)

 //Append Header, Footer and Page Number to the merged document so that it will get
 //applied to all existing PDFs appended to the document           
 CreateHeader(MergedDocument, 100, "<h3>Header</h3>", False, "Page &p; of &P;", 10, 60, "Verdana", 10, "Silver", True, "Silver");
 CreateFooter(MergedDocument, 100, "<h3>Footer</h3>", True, "Page &p; of &P;", 10, 60, "Verdana", 10, "Silver", True, "Silver");
            
 byte[] outPdfBuffer = MergedDocument.Save();
 Response.AddHeader("Content-Type", "application/pdf");
 Response.AddHeader("Content-Disposition", String.Format("attachment;  filename=Header_Footer_in_External_PDF.pdf; size={0}", outPdfBuffer.Length.ToString()));
 Response.BinaryWrite(outPdfBuffer);
 Response.End();

/// <summary>
/// Creates the document header
/// </summary>
 private void CreateHeader(Document pdfDocument, int headerHeight, string   headerHtml, bool addPageNumbers, string pageNumberText, int pageNumberLocationX,
int pageNumberLocationY, string pageNumberFontFamily, int pageNumberFontSize, string pageNumberFontColor, bool drawHeaderLine, string headerLineColor)
        {
            pdfDocument.AddHeaderTemplate(headerHeight);
            HtmlToPdfElement HtmlElement = new HtmlToPdfElement(headerHtml, "");
            HtmlElement.FitHeight = true;
            pdfDocument.Header.AddElement(HtmlElement);
            if (addPageNumbers)
            {
                TextElement pageNumberElement = new TextElement(pageNumberLocationX, pageNumberLocationY, pageNumberText,
                    new System.Drawing.Font(new System.Drawing.FontFamily(pageNumberFontFamily), pageNumberFontSize, System.Drawing.GraphicsUnit.Point));
                pageNumberElement.ForeColor = Color.FromName(pageNumberFontColor);
                pageNumberElement.EmbedSysFont = true;
                pdfDocument.Header.AddElement(pageNumberElement);
            }
            if (drawHeaderLine)
            {
                float Width = pdfDocument.Header.Width;
                float Height = pdfDocument.Header.Height;
                LineElement headerLine = new LineElement(0, Height - 1, Width, Height - 1);
                headerLine.ForeColor = Color.FromName(headerLineColor);
                pdfDocument.Header.AddElement(headerLine);
            }
        }
/// <summary>
/// Creates the document footer
/// </summary>
 private void CreateFooter(Document pdfDocument, int footerHeight, string footerHtml, bool addPageNumbers, string pageNumberText, int pageNumberLocationX,
int pageNumberLocationY, string pageNumberFontFamily, int pageNumberFontSize, string pageNumberFontColor, bool drawFooterLine, string footerLineColor)
        {
            pdfDocument.AddFooterTemplate(footerHeight);
            HtmlToPdfElement HtmlElement = new HtmlToPdfElement(footerHtml, "");
            HtmlElement.FitHeight = true;
            pdfDocument.Footer.AddElement(HtmlElement);
            if (addPageNumbers)
            {
                TextElement pageNumberElement = new TextElement(pageNumberLocationX, pageNumberLocationY, pageNumberText,
                    new System.Drawing.Font(new System.Drawing.FontFamily(pageNumberFontFamily), pageNumberFontSize, System.Drawing.GraphicsUnit.Point));
                pageNumberElement.ForeColor = Color.FromName(pageNumberFontColor);
                pageNumberElement.EmbedSysFont = true;
                pdfDocument.Footer.AddElement(pageNumberElement);
            }
            if (drawFooterLine)
            {
                float Width = pdfDocument.Footer.Width;
                LineElement footerLine = new LineElement(0, 0, Width, 0);
                footerLine.ForeColor = Color.FromName(footerLineColor);
                pdfDocument.Footer.AddElement(footerLine);
            }
        }

 

This might help you if you are stuck in same situation, Please share if any other way with which we can implement this functionality.

Thanks for reading 🙂

Advertisements

EvoPDF – Merging PDFs, Editing existing PDF, and Giving internal link

You may have faced several scenarios where you need a functionality to be develop in which you need to convert html – a web page into PDF.

There are several third party dlls available for conversion from html to pdf in Asp.Net. Among those you can consider EvoPDF/Winnovative as one of the best option having large variety of functionality available. Click here for more information on EvoPDF.

You can see from the link provided above for EvoPDF, they have covered many functionalities with example and sample code. still it’s a large globe and we may find some situation for which we may need to struggle a little bit. I am sharing here few functionality which i have encountered recently and at the end everything working like a charm.

Merging PDFs

Here, we will see basic functionality to create PDF from html and merging it with some existing PDF available to generate a new merged PDF.

You needed to import any of below libraries to begin with:


using Winnovative;
Or
using EvoPdf.HtmlToPdf;

Document MergeResultPdfDocument = new Document();
Document ExistingPdfDocument = new Document();
Document HtmlToPDFDocument = new Document(); 

//Reading Existing file in Document object
Byte[] ExistingPDF = File.ReadAllBytes(fileName);
using (Stream Stream = new MemoryStream(ExistingPDF))
{
   ExistingPdfDocument = new Document(loStream);
   //Adding existing PDF file to Merged PDF 
   MergeResultPdfDocument.AppendDocument(ExistingPdfDocument);
} 

//Convert HtmlToPdf and Merge PDF
PdfConverter Converter = new PdfConverter();
Converter.LicenseKey = LICENSEKEY;
HtmlToPDFDocument = Converter.GetPdfDocumentObjectFromUrl(&amp;quot;URL to convert&amp;quot;);
MergeResultPdfDocument.AppendDocument(HtmlToPDFDocument); 

Generate PDF from Html, Edit Runtime, and Give InternalLink

This is an interesting task. Where you want to generate a PDF from Html and wanted to edit it using code based on some conditions before generating PDF. We will see here how in two steps we can achieve this.

For ex: create a PDF Document from URL (as shown above) having below html:

<table>
<tbody>
<tr>
<td>
              <span data-mapping-enabled="true" style="font-size: 18px; font-weight: bold; color: navy">
			#INDEX1#
              </span></td>
</tr>
<tr>
<td>
              <span data-mapping-enabled="true" style="font-size: 18px; font-weight: bold; color: navy">
			#INDEX2#
              </span></td>
</tr>
<tr>
<td>
              <span data-mapping-enabled="true" style="font-size: 18px; font-weight: bold; color: navy">
			#INDEX3#
              </span></td>
</tr>
</tbody>
</table>

Here, data-mapping-enabled=”true” is very important attribute, using which we can extract those element from PDF and we can update it.

Now, edit the response as per requirement and generate PDF from the final response.

string response = GetResponse("URL to convert");
//Make required changes in response text. We will update here #INDEX# with proper title.

For(int i=0; i<3; i++) // As we have three records in html. this can be any number dynamically.
{
   response = response.replace("#Index" + (i + 1) + "#", Title[i]);
}

//After editing response, create the PDF from final response
PdfConverter htmlToPdfConverter = new PdfConverter();
htmlToPdfConverter.LicenseKey = LICENSEKEY;

Document IndexPdfDocument = htmlToPdfConverter.GetPdfDocumentObjectFromHtmlString(response);

After generating PDF, now we will give internal link to some page to the title. So, we can create a functionality similar to the index page. where we can click on index title to navigate to that page.


//get all html elements which are marked using <strong>data-mapping-enabled="true"</strong>

foreach (HtmlElementMapping htmlElementInfo in htmlToPdfConverter.HtmlElementsMappingOptions.HtmlElementsMappingResult)
                {
                    HtmlElementPdfRectangle[] htmlElementRectanglesInPdf = htmlElementInfo.PdfRectangles;
                    for (int i = 0; i < htmlElementRectanglesInPdf.Length; i++)
                    {
                        int pdfPageIndex = htmlElementRectanglesInPdf[i].PageIndex;
                        RectangleF rectangleInPdfPage = htmlElementRectanglesInPdf[i].Rectangle;

                        //add a rectangle of height 10px and with 1000px at given title to create link
                        RectangleF linkRectangle = new RectangleF(0, rectangleInPdfPage.Y, 1000, 10);

                        //Create explicit destination object to the page number you want to link
                        ExplicitDestination PageDestination = new ExplicitDestination(MergeResultPdfDocument.Pages[pageindex[count]], new PointF(0, 0));

                        //Create Internal Link element and add this element to index page
                        InternalLinkElement internalLink = new InternalLinkElement(linkRectangle, PageDestination);
                       
        IndexPdfDocument.Pages[pdfPageIndex].AddElement(internalLink);
                    }
                    count++;
                }

MergeResultPdfDocument.InsertDocument(0, IndexPdfDocument);

This is very important section of code where you identify the html elements from existing PDF, Creates a rectangle at place of html element, and insert an internal link element.

That’s it, And Wish you all a very happy new year 2016.

Jquery Datatable: Implementing Load on demand Server side with PageMethod/WebMethod

Jquery Datatable is one of the best plugin which we can use in alternate of GridView.

Though there are so many examples available at https://www.datatables.net/ for sever side processing with .txt or .php as AjaxSource. But there isn’t full example on achieving Load on demand with server side processing in asp.net by using PageMethod/WebMethod.

So, if you are looking for similar solution, please walk through below sample code.

Important: Before jumping into the code, it can be beneficial if you have some knowledge of datatable terminology and parameters we send to PageMethod/WebMethod or Parameters we return from PageMethod/WebMethod.

Click here for more information on Parameters sent to the server and returned from server.

Click here for more information on Datatable Methods.

CSS & JS

<link href="//cdn.datatables.net/1.10.7/css/jquery.dataTables.min.css" rel="stylesheet" type="text/css" />
<script src="//cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js" type="text/javascript"></script>

Default.aspx

<form id="form1" runat="server">
        <div id="container">
            <table id="JQtblUsers" class="display">
                <thead>
                    <tr>
                        <th>Email</th>
                        <th>First Name</th>
                        <th>Last Name</th>
                        <th>Company</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </div>
    </form>
<script type="text/javascript">
        $(document).ready(function () {
            var oTable = $('#JQtblUsers').dataTable({
                "paging": true,
                "sPaginationType": "full_numbers",
                "bServerSide": true,
                "sAjaxSource": "Default3.aspx/GetUsers",
                "fnServerData": function (sSource, aoData, fnCallback) {
                    logsRequest = $.ajax({
                        type: "POST",
                        url: sSource,
                        data: "{'sEcho': '" + getValueFromArray(aoData, "sEcho") + "', 'sSearch': '" + getValueFromArray(aoData, "sSearch")
                            + "', 'iDisplayLength': '" + getValueFromArray(aoData, "iDisplayLength") + "', 'iDisplayStart': '" + getValueFromArray(aoData, "iDisplayStart")
                            + "', 'iColumns': '" + getValueFromArray(aoData, "iColumns") + "', 'iSortingCols': '" + getValueFromArray(aoData, "iSortingCols")
                            + "', 'sColumns': '" + getValueFromArray(aoData, "sColumns") + "'}",
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success: function (data) {
                            var json = jQuery.parseJSON(data.d);
                            fnCallback(json);
                        }
                    });
                },
                "aoColumns": [
                                {
                                    "sName": "Email",
                                    "bSearchable": true,
                                    "bSortable": true,
                                    "fnRender": function (oObj) {
                                        return '<a href=\"mailto:' + oObj.aData[0] + '\">' + oObj.aData[0] + '</a>';
                                    }
                                },
                                {
                                    "sName": "FirstName",
                                    "bSearchable": true,
                                    "bSortable": true,
                                    "fnRender": function (oObj) {
                                        return oObj.aData[1];
                                    }
                                },
                                {
                                    "sName": "LastName",
                                    "bSearchable": true,
                                    "bSortable": true,
                                    "fnRender": function (oObj) {
                                        return oObj.aData[2];
                                    }
                                },
                                {
                                    "sName": "Company",
                                    "bSearchable": true,
                                    "bSortable": true,
                                    "fnRender": function (oObj) {
                                        return oObj.aData[3];
                                    }
                                }
                ],
            });
        });

        function getValueFromArray(aoData, Key) {
            for (i = 0; i < aoData.length; i++) {
                if (aoData[i].name == Key) {
                    return aoData[i].value;
                }
            }
        }
    </script>

Default.aspx.cs

#region Class

public class Employee
{
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Company { get; set; }
}

#endregion Class
#region Methods

    [WebMethod]
    public static string GetUsers(string sEcho, string sSearch, int iDisplayLength, int iDisplayStart, int iColumns, int iSortingCols, string sColumns)
    {
        var echo = int.Parse(sEcho);
        var displayLength = iDisplayLength;
        var displayStart = iDisplayStart;

        var records = GetRecordsFromDatabaseWithFilter(sSearch);
        if (records == null)
            return string.Empty;

        var itemsToSkip = displayStart == 0 ? 0 : displayStart + 1;
        var pagedResults = records.Skip(itemsToSkip).Take(displayLength).ToList();
        var hasMoreRecords = false;

        var sb = new StringBuilder();
        sb.Append(@"{" + "\"sEcho\": " + echo + ",");
        sb.Append("\"recordsTotal\": " + records.Count + ",");
        sb.Append("\"recordsFiltered\": " + pagedResults.Count + ",");
        sb.Append("\"iTotalRecords\": " + records.Count + ",");
        sb.Append("\"iTotalDisplayRecords\": " + records.Count + ",");
        sb.Append("\"aaData\": [");
        foreach (var result in pagedResults)
        {
            if (hasMoreRecords)
                sb.Append(",");

            sb.Append("[");
            sb.Append("\"" + result.Email + "\",");
            sb.Append("\"" + result.FirstName + "\",");
            sb.Append("\"" + result.LastName + "\",");
            sb.Append("\"" + result.Company + "\"");
            sb.Append("]");
            hasMoreRecords = true;
        }
        sb.Append("]}");
        return sb.ToString();
    }

    public static List<Employee> GetRecordsFromDatabaseWithFilter(string search)
    {
        List<Employee> Employees = new List<Employee>();
        for (int i = 0; i <= 5; i++)
        {
            Employees.Add(new Employee { Email = "psatikunvar7@gmail.com", FirstName = "Pratik", LastName = "Satikunvar", Company = "Apple" + i });
            Employees.Add(new Employee { Email = "psatikunvar7@gmail.com", FirstName = "Pratik", LastName = "Satikunvar", Company = "Microsoft" + i });
            Employees.Add(new Employee { Email = "psatikunvar7@gmail.com", FirstName = "Pratik", LastName = "Satikunvar", Company = "Google" + i });
            Employees.Add(new Employee { Email = "psatikunvar7@gmail.com", FirstName = "Pratik", LastName = "Satikunvar", Company = "Amazon" + i });
        }
        return Employees.Where(a => a.Company.ToLower().Contains(search.ToLower())).ToList();
    }

    #endregion Methods

I hope it was interesting and useful.

Please share any real time issue or feature you have faced or implemented.

Thank You 🙂

Custom Validator: validation with ajax call

Hello/Namaste all,

Nowadays we do not like to work with postbacks and update panel, this is the time of Jquery/Javascript and Ajax.

So, sometimes we wanted to validate a form from javascript itself without doing postback by calling some method in code behind. So, today we will see how we can validate a form from javascript by doing ajax call to code behind. And what kind of difficulties we will face.

Starting with, we can use custom validator’s client side javascript function, Ajax call ($ajax() function), PageMethod/WebMethod to validate from javascript having actual logic in code behind PageMethod/WebMethod.

Please see below:

  Default.aspx

<form id="form1" runat="server">
        <div>
            <asp:TextBox ID="txtEmail" runat="server"></asp:TextBox>
            <asp:CustomValidator ID="cmtxtForm" runat="server" ClientValidationFunction="OnClientValidate" SetFocusOnError="true"
                ErrorMessage="Email address already exist" ValidateEmptyText="false" ValidationGroup="FormSubmit">
            </asp:CustomValidator>
            <asp:Button ID="btnSubmit" runat="server"  OnClick="btnSubmit_Click" Text="Submit" ValidationGroup="FormSubmit" />
        </div>

        <script type="text/javascript">
            function OnClientValidate(sender, args) {
                var Email = document.getElementById("txtEmail").value;
                if (Email != null && Email != "") {
                    alert(Email);
                    $.ajax({
                        type: "POST",
                        contentType: "application/json; charset=utf-8",
                        url: "Default.aspx/CheckIfEmailExist",
                        data: "{'Email': '" + Email + "'}",
                        dataType: "json",
                        success: function (result) {
                            args.IsValid = result.d;
                            alert(args.IsValid);
                        }
                    });
                }
                else {
                    args.IsValid = false;
                }
                alert(args.IsValid);
            }
        </script>

Default.aspx.cs

    #region PageMethods

    [WebMethod]
    public static bool CheckIfEmailExist(string Email)
    {
        if (Email.ToLower() == "psatikunvar7@gmail.com")
            return false;
        else
            return true;
    }

    #endregion PageMethods

Note: Here, it will call web method and return the result, but if you will check by implementing above code, what you will find is inaccurate args.IsValid in both alert function. No matter what code behind function returns, IsValid will always be true.

This is because, $.ajax() method is by default async method. so, to get the actual response from the PageMethod/WebMethod in ajax function inside client side validation function for custom validator, we need to set option async to false in ajax function as shown below:

 $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "Default.aspx/CheckIfEmailExist",
        data: "{'Email': '" + Email + "'}",
        dataType: "json",
        async: false,
        success: function (result) {
            args.IsValid = result.d;
            alert(args.IsValid);
        }
 });

That’s it. Now it should work fine as per value PageMethod/WebMethod is returning.

If you enjoy working with Jquery and Ajax please drop anything you want to share about it in comment. i would like to learn and discuss.

Thank You. Stay Hungry 🙂

Custom Validator: Pass custom parameter to clientside validation function from code behind

Hello readers,

It is common we uses all kind of validators in our daily programming. Custom Validator is one of them and may be very interesting too.

Recently i was needed to add one Custom Validator to the page and i was needed to validate a control dynamically based on condition. so that i was needed to pass some custom parameter from code behind to clientside validation function based on which i will determine which control to validate.

Please refer below code to achieve this:

Default.aspx

 <form id="form1" runat="server">
        <div>
            <asp:TextBox ID="txtForm" runat="server"></asp:TextBox>
            <asp:CustomValidator ID="cmtxtForm" runat="server" ClientValidationFunction="OnClientValidate" SetFocusOnError="true"
                ErrorMessage="Please provide valid value" ValidateEmptyText="false" ValidationGroup="FormSubmit">
            </asp:CustomValidator>
            <asp:Button ID="btnSubmit" runat="server" OnClick="btnSubmit_Click" Text="Submit" ValidationGroup="FormSubmit" />
        </div>
        <script type="text/javascript">
            function OnClientValidate(sender, args) {
                alert(sender.CustomParameter);
                args.IsValid = sender.CustomParameter;
            }
        </script>
    </form>

Default.aspx.cs

 protected void Page_Load(object sender, EventArgs e)
    {
        bool HasQuerystring = false;
        if (!string.IsNullOrEmpty(Request.QueryString["q"]))
            HasQuerystring = true;
        Page.ClientScript.RegisterExpandoAttribute(cmtxtForm.ClientID, "CustomParameter", HasQuerystring.ToString(), false);
    }

It was so easy right? Thank you 🙂