Friday, May 18, 2007

Finding a Templating Tool for Printing in .NET

We have a pretty basic requirement for printing cards, envelopes, brochures, etc. We want to take requests in for donations and then allow for printing in batches of these orders.

The PrintDocument is the main class in .NET for printing. The sample code shown looks like this:

Private Sub pd_PrintPage(sender As Object, ev As PrintPageEventArgs)
Dim linesPerPage As Single = 0
Dim yPos As Single = 0
Dim count As Integer = 0
Dim leftMargin As Single = ev.MarginBounds.Left
Dim topMargin As Single = ev.MarginBounds.Top
Dim line As String = Nothing

' Calculate the number of lines per page.
linesPerPage = ev.MarginBounds.Height / printFont.GetHeight(ev.Graphics)

' Print each line of the file.
While count < linesPerPage line = streamToPrint.ReadLine() If line Is Nothing Then Exit While End If yPos = topMargin + count * printFont.GetHeight(ev.Graphics) ev.Graphics.DrawString(line, printFont, Brushes.Black, leftMargin, yPos, New StringFormat()) count += 1 End While ' If more lines exist, print another page. If Not (line Is Nothing) Then ev.HasMorePages = True Else ev.HasMorePages = False End If End Sub


However, this requires you to manually print by drawing through the graphics context. This is not a great solution for something like envelopes, cards, etc. because hard coding a bunch of strings is not really a scalable solution.

What we need is a template engine - something to define a template that can be filled with data and then printed, all through .NET code. In addition, I would like the ability to print the same document to an image file so that I can show the user a print preview through the web.

So what should we use for a template engine? The options we came up with were:

1. Word - using the supplied primary interop assemblies, we could load up a word document, fill out some form fields or do a mail merge programmatically. However, this isn't recommended to be done by server based applications because each time you load up an instance of Word to do the processing.

2. Custom template - we could define our template layout. Given the amount of data we have, it wouldn't be that difficult to define a template that contains a basic background, some text fields with X,Y locations, etc. I'm not exactly a fan of this type of solution because it doesn't scale in terms of functionality - what happens when the user wants rich font support, margins, tables, embedded bitmaps, paragraph formatting, etc. Surely, we shouldn't have to re-invent the wheel here.

3. Printing existing controls. See this article on a basic explanation. This would allow you use a windows form as a template and the print it. This is better than custom coding a template engine, but its not exactly a great solution either - who wants to build a windows form to represent an envelope?

After exploring a few other ideas, we came up with a basic idea - use SQL Server 2005 Reporting Services. After thinking about it, it provides us all the pieces we need in a nice package:

1. Template engine based on XML that can be generated by a relatively non-technical user.
2. Automatic data binding to the data source - simply create a report that represents a card, an envelope, etc.
3. Page sizing and pagination support - we would need this for envelopes and odd sized cards.
4. Rendering to image format - we can use this for preview.
5. Programming model through web services and ability to grab rendered output through web services. So for our preview feature, we can simply call the web service and stream out a rendered image. Alternatively, we can simply point the URL that will supply a rendered image of your report.

So we think we can use SQL Server 2005 Reporting Services as a template engine and embed it into our web application to enable server side printing.

One drawback on this approach - I haven't found anything in the web services API that allows me to render the report directly to the printer. I can see the ReportExecutionService.Render method that can give me back a stream in any format, but then I would need to figure out how send that stream to the printer. The simplest format to handle would be an image - simply then drawing on the graphics object and printing it would then be possible. There is a good post on this type of idea here...

No comments: