Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / XHTML

Using RsClientPrint to Print Reports from ASP.NET Webforms

5.00/5 (2 votes)
28 Mar 2009CPOL13 min read 170.5K   1  
A How to that demonstrates how to use RsClientPrint ActiveX control to print reports from Microsoft SQL Server Reporting Services

Introduction

When developing ASP.NET applications that use the Microsoft SQL Server Reporting Services 2005 (SSRS) to display and print reports, you will face a problem when you want to provide a direct print functionality to user outside the traditional Web Report Viewer. This article will describe how to use the RsClientPrint ActiveX control provided by Microsoft to print SSRS reports inside custom ASP.NET applications.

Note that this article doesn’t include any samples to download; only code snippets and scenario descriptions will be included.

Background

If you Googled through the web regarding printing options for SSRS reports, you will be left with three probable options:

  1. Render the report to HTML or Images and use JavaScript to print the report using web browser (Internet Explorer or Firefox) printing capability. The odds of this option are:
    • When your report is rendered in multiple pages, the pages will not print in correct format. You will have pages that don’t break correctly due the web browser printing margins settings and other printing problems because the browser will print it as an HTML page.
    • The web browser headers and footers will be displayed in the printout. You can guide users to remove the web browser headers and footers when using your print functionality. (Users will hate this.)
    • Printing landscape reports will require users to change their print settings every time they want to print a report with landscape orientation.
  2. Export the report to another format that can be used for printing. E.g. PDF or Microsoft Excel. The odds are:
    • User must have PDF or Excel viewer to print the reports.
    • Print operation will be tedious since the user needs to do many steps to just print the report.
  3. Use the RsClientPrint ActiveX control. This is a good solution to sends the reports directly to the printer. The odds are:
    • User must use Internet Explorer as web browser to support ActiveX controls. Also you can write Firefox extension for this task.
    • The Microsoft documentation is very short (don’t expect so much information from Microsoft MSDN regarding using RsClientPrint.
    • If you follow the Microsoft documentation, you will face a problem with SSRS authentication as we are going to discuss through this article.

In this article, I will describe how to use the RsClientPrint in an efficient way and implement the same functionality provided in Microsoft Reports Web Viewer printing.

Introducing RsClientPrint

The RsClientPrint is an ActiveX control. This control should be registered to clients PCs before we can use it.

The RsClientPrint control contains several properties and one method. Please refer to Microsoft RsClientPrint for more information here.

Here is the list of the control properties and methods borrowed from MSDN site above:

RsClientPrint Properties

Property Type RW Default Description
MarginLeft Double RW report setting Gets or sets the left margin. The default value if not set by the developer or specified in the report is 12.2 millimeters. For more information about report margins, see this link.
MarginRight Double RW report setting Gets or sets the right margin. The default value if not set by the developer or specified in the report is 12.2 millimeters. For more information about report margins, see, this link.
MarginTop Double RW report setting Gets or sets the top margin. The default value if not set by the developer or specified in the report is 12.2 millimeters. For more information about report margins, see, this link.
MarginBottom Double RW report setting Gets or sets the bottom margin. The default value if not set by the developer or specified in the report is 12.2 millimeters. For more information about report margins, see this link.
PageWidth Double RW report setting Gets or sets the page width. The default value if not set by the developer or the report definition is 215.9 millimeters.
PageHeight Double RW report setting Gets or sets the page height. The default value if not set by the developer or the report definition is 279.4 millimeters.
Culture Int32 RW Browser locale Specifies the locale identifier (LCID). This value determines the unit of measurement for user input. For example, if a user types 3, the value will be measured in millimeters if the language is French or inches if the language is English (United States). Valid values include: 1028, 1031, 1033, 1036, 1040, 1041, 1042, 2052, 3082.
UICulture String RW Client culture Specifies string localization of the dialog box. Text in the Print dialog is localized into these languages: Chinese-Simplified, Chinese Traditional, English, French, German, Italian, Japanese, Korean, and Spanish. Valid values include: 1028, 1031, 1033, 1036, 1040, 1041, 1042, 2052, 3082.
Authenticate Boolean RW Specifies whether the control issues a GET command against the report server to initiate a connection for out-of-session printing.

RsClientPrint Methods

The RsClientPrint control supports one method only called print. This method is used to launch the print dialog. The method arguments are described in the above Microsoft link. I pasted them here as reference.

Argument I/O Type Description
ServerPath In String Specifies the report server virtual directory (for example, https://adventure-works/reportserver).
ReportPathParameters In String Specifies the full name to the report in the report server folder namespace, including parameters. Reports are retrieved through URL access. For example: "/AdventureWorks Sample Reports/Employee Sales Summary&EmpID=1234"
ReportName In String The short name of the report (in the example above, the short name is Employee Sales Summary). It appears in the Print dialog box and in the print queue.

How RsClientPrint Works

The RsClientPrint renders reports in EMF (Enhanced Meta File) format to send the reports to the printer. The EMF format is a vector format which makes the rendered report independent from the resolution. Visit this Wikipedia link for more information.

The RsClientPrint uses the Reporting Service direct URL access to render the report to EMF format. Direct URL access enables users to communicate with the Reporting Service using Reporting Service URL and query strings to pass commands to the Reporting Service. Visit this MSDN link for more information about URL access.

When rendering the reports using the EMF format, the RsClientPrint instructs the Reporting Service to persist the rendered report stream using the URL command rs:PresistStream=true. This command will force Reporting Service to render the first page and return the result stream to the caller directly. To get the next page, RsClientPrint will replace the rs:PresistStream command with rs:GetNextStream=true. The Reporting Service will render the next page and return its stream to the caller. When there are no more pages, Reporting Service will return an empty stream.

RsClientPrint will read the result EMF streams and send it to the selected client printer or prepare it for preview.

Microsoft Web Report Viewer

When using the Report Viewer inside Internet Explorer to render SSRS reports, you will have a little print button. Pressing the print button will show up a print dialog and a preview capability to review the report before printing. This print dialog is Microsoft ActiveX control called RsClientPrint.

What is interesting about the Report Viewer is that if you reviewed the page source from Internet Explorer, you will not find any code that references the RsClientPrint.

How does the Web Report Viewer print?

The Report Viewer uses IFRAM object to load the print code when you press the print button. This IFRAME object has a style with display: none. By this way the IFRAM will never be visible in the client browser.

The first time you render the report in the Report Viewer, the IFRAM src attribute is empty. But when printing the report, the src attribute will point to another temp page that contains the required HTML code to print the required report.

Below is a code snippet of a sample HTML code that is generated by the Report Viewer and used by the hidden print IFRAME.

HTML
<HTML>
<BODY onload="Print()">
<OBJECT ID="RSClientPrint" CLASSID="CLSID:FA91DF8D-53AB-455D-AB20-F2F023E498D3"
CODEBASE="/Reports/Reserved.ReportViewerWebControl.axd?
ReportSession=4nfhfoj4e20k4v55jrw3ijnr&amp;ControlID=
aa4dfda2cb2b48388a2956ca7db7d995&amp;Culture=1033&amp;
UICulture=9&amp;ReportStack=1&amp;OpType=PrintCab#Version=2005,090,3042,00"
VIEWASTEXT></OBJECT>
<script language="javascript">
function Print()
{
if (typeof RSClientPrint.Print == "undefined")
{
alert("Unable to load client print control.");
return;
}

RSClientPrint.MarginLeft = 9.906;
RSClientPrint.MarginTop = 9.906;
RSClientPrint.MarginRight = 9.906;
RSClientPrint.MarginBottom = 9.906;

RSClientPrint.PageHeight = 296.926;
RSClientPrint.PageWidth = 230.124;

RSClientPrint.Culture = 1033;
RSClientPrint.UICulture = 9;

RSClientPrint.UseEmfPlus = true;

RSClientPrint.Print("/Reports/Reserved.ReportViewerWebControl.axd",
"ReportSession=4nfhfoj4e20k4v55jrw3ijnr&ControlID=aa4dfda2cb2b48388a2956ca7db7d995&
Culture=1033&UICulture=9&ReportStack=1&OpType=PrintRequest","Cashier
Balance")
}
</script>
</BODY>
</HTML>

From the HTML code above, this is a complete HTML page tags that includes the following:

  1. HTML page tags (HTML and Body tags)
  2. RsClientPrint object declaration
  3. A JavaScript print method
  4. Body attribute to call the print method when the page loads

What is interesting here is that if we look to the parameters passed to the RsClientPrint Print method, we will not find a URL of the Remote Reporting Service? We will find a URL of the Report Viewer HTTP Handler. This HTTP Handler is registered automatically in the web application web.config file when the Report Viewer is dropped in the ASP.NET web form.

Why is the Report Viewer using this HTTP handler instead of the Report Server URL? The answer is to avoid authentication when using the RsClientPrint to print the reports.

To understand the reason, we need to know more about the authentication mode of the Reporting Service as follows:

The Reporting Service is hosted by default under the IIS and uses the Windows Integrated Security to secure connection to the Reporting Service web services. The windows integrated security will provide the Reporting Services information about the caller and authorize him for privileged items only.

Also the Reporting Service uses integrated windows accounts to specify access privileges to different Reporting Service folders, reports and data sources definitions.

So when the user tries to connect the Reporting Service in default configuration, the IIS will pass the user window account to the Reporting Service which in turn will use it to authorize the user to interact with Reporting Service items according to his assigned privileges.

This default configuration will not work if your custom web application is published in internet or you can’t privilege every user for the Reporting Service items.

The simple solution to authorize every user is to do the following:

  1. Add the user which ASP.NET application runs under IIS to the Reporting Service authorized users list and assign the required privileges to this user. You can use the SSRS Reports Manager to do this.
  2. Assign the required privileges to this user. You can use the SSRS Reports Manager to do this.

For IIS 5.1, this will be the ASPNET user. Some developers prefer to personalize the ASP.NET application using custom windows user. In this case, they add this user to the Reporting Service instead of the ASPNET default user.

For IIS 6.0 and 7.0, this will be the identity at which the IIS application pool uses to run under the IIS. I can’t go further about the application pools here. You can read more about the application pools in the IIS site.

Doing this will enable the ASP.NET application to use the Report Viewer control to render reports to user using the application process identity to authorize the requests to the Reporting Service.

The above long rational explains why the Report Viewer control uses the HTTP handler to print the report.

If the Report Viewer passes the Reporting Service URL instead of using HTTP handler, the user will need to enter Windows account user name and password to gain access to the Reporting Service. It needs to do so because it is printing outside of the ASP.NET application context.

The above will introduce problems if:

  1. The application is enabled over the internet and there is no Windows accounts defined for users (Forms authentication)
  2. The user is not authorized in the Reporting Service and expecting a print functionality when using the ASP.NET web application.

So Report Viewer directs the RsClientPrint to communicate with its HTTP handler to accept its rendering requests. The HTTP handler will communicate to the Reporting Service with direct URL access using the ASP.NET application credential context. The Reporting Service will accept the connection from the application since it is running with authorized user identity. The Reporting Service will render the requested reports and returns the EMF stream to the application. In turn, the application will forward these streams to the RsClientPrint control.

Note: The above information is derived by observation; I didn’t find any formal reference.

Implementing the RsClientPrint Handler

As we see from the above description, if we have an HTTP handler that accepts the RsClientPrint requests and render the required reports in the context of the current ASP.NET application, we will be able to print the reports without any need for authentication with the Reporting Service.

The solution can be resolved by an HTTP handler or a simple ASP.NET web form. I thought since this article is long enough I will use the web form instead of HTTP handlers for the sake of simplicity.

Let us begin by creating a web form called PrintReport.aspx. This web form will accept requests from the RsClientPrint control and send back the rendered EMF streams.

The following is the PrintReport.aspx code:

C#
protected void Page_Load()
{
if(Request.QueryStrings.Count > 0)//this is a request from the RsClientPrint control
{
string requestUri = string.Format("http://{0}/reportserver?{1}",_serverName,
Request.QueryStrings.ToString());

WebRequest request = WebRequest.Create(requestUri);

CookieContainer cookies = null;
if(Session["PrintReport.CookieContainer"] != null)
{
cookies = Session["PrintReport.CookieContainer"] as CookieContainer;
}
else
{
cookies = new CookieContainer;
Session["PrintReport.CookieContainer"] = cookies;
}
(request as HttpWebRequest).CookieContainer = cookies;
request.UseDefaultCredentials = true;

try
{
using (WebResponse rsResponse = request.GetResponse())
{
Stream s = rsResponse.GetResponseStream();
Response.Clear();
Response.BufferOutput = true;
BinaryReader reader = new BinaryReader(s);
byte[] buf = new byte[256];
int count = 0;
do
{
count = reader.Read(buf,0,256);
 if(count > 0)
{
Response.OutputStream.Write(buf,0,count);
}
else
{
Response.End();
return;
}
}while(count > 0);
Response.Flust();
Response.End();
}
}
catch
{
//Report error
}
}

In the above code, the RsClientPrint control sends requests to the PrintReport.aspx page. The page will process the request by forwarding it to the reporting server using the current ASP.NET credentials. When the response is received from the reporting server, the page will forward this response to the RsClientPrint control.

The RsClientPrint will continue sending requests to the PrintReport.aspx page until it receives an empty response. The empty response is received only when there are no more report pages to render in the current reporting server session.

The reporting server identifies the session using the request cookies container in the incoming request.

Embedding the IFRAME Object

In the page you want to print, you can embed the IFRAM object by inserting the following snippet in your web form:

ASP.NET
<asp:Literal ID="IFrameLiteral" runat="server" Text=""/>

The src attribute of this IFRAME object points to the page that contains the required code to launch the RsClientPrint control Print method.

The trick here is how to generate this dynamic code?

We can use the PrintReport.aspx web form to generate this dynamic code as follows:

  1. Place a formatted string in a text file. Name this file PrintScript.txt.
  2. Paste the following text to the file:
    JavaScript
    <HTML>
                    <BODY onload="Print()">
                    <OBJECT ID="RSClientPrint"
    CLASSID="CLSID:FA91DF8D-53AB-455D-AB20-F2F023E498D3" CODEBASE="RsClientPrint.cab"
    VIEWASTEXT></OBJECT>
                    <script language="javascript">
                    function Print()
                    [
                    if (typeof RSClientPrint.Print == "undefined")
                    [
                    alert("Unable to load client print control.");
                    return;
                    ]
                    
                    RSClientPrint.MarginLeft = {0};
                    RSClientPrint.MarginTop = {1};
                    RSClientPrint.MarginRight = {2};
                    RSClientPrint.MarginBottom = {3};
                    
                    RSClientPrint.PageHeight = {4};
                    RSClientPrint.PageWidth = {5};
                
                    RSClientPrint.Culture = 1033;
                    RSClientPrint.UICulture = 9;
                    
                    RSClientPrint.UseEmfPlus = true;
                    
                    RSClientPrint.Print("{6}", "{7}", "{8}")
                    ]
                    </script>                 
                    </BODY>
    </HTML>
  3. When user wants to print a report, the text in the PrintScript.txt can be formatted as follows:
    JavaScript
    string frameSrc = File.Load(Request.GetApplicationPath() + @"PrintScript.txt");
    frameSrc = string.Format(frameSrc,marginLeft,marginTop,marginRight,
    		marginBottom,"/PrintReport.aspx",Report
    Path and parameters ,"Report Name");
    frameSrc = frameSrc.Replace(‘[‘,’{‘);
    frameSrc = frameSrc.Replace(‘]‘,’}‘);
  4. Store the created text in a session variable:
    JavaScript
    Session["FrameSource"]  = frameSrc;
    
    IFrameLiteral.Text =  "<IFRAME
    id="PrintIFrame" src="PrintReport.aspx"/>
  5. When loading the page, the IFRAM will load the PrintReport.aspx page. The PrinReport.aspx page will intercept the call and send the response as illustrated in the following code:
    JavaScript
    protected void Page_Load()
    {
        if(Request.QueryStrings.Count == 0)
        {
            Response.Clear();
            if(Session["FrameSource"] != null)
            {
                Response.Write(Session["FrameSource"].ToString());
            }
            Response.End();
        }
    }

    From the above code, the response will return the content of the FrameSource session variable to the IFRAME. This will contain a full HTML code to call the RsClientPrint object print method.

  6. The following is a sample of the resulted IFRAME HTML code:
    JavaScript
    <HTML>
            <BODY onload="Print()">
            <OBJECT ID="RSClientPrint"
    CLASSID="CLSID:FA91DF8D-53AB-455D-AB20-F2F023E498D3" CODEBASE="RsClientPrint.cab"
    VIEWASTEXT></OBJECT>
            <script language="javascript">
            function Print()
            {
            if (typeof
    RSClientPrint.Print == "undefined")
            {
            alert("Unable to load client print control.");
            return;
            }
            RSClientPrint.MarginLeft= 10;
            RSClientPrint.MarginTop= 10;
            RSClientPrint.MarginRight= 10;
            RSClientPrint.MarginBottom= 10;
            RSClientPrint.PageHeight= 297;
            RSClientPrint.PageWidth= 210;
            RSClientPrint.Culture= 1033;
            RSClientPrint.UICulture= 9;
            RSClientPrint.UseEmfPlus= true;
            RSClientPrint.Print(http://localhost/PrintReport.aspx,
    		"/Reports/FinancialReport&Year=2008", "Financial Report")
            }
            </script>
            </BODY>
    </HTML>
  7. The RsClientPrint control will communicate with the print page as shown above and the RsClientPrint control print dialog will appear.

    So the scenario is as follows:

    1. User clicks ASP.NET print button.
    2. The print button OnClick event will be fired.
    3. In the OnClick event handler, user build the IFRAME source string and store it in a session variable.
    4. The IFRAM tries to load the PrintReport.aspx page.
    5. The PrintReport.aspx page sends the appropriate response.
    6. The RsClientPrint control declared in the IFRAM JavaScript loads the Print Dialog.
    7. The RsClientPrint control communicates with the PrintReport.aspx to get streams of rendered report.
    8. RsClientPrint control prints the received EMFs.

History

This is version 3 of the article and I will update this article frequently to add the missing code and enhance its content.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)