0

Let me start by saying if I'm making this overly complicated, please, please offer suggestions on how to simplify!! I'm trying to create some logic that will let users export the contents on the screen to a PDF file. The contents contains multiple highcharts graphs, so I need for the html to be rendered completely to the page so I can grab the svg information to pass into the PDF file. Since the page has been rendered completely, the only way I know to pass the rendered html back to the server is through Web Services. Web Services is calling a .cs file where the PDF functionality is located. I can set a break point in the .cs file at the end of the pdf function, and it is hitting it. The problem is, it's returning an error of "200 OK parsererror SyntaxError: Unexpected token %".

Now, I create a dummy html string in my C# codebehind and call the .cs file from the codebehind, and it creates a PDF file with no issues at all. So my question is, why would the class work when being called from a codebehind, but not from Web Services? The only thing I can think of, is that it has something to do with a postback? My code is below:

JavaScript that is calling the Web Service..I can step all the way through the web service and class functions being called, they are making it to the end, but it's returning to the error function with 200 OK parsererror SyntaxError: Unexpected token %

function checkSplash() {
    timesChecked++;
    if (loadImages >= document.getElementsByName('img').length || timesChecked * checkInterval >= maxLoadTime) {
        clearInterval(intervalID);

        if (document.getElementById("exportDiv") != null) {
            var mydata = {
                src: document.getElementById("export_pdf").innerHTML
            }

            $.ajax({
                type: "post",
                contentType: "application/json; charset=UTF-8",
                url: "../WebServices/print.asmx/export",
                data: JSON.stringify(mydata),
                dataType: "json",
                success: function (response) {
                    alert("here");
                    location.reload(true);
                },
                error: function (xhr, textStatus, errorThrown) {
                    alert(xhr.status + ' ' + xhr.statusText + ' ' + textStatus + ' ' + errorThrown);
                }
            })
        }
    }
}

Web Service being called...this function is identical the a test function I created in the codebehind. The codebehind test function works and creates the PDF document with no issues. But when this gets called from the javascript, it returns the error

using DMC.Classes;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Web;
using System.Web.Services;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace DMC.WebServices
{
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [System.Web.Script.Services.ScriptService]

    public class print : System.Web.Services.WebService
    {
        [WebMethod]
        public bool export(string src)
        {
            string fileName = null;
            export dmc = new export();

            fileName = " Behavior Statistics YTD ";

            dmc.exportPDF(fileName, "Portrait", src);

            return true;
        }
    }
}

Finally, the .cs methods being used:

using iTextSharp.text;
using iTextSharp.text.html.simpleparser;
using iTextSharp.text.pdf;
using NReco.PdfGenerator;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace DMC.Classes
{
    public class export 
    {
        public void exportPDF(string fileName, string Orientation, string html)
        {
            HtmlToPdfConverter pdf = new HtmlToPdfConverter();

            html = html.Replace("\n", "");
            html = html.Replace("\t", "");
            html = html.Replace("\r", "");
            html = html.Replace("\"", "'");

            switch (Orientation)
            {
                case "Portrait":
                    pdf.Orientation = PageOrientation.Portrait;
                    break;
                case "Landscape":
                    pdf.Orientation = PageOrientation.Landscape;
                    break;
                default:
                    pdf.Orientation = PageOrientation.Default;
                    break;
            }

            pdf.Margins.Top = 25;
            pdf.PageFooterHtml = createPDFFooter();

            var pdfBytes = pdf.GeneratePdf(createPDFScript() + html + "</body></html>");

            HttpContext.Current.Response.ContentType = "application/pdf";
            HttpContext.Current.Response.ContentEncoding =  System.Text.Encoding.UTF8;
            HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=" + fileName + ".pdf");
            HttpContext.Current.Response.BinaryWrite(pdfBytes);
            HttpContext.Current.Response.Flush();
            HttpContext.Current.Response.End();
        }

        private string createPDFScript()
        {
            return "<html><head><style>td,th{line-height:20px;} tr { page-break-inside: avoid }</style><script>function subst() {var vars={};var x=document.location.search.substring(1).split('&');for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}" +
        "var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];for(var i in x) {var y = document.getElementsByClassName(x[i]);" +
        "for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];}}</script></head><body onload=\"subst()\">";
        }

        private string createPDFFooter()
        {
            return "<div><table style='font-family:Tahoma; font-size:9px; width:100%'><tr><td style='text-align:left'>Research Dept|RR:mm:jpg</td><td style='text-align:right'>Page <span class=\"page\"></span> of <span class=\"topage\"></span></td></div>";
        }
    }
}
Pit Digger
  • 9,618
  • 23
  • 78
  • 122
Jimmy Genslinger
  • 557
  • 3
  • 21

2 Answers2

0

In your exportPDF function, add a try-catch to trap the Response.End() exception and ignore it:

public void exportPDF(string fileName, string Orientation, string html)
{
    try
    {
        HtmlToPdfConverter pdf = new HtmlToPdfConverter();

        html = html.Replace("\n", "");
        html = html.Replace("\t", "");
        html = html.Replace("\r", "");
        html = html.Replace("\"", "'");

        switch (Orientation)
        {
            case "Portrait":
                pdf.Orientation = PageOrientation.Portrait;
                break;
            case "Landscape":
                pdf.Orientation = PageOrientation.Landscape;
                break;
            default:
                pdf.Orientation = PageOrientation.Default;
                break;
        }

        pdf.Margins.Top = 25;
        pdf.PageFooterHtml = createPDFFooter();

        var pdfBytes = pdf.GeneratePdf(createPDFScript() + html + "</body></html>");
        HttpContext.Current.Response.ContentType = "application/pdf";
        HttpContext.Current.Response.ContentEncoding = System.Text.Encoding.UTF8;
        HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=" + fileName + ".pdf");
        HttpContext.Current.Response.BinaryWrite(pdfBytes);
        HttpContext.Current.Response.Flush();
        HttpContext.Current.Response.End();
    }
    catch
    {
        //if you have other exceptions you'd like to trap for, you can filter or throw them here
        //otherwise, just ignore your Response.End() exception.
    }
}
mjw
  • 1,196
  • 1
  • 12
  • 19
  • Thanks for the response @mjw...I tried this, and I still received the error. However, somebody else told me to try "blanking out" my datatype in the ajax call that I'm making, so instead of datatype:"json", I changed it to datatype:"" and the response now goes to the success function. – Jimmy Genslinger Sep 02 '15 at 20:23
  • Unfortunately it didn't fix the overall problem of generating a PDF. I traced through the code when it is called from the codebehind, then again when it's called from the ajax function, & the only difference I'm seeing is the handler. From codebehind the handler being used is ASP.behavior_referrals_aspx & from ajax script it is System.Web.Script.Services.ScriptHandlerFactory.HandlerWrapper. My head is now spinning from reading about handlers all day, & I see that handlers process code in different ways. So my new question is, can I just change the handler to be the ASP one that's working? – Jimmy Genslinger Sep 02 '15 at 20:31
  • What's the actual error message now? Where is it failing? – mjw Sep 02 '15 at 20:33
  • That's just it, there is no error message. The exportPDF function runs all the way through, and the AJAX calls the success function. The problem is that it just doesn't do anything. No error message, no PDF file created...nothing. As I mentioned, I think the "System.Web.Script...." handler must be generic and does not have any ability to handle the binarywrite line (where the file gets created), whereas the ASP handler does. But, I'm completely guessing... – Jimmy Genslinger Sep 02 '15 at 20:57
  • Does it render any of the HTML you're emitting at all? Any js errors in the console? – mjw Sep 02 '15 at 21:03
  • Not 100% sure at what point you're inquiring about the html, but I can see that it's been generated completely before submitting it through to the AJAX. After the submission, however, there is nothing -- no console errors, no further emissions, no PDFs...nothing :( – Jimmy Genslinger Sep 02 '15 at 21:06
0

Referencing HttpContext from within the PDF creator class really is not a good idea as it violates the principal of Separation of Concerns. A better way would be for your exportPDF method to return the pdfBytes array and have the caller can handle it appropriately.

So your exportPDF method ends up looking like this:

public byte[] exportPDF(string fileName, string Orientation, string html)
{
    // etc.

    var pdfBytes = pdf.GeneratePdf(createPDFScript() + html + "</body></html>");

    return pdfBytes;
}

And your web method looks like this:

[WebMethod]
public byte[] export(string src)
{
    string fileName = null;
    export dmc = new export();

    fileName = " Behavior Statistics YTD ";

    Context.Response.ContentType = "application/pdf";
    Context.Response.ContentEncoding =  System.Text.Encoding.UTF8;
    Context.Response.AddHeader("content-disposition", "attachment; filename=" + fileName + ".pdf");

    return dmc.exportPDF(fileName, "Portrait", src);
}

One thing that isn't clear to me is how you are planning to handle the response in the JavaScript. What this returns is the PDF file content. What is your JavaScript going to do with that?

Jack A.
  • 4,245
  • 1
  • 20
  • 34
  • To give background, I work for a school system & when I initially developed this page the intent was to show data for 1 school on the page. When I did that, the page would render, the user would click "Export" and my code behind would call the exportPDF function & download the PDF file with no issue. District admins asked for the ability to select & export multiple schools at a time, so I had to create a way to build the html for multiple schools, let the page render fully to grab the .svg data for the graphs, & send this back to the exportPDF function. – Jimmy Genslinger Sep 03 '15 at 15:26
  • My hope was that if I could make it back to the exportPDF function, that the function would work exactly as it did from getting called by the code behind. When it didn't, that's when I started to learn about HTTPContext handlers. So to answer your question, my hope is that the pdf file would get created from the exportPDF method and no response would be needed to the JavaScript -- my success function would just be empty. – Jimmy Genslinger Sep 03 '15 at 15:27
  • "No response is needed to the JavaScript". Then where does the PDF go? The calls to HttpContext from your original code are most definitely creating a response. When you make an AJAX call, the response is returned to the JavaScript. You might want to take a step back and try to define exactly what behavior you want to implement. – Jack A. Sep 03 '15 at 16:15
  • When I initially created the page -- JavaScript and the Web Service was not in play. The user would load the page, the page would render fully, the user had the option to click an "Export to PDF" button. Upon clicking the button, the c# code behind was called, and a function in the code behind called the exportPDF function -- the one above. The PDF would be generated from this function -- the exportPDF function above -- when it hit the line: HttpContext.Current.Response.Flush(); – Jimmy Genslinger Sep 03 '15 at 20:59
  • Then a change request was made -- the one described above. As I mentioned, I need the html to render fully, so that I can get the SVG data generated. Since I know the exportPDF function above works, because it has been, I wanted to send the fully rendered html to it, as I had been. The only way I know to do that, is to wait for the page to finish rendering, then call a web service, passing the fully rendered html to the web service. The web service in turn calls the exportPDF function, at which point I hoped it would operate as it always has. – Jimmy Genslinger Sep 03 '15 at 21:01
  • Since previously the PDF was rendered in the exportPDF function, I didn't really care what came back...because the PDF should have already been exported. However, calling the Web Service from javascript, uses a different HTTPContext Handler, one different than when called from the code behind. That is the only difference that I can see. It's my understanding that the prebuilt/ASP-built handlers have different functionality, so since they are different handlers, I'm assuming the handler used with Web Services was not coded to take any action with the HTTPContext.Flush command. – Jimmy Genslinger Sep 03 '15 at 21:04
  • Hence the original question...can I change HTTPContext handlers to use another pre-existing one, and if so, how? – Jimmy Genslinger Sep 03 '15 at 21:07
  • If I understand correctly, what you want to be able to do is to download the PDF using an AJAX call. In your original implementation, the file was being downloaded via a page navigation. Try researching "download file using ajax". I'm leaving work now but I'll circle back later tonight. – Jack A. Sep 03 '15 at 21:07
  • You can't really download a file using an AJAX call, but I did find this: http://stackoverflow.com/questions/4545311/download-a-file-by-jquery-ajax – Jack A. Sep 04 '15 at 15:02