Even though the ReportViewer has built-in print functionality, people often wish to implement their own version to provide significant customizations or a deeper integration with their application.  There are a number of samples out on the web, but I thought it might be useful to provide one here since the request is so common.  Attached to this post is a sample PrintDocument that will programmatically print a given ServerReport or LocalReport.

Generating a report for printing uses the image renderer, which is a hard page break renderer.  Unlike viewing a report within the report viewer UI, a hard page break renderer must cut off a page at a specific size.  In the report viewer UI, a page can be enlarged to make a table or chart fit.  You can’t do that with paper.

As a result, calculating the content for an arbitrary page can be very expensive.  When the image renderer receives a request to generate page 10, for example, it internally generates pages 1 through 9 and throws them out.  So asking for one page at a time from the image renderer will be prohibitively slow, even for small reports.  To address this performance issue, both local and server mode have the ability to request all the pages at once.

In local mode, the sample uses the Render overload that takes a CreateStreamCallback.  This causes the renderer to keep generating pages until it hits the end of the report.  The callback is invoked once for each page to generate a stream to store the image for that page.

Server mode is a little different - the server can’t ask the client for a callback for each page.  Instead, the report server supports the concept of persisted streams.  The URL access parameter rs:PersistStreams is used to tell the server to generate all of the pages and to persist those pages on the server.  This request returns the first page.  Each subsequent page can be retrieved using the rs:GetNextStream parameter.  When you try to go beyond the last page, the server returns an empty response.

This sample is just a starting point to demonstrate the ReportViewer APIs.  There are a number of ways it can be improved.  Given that both rendering a page and sending it to the printer can be long running operations, you might consider rendering the pages in a background thread, allowing you to continue generating pages while sending completed pages to the printer.  You also might wish to extend it to use printers other than the default printer.  Or you could extend OnBeginPrint to handle printing a specific range of pages rather than all of them.  Or you could use file system backed streams, since using MemoryStreams could consume a lot of memory on a large report.