The Artima Developer Community
Sponsored Link

.NET Buzz Forum
WYSIWYG Web Printing with PDF & Response Filters

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Brendan Tompkins

Posts: 158
Nickname: brendant
Registered: Apr, 2005

Brendan Tompkins is .NET Developer and founder of CodeBetter.Com
WYSIWYG Web Printing with PDF & Response Filters Posted: May 22, 2006 2:50 PM
Reply to this message Reply

This post originated from an RSS feed registered with .NET Buzz by Brendan Tompkins.
Original Post: WYSIWYG Web Printing with PDF & Response Filters
Feed Title: Brendan Tompkins
Feed URL: /error.htm?aspxerrorpath=/blogs/brendan.tompkins/Rss.aspx
Feed Description: Blog First. Ask Questions Later.
Latest .NET Buzz Posts
Latest .NET Buzz Posts by Brendan Tompkins
Latest Posts From Brendan Tompkins

Advertisement

By far the #1 complaint I hear from users of various web applications is that printing a page is not WYSIWYG.  It’s really a simple problem that the industry has sort of swept under the rug for most of the history of the Internet.  Browser printing in the current versions of IE and Firefox is simply horrible, and while server technologies like ASP.NET and PHP have gotten better and better at screen presentation with every release, printing simply not kept up pace.  As web developers we spend our time figuring out all the intricacies of browser page rendering, and often are asked to do the same thing for printing.  It’s frankly a frustrating, difficult problem to solve.  IE7 should finally fix many of these problems when it’s released, but what are we supposed to do until then?  And even when it’s released, how many years will we be designing to support the current crop of web browsers?

Adobe PDF Printing Rocks

Adobe’s PDF format does have it’s issues, but it also has huge advantages that make it a great solution for creating printable website documents. 1) Nearly all your users already have the free reader software and 2) Printing is nearly 100% WYSIWYG.  So what’s the solution to your website printing problems at least until you can guarantee that the majority of your users will be using a print-friendly browser?  Simple, when a user makes a print request on your site, dynamically create PDF documents on the fly making sure to include all of your users page state, and output that PDF document to the browser.  Simple right? Well, it’s actually easier said than done.  You have two big issues when attempting to solve this problem: Finding a decent PDF writing component to deploy on your webservers, and being able to accurately render a html page to this component while at the same time preserving your user’s page state.

Finding a Decent PDF Component

In order to create a PDF document on the fly, I wanted a PDF component that would take a URL and create a document from the resulting page.  There are plenty of resources out there for you to use that will help you find the right PDF component, so I won’t list them here.  I settled on webSuperGoo’s ABCPdf component.  I liked it’s simplicity – you can grab any page and turn it into a PDF, including JavaScript rendered elements using the following code:

Doc theDoc = new Doc();

theDoc.HtmlOptions.PageCacheEnabled = false;

theDoc.HtmlOptions.PageCacheClear();

 

theDoc.Page = theDoc.AddPage();

 

int theID;

theDoc.HtmlOptions.UseScript = true;

theID = theDoc.AddImageUrl("http://codebetter.com");

 

while (true)

{

    theDoc.FrameRect(); // add a black border

    if (!theDoc.Chainable(theID))

        break;

    theDoc.Page = theDoc.AddPage();

    theID = theDoc.AddImageToChain(theID);

}

 

for (int i = 1; i <= theDoc.PageCount; i++)

{

    theDoc.PageNumber = i;

    theDoc.Flatten();

}

You can then take the resulting document and stream it back to the browser.  Here’s what the PDF output looks like rendered via the Acrobat reader in IE

 

Preserving Page State

A problem you’ll find with this method of generating PDF documents is that while it works well for static HTML pages and other content, it quickly breaks down for anything but the simplest web applications.  The reason?  This PDF component won’t carry the user’s state information such as login status, session and viewstate cache, through the request, therefore, the resulting PDF output may not show things like secure content, grid sorting, or other page content that is dependent on Page State.  You’ll often bump up against this limitation when you’re trying to stream the content of a web response to some other output location, such as a file, database, cache, email or something else entirely.  The solution to this problem is to create a custom ASP.NET Response.Filter.  This will allow you to intercept the response that would normally be sent to the user’s browser, and do all kinds of neat things with it, like turn it into a PDF document.  Here’s a great article from a great site that talks all about these Response.Filter thingeys.

Putting it all Together

In order to get this all to work properly, you’ve got to wire it all up.  I’ve found that the following method works best:

First, create a linkbutton on your master page to allow the user to create a â€œPrintable PDF View” Handle the button click event, and hand off the response to your custom PDF response filter.  Here’s the code Snippet which switches the filter:

    protected void LinkButton1_Click(object sender, EventArgs e)

    {

        // HOW THIS WORKS:

        //    Add a new response filter.  This saves the page output in the database, and redirects to

        //    Pdf/PageToPdf.ashx.  This page creates a PDF object and adds the saved output page via

        //    Pdf/PreRenderedPdfPage.ashx.  This allows the current page state and view to be requestsed

        //    via the PDF object, which normally wouldn't be able to share session, etc.

        Response.Filter = new PdfResponseFilter(Response.Filter);

    }

In your custom filter, write the output stream temporarially to the cache (or wherever you want to temporarially store it).  It has to be stored somewhere that any web request can access it.  Good candidates are the file system, cache, or the database.  NOTE: This is what is called “Security by Obscurity” and is generally considered not secure enough for any highly sensitive data, such as credit card information.  The issue is that while it may be extremely difficult to guess the GUID and you’d have to make the page request during the milliseconds or so that passes while this rendered HTML waits to be picked up, it is possible that it could be intercepted.  So, don’t do this if you work for a bank or the government . I don’t and find it secure enough for most applications I work with.

    public override void Write(byte[] buffer, int offset, int count)

    {

        string strBuffer = UTF8Encoding.UTF8.GetString(buffer, offset, count);

 

        // ---------------------------------

        // Wait for the closing </html> tag

        // ---------------------------------

        Regex eof = new Regex("</html>", RegexOptions.IgnoreCase);

 

        if (!eof.IsMatch(strBuffer))

        {

            responseHtml.Append(strBuffer);

        }

        else

        {

            responseHtml.Append(strBuffer);

            string finalHtml = responseHtml.ToString();

 

            Guid g = Guid.NewGuid();

 

            HttpContext.Current.Cache[g.ToString()] = finalHtml;

 

            HttpContext.Current.Response.Redirect("PageToPdf.ashx?DocId=" + g.ToString(), false);           

        }

    }

The PDF component has be issued a one-time ticket (GUID) to pickup the resulting HTML (which may contain secure information) and the Http response is re-directed to an ASHX handler that will create the PDF document.  This handler creates a SuperGoo pdf document, and requests the stored html, passing in the ticket (GUID). 

public void ProcessRequest(HttpContext context)

    {

 

        Doc theDoc = new Doc();

        theDoc.HtmlOptions.PageCacheEnabled = false;

        theDoc.HtmlOptions.PageCacheClear();

 

        theDoc.Page = theDoc.AddPage();

 

        int theID;

        theDoc.HtmlOptions.UseScript = true;

        theID = theDoc.AddImageUrl("http://localhost/" + context.Request.ApplicationPath + "/PreRenderedPdfPage.ashx?DocId=" + context.Request["DocId"]);

 

        while (true)

        {

            theDoc.FrameRect(); // add a black border

            if (!theDoc.Chainable(theID))

                break;

            theDoc.Page = theDoc.AddPage();

            theID = theDoc.AddImageToChain(theID);

        }

 

        for (int i = 1; i <= theDoc.PageCount; i++)

        {

            theDoc.PageNumber = i;

            theDoc.Flatten();

        }

 

 

        context.Response.ClearHeaders();

        context.Response.Expires = 0;

        context.Response.Cache.SetCacheability(HttpCacheability.NoCache);

        context.Response.Cache.SetNoServerCaching();

        context.Response.Cache.SetNoStore();

        context.Response.Cache.SetMaxAge(TimeSpan.Zero);

 

        context.Response.AddHeader("content-disposition", "attachement; filename=Doc" + context.Request["DocId"] + ".pdf");

        context.Response.ContentType = "application/pdf";

 

        theDoc.Save(context.Response.OutputStream);

        theDoc.Clear();

 

        context.Response.End();

    }

A final ASHX handler writes out the stored HTML and removes it from cache.

    public void ProcessRequest (HttpContext context)

    {

        context.Response.Expires = 0;

        context.Response.Cache.SetCacheability(HttpCacheability.NoCache);

        context.Response.Cache.SetNoServerCaching();

        context.Response.Cache.SetNoStore();

        context.Response.Cache.SetMaxAge(TimeSpan.Zero);

 

        if (context.Request["DocId"] != null && context.Cache[context.Request["DocId"]] != null)

        {

            String preRenderedHtml = context.Cache[context.Request["DocId"]].ToString();

 

            // Write out this document.               

            context.Response.Write(preRenderedHtml);

 

            // Delete this so no one can pick up this page by guessing an ID later on.

            context.Cache.Remove(context.Request["DocId"]);

        }

        else

        {             

           context.Response.Write("<HTML><BODY><H1>THIS PAGE HAS EXPIRED.</H1></BODY></HTML>");

        }                   

 

 

        context.Response.End();

    }

This all allows the exact HTML that would normally be sent back to the browser, to be routed through the PDF component and sent back to the user in PDF resulting in a true WYSIWYG printable page.

I’ve thrown together a quick sample app that you can download here that demonstrates this entire process.  You’ll have to visit webSuperGoo and download the trial PDF component to get this to work.   It shows you the following page, allows you to set some session and view state information, then allows you to generate a WYSIWYG PDF document.

Clicking the Printable PDF View link will generate a pdf, with the calendar and other page state and login information saved.

Good luck, and I hope this can help solve your web printing headaches, it certainly did for me!

Technorati Tags: , ,
Share this post: Email it! | bookmark it! | digg it! | reddit!

Read: WYSIWYG Web Printing with PDF & Response Filters

Topic: Could not allocate ancillary table for view or function resolution Previous Topic   Next Topic Topic: A refresh button for asp.net pages

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use