How to convert a web page to a PDF using iText.Net and a WebBrowser object

by Heathesh 12. August 2010 06:09

Disclaimer: This is a hack, it uses screen captures using the WebBrowser object to populate the PDF. I did it in my spare time for fun, and I would not advise using it in a production environment without thorough testing and nerves of steel.

I've noticed a few websites that charge a fee to convert a web page to a PDF. So I was curious if there was an open source alternative to this. I know there are libraries to create PDF's, but all my googling could not find one that would convert a web page or HTML to PDF. Having played around with the iText.Net ( libraries before, I thought I would like to see if there was a way to use them to accomplish this for free.

To begin with, you need to download the relevant libraries from For convienience sake I've added the DLL's I used here (zipped - 2,816 KB download):

I'm going to be using Visual Studio 2008 for this and have not tried it using Visual Studio 2010. So using Visual Studio 2008 create a Console Application project. This needs to work in a single-threaded apartment state so using a web page or web service would require you to do more "hacking". You can see my previous post "Run a single-threaded apartment method with parameters that returns a value within a web service" on what is needed to achieve that.

Once your project has been created add all the DLL's from the ZIP file as references to your project. Also add references to the following .Net DLLs:


Now add the following usings to your console app Program.cs file:

//NOTE: Do not add System.Drawing as the namespace will cause conflicts with classes in the PDF libraries
using System.IO;
using System.Windows.Forms;
using com.lowagie.text;
using com.lowagie.text.pdf;

Next you need to add the following method to your code. The method will load a web browser, navigate to the specified url and take a screen shot of the web page.

        /// <summary>
        /// Generate the screen shot image for the specified URL
        /// </summary>
        /// <param name="url"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        public static System.Drawing.Bitmap generateScreenshotImage(string url, int width, int height)
            // Load the webpage into a WebBrowser control
            using (WebBrowser webBrowser = new WebBrowser())
                //disable the scroll bars and supress script errors, then navigate to the url
                webBrowser.ScrollBarsEnabled = false;
                webBrowser.ScriptErrorsSuppressed = true;

                //wait for the page to load
                while (webBrowser.ReadyState != WebBrowserReadyState.Complete) { Application.DoEvents(); }

                // Set the size of the WebBrowser control
                webBrowser.Width = width;
                webBrowser.Height = height;

                if (width == -1)
                    // Take Screenshot of the web pages full width
                    webBrowser.Width = webBrowser.Document.Body.ScrollRectangle.Width;

                if (height == -1)
                    // Take Screenshot of the web pages full height
                    webBrowser.Height = webBrowser.Document.Body.ScrollRectangle.Height;

                // Get a Bitmap representation of the webpage as it's rendered in the WebBrowser control
                System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(webBrowser.Width, webBrowser.Height);
                webBrowser.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, webBrowser.Width, webBrowser.Height));
                return bitmap;

Okay... so we've now got a method to generate a screen shot of the web page, next we need to be able to retrieve the byte[] array of the specified bitmap the above method returns. So add the following method to your code:

        /// <summary>
        /// Gets the byte array for the specified bitmap
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        private static byte[] getBytesForBitmap(System.Drawing.Bitmap bitmap)
            using (MemoryStream memoryStream = new MemoryStream())
                bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
                return memoryStream.GetBuffer();

We're almost done. We now need to add methods to use the bitmap created above and create the actual PDF. For this I created and added the following four methods. The code comments should be self-explanatory:

        /// <summary>
        /// Converts the specified url to a pdf file
        /// </summary>
        /// <param name="url"></param>
        /// <param name="fileName"></param>
        private static void convertUrlToPdf(string url, string fileName)
            byte[] pdfBytes = convertWebPageToPdf(url);

            using (FileStream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite))
                BinaryWriter binaryWriter = new BinaryWriter(fileStream);

        /// <summary>
        /// Converts the specified url to a PDF byte array
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private static byte[] convertWebPageToPdf(string url)
            // step 1: creation of a document-object
            Document document = new Document();

            // step 2:
            // we create a writer that listens to the document
            // and directs a PDF-stream to a file
            MemoryStream memoryStream = new MemoryStream();
            PdfWriter.getInstance(document, memoryStream);

            // step 3: we open the document

            //get the screen shot of the web page
            using (System.Drawing.Bitmap screenshot = generateScreenshotImage(url, 1020, -1))
                //if there is more than one page, split the image otherwise just add the image
                if (screenshot.Height > 1500)
                    separatePages(document, screenshot);
                    addImage(document, screenshot);

            // step 5: we close the document
            //return the byte[] of the pdf
            return memoryStream.GetBuffer();

        /// <summary>
        /// Add the image to the PDF document
        /// </summary>
        /// <param name="document"></param>
        /// <param name="screenshot"></param>
        private static void addImage(Document document, System.Drawing.Bitmap screenshot)
            Image png = Image.getInstance(getBytesForBitmap(screenshot));

        /// <summary>
        /// Separates the pages of the bitmap into the PDF document
        /// </summary>
        /// <param name="document"></param>
        /// <param name="screenshot"></param>
        private static void separatePages(Document document, System.Drawing.Bitmap screenshot)
            int reminder = screenshot.Height % 1500;
            int pages = screenshot.Height / 1500 + (reminder > 0 ? 1 : 0);
            int y = 0;
            int height = 1500;

            for (int i = 0; i < pages; i++)
                //if this is the last page, and we have a reminder, we need to adjust the height accordingly
                if (i == pages - 1 && reminder > 0)
                    height = screenshot.Height - y;

                using (System.Drawing.Bitmap pageBitmap = screenshot.Clone(new System.Drawing.Rectangle(0, y, 1020, height), System.Drawing.Imaging.PixelFormat.DontCare))
                    //add the image
                    addImage(document, pageBitmap);

                    //increment the height counter to move to the next page
                    y += 1500;

Okay so we've now got everything we need. In our Main method we simply need to call the convertUrlToPdf method with a URL and PDF file name and it will generate a PDF of the website for us. There is just one thing, the WebBrowser control can only run in a single-threaded apartment state. So we need to decorate our Main method with the STAThread attribute:

        [STAThread] //run in single-threaded apartment state
        static void Main(string[] args)
            convertUrlToPdf("", "iservice.pdf");

That's basically it. Run the code and you'll find the iservice.pdf in your output (bin\Debug - depending on your configuration mode) folder.

Happy PDFing!

Tags: , , , , ,

Development | .Net | Visual Studio 2008 | VS2008 | PDF

Calling or using a web service method with parameters as a data source for a report

by Heathesh 21. July 2010 03:11

Using Visual Studio 2008, I came across an interesting problem a couple of developers were having trying to call a web service method with parameters and use it as a data source for a report. I actually found it's quite a finicky thing to do, but is relatively easy.

To begin with, I created a web service with a single method as a sample. My web service contained a method called GetPerson which expected a int parameter called id which would return a person object for the specified id.

Next I added a new report to a Visual Studio 2008 reports project I had already setup. Using the Report Wizard I specified the datasource type as XML, and added the URL of the web service as my connection string. You might have to click on "Credentials" and select "Use windows authententication" if you have a problem moving to the next window.

You should be presented with the "Design the Query" window next. Here click on the "Query Builder" button. Copy and paste the following text into your Query Designer:

   <Method Name="GetPerson" Namespace="">
       <Parameter Name="id">
   <ElementPath IgnoreNamespaces="true">*</ElementPath>

Things you have to change.

To start off with change the <SoapAction>. Access the web service using your browser e.g. go to "http://localhost/ReportWebService/DataService.asmx" and click on the method you're trying to run. In the description of the service you should see the name of the SoapAction you should use:

SOAP 1.1
The following is a sample SOAP 1.1 request and response. The placeholders shown need to be replaced with actual values.

POST /ReportWebService/DataService.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: ""

Use the above mentioned SoapAction as specified above in the query:


Next you need to specify the method name and namespace. The method name should be simple enough, but be careful to include any trailing slashes "/" in the namespace should they be required (take note of the trailing slash at the end of my namespace):

   <Method Name="GetPerson" Namespace="">

Next change the Parameter info as required. Please remember Parameter names are case sensitive and should be specified exactly the same as in your WSDL of your web service. Also I recommend always specifying a default value that will return data, this makes it easier for you to check that your method is executing properly from within your query designer (click on the exclamation mark "!" to run the query).

       <Parameter Name="id">

The rest of the query should be left as is. Now that you've got your query setup, you can continue through the wizard setting up the other features of your report until your report has been created.

Adding the report parameters

The next step is to create report parameters that map to your web service method parameters. To do this open the "Report Data" window on the left. This window should appear next to your "Toolbox" and "Server Explorer" windows on the left of your Visual Studio IDE. Once the window is open, right click Parameters and select "Add new parameter". Give the parameter a relevant name etc. and set the data type to the relevant data type of the parameter of your web service and click "Okay".

Next right click the DataSet you created in the "Report Data" window (which is the web service dataset) and select "Dataset Properties". Click on "Parameters" in the window that opens and then "Add". Set the parameter name to the name of the parameter on the webservice method, for example my method needs a parameter called "id" so that's the name I used and in the drop down for the parameter value select the report parameter you created above then click "Okay".

Alright, that should be it. Select preview to view your report and you should be able to enter a parameter value and select "View Report" to view your report...

Happy Reporting!


Powered by BlogEngine.NET (with enhancements by Heathesh)
Theme by Mads Kristensen (with tweeks by Heathesh)


Microsoft Certified Professional

Microsoft Certified Technology Specialist

Answer Questions


Tag cloud


<<  August 2017  >>

View posts in large calendar