| You can download the complete source code of the XpsPrint sample here. |
This article can be useful to anyone who wants to submit an XPS document to the unmanaged XpsPrint API from a .NET application. But the main goal if this article is to show how to print a word processing document from an ASP.NET or Windows Service application using Aspose.Words and the XpsPrint API.
Problem
When you develop a .NET application and you need to produce some printed output, you can use the classes in the System.Drawing.Printing namespace or the WPF classes. But, as it turns out, if you develop an ASP.NET or Windows Service application, then your options for printing are severely limited, because Microsoft recommends against using these approaches. See the links below for more information.
http://support.microsoft.com/kb/324565
The use of the .NET Framework Printing classes is not supported from a service. This includes ASP pages, which generally run in the context of the server service.
http://msdn.microsoft.com/en-us/library/system.drawing.printing.aspx
Classes within the System.Drawing.Printing namespace are not supported for use within a Windows service or ASP.NET application or service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.
http://msdn.microsoft.com/en-us/library/bb613549.aspx
The use of WPF to build Windows services is unsupported. Because WPF is a presentation technology, the Windows service requires the appropriate permissions to perform visual operations that involve user interaction. If the Windows service does not have the appropriate permissions, there may be unexpected results.
The Document object provides a family of the Print methods to print documents and these methods print via the .NET printing classes defined in the System.Drawing.Printing namespace. There are many customers of Aspose.Words who use this printing method in their server-side applications without any problems, but there is a way to comply with Microsoft’s recommendations and it is described in this article.
Solution
The proper way to print documents according to Microsoft is to use the unmanaged XpsPrint API. This API is available on Windows 7, Windows Server 2008 R2 and also on Windows Vista, provided the Platform Update for Windows Vista is installed.
Since Aspose.Words can easily convert any document to XPS, we only need to write code that passes an XPS document to the XpsPrint API. The only problem is that the XpsPrint API is unmanaged and it requires some knowledge of the Platform Invoke.
The Code
We have created the XpsPrintHelper class with the Print method, which is very easy to use. You just need to specify a document that you want to print, a printer name and an optional job name. If there was any problem submitting or printing the document, the method will throw an exception.
The last parameter is a Boolean value that specifies whether the code should wait until the job is printed or return immediately after the print job has been submitted. If you choose to return immediately, then you will not be able to determine whether the document has printed successfully or not in the end.
Example
Invoke the utility class to print via XPS.
// Open a sample document in Aspose.Words. Aspose.Words.Document document = new Aspose.Words.Document(dataDir + "SampleDocument.doc"); // Specify the name of the printer you want to print to. const string printerName = @"\\COMPANY\Brother MFC-885CW Printer"; // Print the document. XpsPrintHelper.Print(document, printerName, "My Test Job", true);
' Open a sample document in Aspose.Words. Dim document As New Aspose.Words.Document(dataDir & "SampleDocument.doc") ' Specify the name of the printer you want to print to. Const printerName As String = "\\COMPANY\Brother MFC-885CW Printer" ' Print the document. XpsPrintHelper.Print(document, printerName, "My Test Job", True)
There are two overloads of the XpsPrintHelper.Print method. The first overload takes an Aspose.Words.Document object and saves it into a MemoryStream in the XPS format. Then it invokes the other XpsPrintHelper.Print overload.
If you want to use this sample without Aspose.Words (e.g. you already have an XPS document and just want to print it from an ASP.NET or Windows Service application), then you can just delete this method.
Example
Convert an Aspose.Words document into an XPS stream and print.
/// <summary> /// Sends an Aspose.Words document to a printer using the XpsPrint API. /// </summary> /// <param name="document"></param> /// <param name="printerName"></param> /// <param name="jobName">Job name. Can be null.</param> /// <param name="isWait">True to wait for the job to complete. False to return immediately after submitting the job.</param> /// <exception cref="Exception">Thrown if any error occurs.</exception> public static void Print(Aspose.Words.Document document, string printerName, string jobName, bool isWait) { if (document == null) throw new ArgumentNullException("document"); // Use Aspose.Words to convert the document to XPS and store in a memory stream. MemoryStream stream = new MemoryStream(); document.Save(stream, Aspose.Words.SaveFormat.Xps); stream.Position = 0; Print(stream, printerName, jobName, isWait); }
''' <summary> ''' Sends an Aspose.Words document to a printer using the XpsPrint API. ''' </summary> ''' <param name="document"></param> ''' <param name="printerName"></param> ''' <param name="jobName">Job name. Can be null.</param> ''' <param name="isWait">True to wait for the job to complete. False to return immediately after submitting the job.</param> ''' <exception cref="Exception">Thrown if any error occurs.</exception> Public Shared Sub Print(ByVal document As Aspose.Words.Document, ByVal printerName As String, ByVal jobName As String, ByVal isWait As Boolean) If document Is Nothing Then Throw New ArgumentNullException("document") End If ' Use Aspose.Words to convert the document to XPS and store in a memory stream. Dim stream As New MemoryStream() document.Save(stream, Aspose.Words.SaveFormat.Xps) stream.Position = 0 Print(stream, printerName, jobName, isWait) End Sub
| The second XpsPrintHelper.Print overload accepts a Stream object. The stream must contain a document in the XPS format. This method starts an XPS print job, sends the document to the XpsPrint API and then waits for the result if needed. |
Example
Prints an XPS document using the XpsPrint API.
/// <summary> /// Sends a stream that contains a document in the XPS format to a printer using the XpsPrint API. /// Has no dependency on Aspose.Words, can be used in any project. /// </summary> /// <param name="stream"></param> /// <param name="printerName"></param> /// <param name="jobName">Job name. Can be null.</param> /// <param name="isWait">True to wait for the job to complete. False to return immediately after submitting the job.</param> /// <exception cref="Exception">Thrown if any error occurs.</exception> public static void Print(Stream stream, string printerName, string jobName, bool isWait) { if (stream == null) throw new ArgumentNullException("stream"); if (printerName == null) throw new ArgumentNullException("printerName"); // Create an event that we will wait on until the job is complete. IntPtr completionEvent = CreateEvent(IntPtr.Zero, true, false, null); if (completionEvent == IntPtr.Zero) throw new Win32Exception(); try { IXpsPrintJob job; IXpsPrintJobStream jobStream; StartJob(printerName, jobName, completionEvent, out job, out jobStream); CopyJob(stream, job, jobStream); if (isWait) { WaitForJob(completionEvent); CheckJobStatus(job); } } finally { if (completionEvent != IntPtr.Zero) CloseHandle(completionEvent); } }
''' <summary> ''' Sends a stream that contains a document in the XPS format to a printer using the XpsPrint API. ''' Has no dependency on Aspose.Words, can be used in any project. ''' </summary> ''' <param name="stream"></param> ''' <param name="printerName"></param> ''' <param name="jobName">Job name. Can be null.</param> ''' <param name="isWait">True to wait for the job to complete. False to return immediately after submitting the job.</param> ''' <exception cref="Exception">Thrown if any error occurs.</exception> Public Shared Sub Print(ByVal stream As Stream, ByVal printerName As String, ByVal jobName As String, ByVal isWait As Boolean) If stream Is Nothing Then Throw New ArgumentNullException("stream") End If If printerName Is Nothing Then Throw New ArgumentNullException("printerName") End If ' Create an event that we will wait on until the job is complete. Dim completionEvent As IntPtr = CreateEvent(IntPtr.Zero, True, False, Nothing) If completionEvent = IntPtr.Zero Then Throw New Win32Exception() End If Try Dim job As IXpsPrintJob Dim jobStream As IXpsPrintJobStream StartJob(printerName, jobName, completionEvent, job, jobStream) CopyJob(stream, job, jobStream) If isWait Then WaitForJob(completionEvent) CheckJobStatus(job) End If Finally If completionEvent <> IntPtr.Zero Then CloseHandle(completionEvent) End If End Try End Sub
The code for the StartJob, CopyJob, WaitForJob and CheckJobStatus methods as well as definitions of the IXpsPrintJob and IXpsPrintJobStream interfaces is quite low-level and uses Platform Invoke and COM Interop. This code is not included in the article for brevity, but it is available in the sample download.
The XpsPrint API also provides additional functionality, such as monitoring the job progress, but our XpsPrintHelper is a very simple wrapper and does not expose this functionality. You could add this yourself if you want to.
End Result
| When you run the project, it prints a sample document on the specified printer. To make results visible, the console window opens. The program displays the success message or the text of an exception if one was thrown. |
