|
|  |
|
 |
 |
| Implementing Crystal Reports in an Iron Speed Application |
|
Michael F. Sumption
GovIS, LLC
May 17,2005
Iron Speed Designer V2.1
|
|
| Introduction |
|
There are several ways to implement Crystal Reports ("CR") in an Iron Speed Designer application. The
"Code
Customization Wizard" provides one example. This article expands the scope of that example by including sample code and
documentation for a "Crystal Reports Viewer" page, dynamically loading and persisting reports, setting various properties,
exception handling, exporting/displaying reports, and other tips and tricks.
|
| A Tale of Two Editions |
|
Before we get started with the code samples, let me say a brief word about two editions of CR: Visual Studio .NET 2003 Crystal
Reports ("VS.NET CR") and Crystal Reports 10 Developer Edition ("CR 10 DE"). If you can afford to buy CR 10 DE or CR XI DE, do
so. Why? The VS.NET CR is actually version 9.1, and the report/print engine is based on the old RDC architecture. The .NET
components in CR 10 DE are based on the new RAS report/print engine, which is more extensible and handles processes more
efficiently (i.e. thread based). Also, the report designer and viewers are better and have more features. For a complete list
of features by version and edition, here’s the data sheet from Business Objects:
http://www.businessobjects.com/global/pdf/products//crystalreports/crxi_feat_ver_ed.pdf. The code samples in this article
should work in both editions, but have only been tested with CR 10 DE and Iron Speed Designer Professional Edition Version 2.1.3.
|
| Microscope – Creating a Viewer Page |
Most Iron Speed Designer applications typically have header, menu, body, and footer sections that makeup the user interface. What
happens when you export/display a report as PDF? Your nice user interface ("that you spent hours creating") is replaced with
the ugly Acrobat Reader toolbar and the report. So much for consistency. Well, here’s a step-by-step method to create a
report viewer page that will fit right in and provide the benefits of the CR DHTML
Viewer, which Acrobat Reader does not have, such as drill down and exporting to
other file formats. If you still prefer to export/display directly to PDF, then
see the "Goods - Exporting and Displaying" section toward the end of this article for some sample
code.
- In your ISD application, right click the [Shared] folder and select [New Page]. In the
"New Page" dialog, select the [Master Page] template and name the page "ReportViewer."

- Build and then close the application.
- Now we need to add the "CR Viewer" component to the page including the
necessary declarations for the code-behind and references for the project.
The easiest way to do this is with VS.NET. So, open VS.NET, open your
project, and select [Rebuild] from the [Build] menu. You need to
rebuild the project in VS.NET first before proceeding in order to resolve some class
reference issues.
- Open the "ReportViewer.aspx" page in [Design] view. From the VS.NET [Toolbox
- Web Forms] tab, drag and drop a "CrystalReportViewer" component onto the page
at the position marked with a red rectangle below (i.e. the main content
area).

NOTE: When the "CrystalReportViewer" component is added to the page, the
associated class declaration is added to the "ReportViewer.gen.aspx.vb"
code-behind class and several CR namespace references are added to the
project. In the project [References] node of the "Solution Explorer", the three
"CrystalDecisions.Enterprise.Xxx" references can be removed.
- Click on the "CrystalReportViewer" component
and go to [Properties]. Change the following properties to "False": [DisplayGroupTree],
[EnableDatabaseLogonPrompt], and [EnableParameterPrompt]. The
property changes will be explained in the "Monopoly - Setting Various
Properties" section.
- Switch to the [HTML] view and clean up the code
to look like this:

NOTE: Be sure to remove the width and height attributes in the "CrystalReportViewer"
component tag if they appear.
- Open the "ReportViewer.safe.aspx.vb" code-behind safe class and add the
"CrystalReportViewer"
component class declaration as follows:
Public Class
ReportViewer
Inherits ReportViewerGen
Public Shadows WithEvents CrystalReportViewer1 As
CrystalDecisions.Web.CrystalReportViewer
NOTE:
This declaration allows us to write code in the safe class that references the
"CrystalReportViewer"
component.
- Rebuild the project, exit VS.NET, go back to ISD, and open the
application.
- Open the "ReportViewer" page and switch to the [ASPX] view. Highlight and
copy the following code that was previously set up in VS.NET:

Switch to [HTML] view and paste the code at the top above the "GEN:TEMPLATE" tag.
Switch back to the [ASPX] view and highlight and copy the following code:

Switch to the [HTML] view and paste over (i.e. replace) the following code:

Now you may be saying to yourself, "Wait a minute, I just did that last
part in VS.NET."
Well, yes you did, but ISD uses an HTML file as a template to build the aspx
page whereas VS.NET does not. So, we need to copy the "CrystalReportViewer"
component tag to the HTML file in order for ISD to regenerate the "ReportViewer" aspx page properly in the future.
- Almost done, there's a couple more steps to make sure that Iron Speed Designer compiles
the application properly when using the Visual Basic (vbc.exe) compiler. Copy the following files to your application's
"Bin" directory: "CrystalDecisions.CrystalReports.Engine.dll", "CrystalDecisions.ReportSource.dll",
"CrystalDecisions.Shared.dll", "CrystalDecisions.Web.dll". In the default installation of CR 10 DE, these
files are located at "C:\Program Files\Common Files\Crystal
Decisions\2.5\managed."
- In your application's root directory, open the file
"CompileApplication.rsp" in a text editor. Append the names of the four
Crystal Decisions DLL's to the "/reference:" line as follows (comma delimited, no spaces):
/reference:BaseClasses.dll, BaseClasses.Web.Controls.dll, System.dll,System.Data.dll, System.Drawing.dll, System.Web.dll,
System.Xml.dll, CrystalDecisions.CrystalReports.Engine.dll, CrystalDecisions.ReportSource.dll, CrystalDecisions.Shared.dll, CrystalDecisions.Web.dll
- That's it. You're done with creating the viewer page, but now we need to make it work.
So let's start looking through the microscope to view some tiny code.
|
| Washing Machine - Dynamically Loading |
|
In order for the "CR Viewer" to display a report, the [ReportSource] property must be set to an instance of a
"ReportDocument"
object. The "ReportDocument" object uses the [Load]
method to load the report file from the file system into the object. You could
hard code the name of the report file in the [Load] method. However, you could
also dynamically load a report file by using a
string variable that is set from the [QueryString]. Here's an example in the [Page_Load] event of the
"ReportViewer.safe.aspx.vb"
code-behind safe class:
Private Sub MyPage_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim oRpt As New CrystalDecisions.CrystalReports.Engine.ReportDocument
If (Not Me.IsPostBack) Then
Dim rptFile As String = Request.QueryString.Item("ReportFile")
oRpt.Load(Request.MapPath(Request.ApplicationPath) & "\Reports\" &
rptFile)
CrystalReportViewer1.ReportSource = oRpt
...
The path in the [Load] method specifies that the actual report file is in the "Reports" sub-directory of the virtual
directory. If only our washing machine could dynamically load clothes that easy.
|
| Monopoly – Setting Various Properties |
|
The code above shows how the [ReportSource] property is set; however, there are a
few other "ReportDocument" properties that need to be set prior such as [DatabaseLogon] or perhaps the
[RecordSelectionFormula] and [Parameters]. Here's an
example of how to use the database connection string key in the "web.config" file to set the
[DatabaseLogon] properties of the loaded report:
Dim conStr As String
Dim userID As String
Dim pw As String
Dim serverName As String
Dim dbName As String
Dim i As Integer
Dim j As Integer
' Get db conn settings
conStr = System.Configuration.ConfigurationSettings.AppSettings.Item("Database[YourDatabaseName]1")
' Get user id
i = InStr(1, conStr, "User Id") + 8
j = InStr(i, conStr, ";")
userID = Mid(conStr, i, j - i)
' Get pw
i = InStr(1, conStr, "Password") + 9
j = conStr.Length + 1
pw = Mid(conStr, i, j - i)
' Get server name
i = InStr(1, conStr, "Data Source") + 12
j = InStr(i, conStr, ";")
serverName = Mid(conStr, i, j - i)
' Get db name
i = InStr(1, conStr, "Database") + 9
j = InStr(i, conStr, ";")
dbName = Mid(conStr, i, j - i)
' Set DB login info
oRpt.SetDatabaseLogon(userID, pw, serverName, dbName)
NOTE: Don't forget to replace [YourDatabaseName] above with your actual database
name.
What if your report requires that the "Record Selection Formula" or a parameter be
set dynamically at run-time by the application.
Following are examples of how to set the "Record Selection Formula" and a
parameter by using a [QueryString] and session variable:
Dim rsfVar As String = Request.QueryString.Item("RSFVar")
If oRpt.RecordSelectionFormula = "" Then
oRpt.RecordSelectionFormula = "{table.field} = '" &
rptVar & "'"
Else
oRpt.RecordSelectionFormula += " and {table.field} = '" &
rptVar & "'"
End If
oRpt.SetParameterValue("ParamName",
CStr(Session("VarName")))
When should I use the "Record Selection Formula" instead of a parameter? When using a
parameter in a report, the parameter is automatically included in the SQL
statement's "WHERE" clause, and therefore a parameter value must be provided at
run-time. When modifying the "Record Selection Formula," the SQL statement's "WHERE"
clause is changed dynamically at run-time, which provides some flexibility.
Either way, you get to pass GO and collect $200.
NOTE: If you would like the user to be prompted for the database logon or parameter values, the CR 10 DE
DHTML Viewer will pop up prompt windows if you set the [EnableDatabaseLogonPrompt]
or [EnableParameterPrompt] attributes to "True" in the "CR Viewer" component tag in
the HTML as mentioned earlier.
|
| PostBack Blues - Persistence |
|
So now everything is set and you can display a report in your application,
right? Well, almost -- but not quite. Since the "CR Viewer" is DHTML,
as soon as you click a button on the "CR Viewer" toolbar that causes a postback,
-- poof --, your report is gone. To avoid the "PostBack Blues," you need to persist the
"ReportDocument" object to a
good ol' session variable as such:
Dim oRpt As New CrystalDecisions.CrystalReports.Engine.ReportDocument
If (Not Me.IsPostBack) Then
...
...
Session("ReportDoc") = oRpt
Else
oRpt = CType(Session("ReportDoc"), CrystalDecisions.CrystalReports.Engine.ReportDocument)
CrystalReportViewer1.ReportSource = oRpt
End If
|
| BMW Z3 – Exceptional Handling |
Oops! Something crashed and I got the big nasty stack dump error screen. Why didn't Iron Speed Designer's exception handling catch it?
Unfortunately, the "CR Viewer" component does not expose an event to handle
exceptions, so you have to roll your own by using the [Page_Error] event of the "ReportViewer" page. Also, a lot of CR's supplemental DLL's are still COM-based,
which means non-managed code. The following steps will ensure that your code has
exceptional handling - like your favorite sports car, according to the Business
Objects KBase article
http://support.businessobjects.com/library/kbase/articles/c2015775.asp.
- Build and close the application in ISD.
- Open VS.NET, open your project, and open the "ReportViewer.safe.aspx.vb"
code-behind safe class file.
- Put the following line at the top of the page:
Imports CrystalDecisions.CrystalReports.Engine
- In the editor's [Class Name] drop-down-listbox, select the (Page
Events) option:

- In the editor's [Method Name] drop-down-listbox, select the [Error] option:

- Enter the following code in the [Page_Error] event:
Private Sub
Page_Error(ByVal sender As Object, ByVal e As System.EventArgs) Handles
MyBase.Error
Dim ex As Exception
'Retrieve the last exception that occured
ex = Server.GetLastError()
Server.ClearError()
' Check if the exception is a Crystal Reports EngineException
If TypeOf (ex) Is EngineException Then
' Cast the exception into an
EngineException object
Dim exEngine As EngineException =
CType(ex, EngineException)
' Check the type of error and handle
accordingly
Select Case exEngine.ErrorID
Case
EngineExceptionErrorID.DataSourceError
Response.Write("An error has occurred while connecting to the database.")
Case
EngineExceptionErrorID.ExportingFailed
Response.Write("An error occured while exporting the report.")
Case
EngineExceptionErrorID.MissingParameterFieldCurrentValue
Response.Write("At least one of the parameter fields is missing a current
value.")
Case
EngineExceptionErrorID.LogOnFailed
Response.Write("Incorrect Logon Parameters. Check your user name and password.")
Case
EngineExceptionErrorID.OutOfLicense
Response.Write("There are no more licenses available. Contact your network
administrator.")
Case Else
Response.Write("An undocumented error occurred.")
End Select
Else 'Display the error message
Response.Write(ex.Message)
End If
End Sub
NOTE:
Of course you can be more creative with how you display the error messages.
- Build and close the application in VS.NET.
|
| Goods – Exporting and Displaying |
As I mentioned earlier in the "Microscope - Creating a Viewer Page" section, if
you prefer to skip the whole "CR Viewer" page concept and go directly to PDF,
here's an approach that's a little different than the ISD "Code Customization Wizard" example. This
simplified approach uses the [ExportToHttpResponse] method of the "ReportDocument"
object to stream the binary PDF document to the browser, which avoids the
"Malicious Code" popup warning message in IE as detailed in the Business Objects
KBase article:
http://support.businessobjects.com/library/kbase/articles/c2016725.asp.
Follow all of the steps and code from the prior sections except "Microscope -
Creating a Viewer Page" and "PostBack Blues - Persistence." Remove the following code fragments from the [Page_Load]
event since they are not necessary:
- The "If (Not Me.IsPostBack) Then" structure
- The "CrystalReportViewer1.ReportSource = oRpt" statements
Then insert this code at the end of the [Page_Load] event.
...
...
Response.ClearContent()
Response.ClearHeaders()
Response.ContentType = "application/pdf"
' Export the document to PDF
oRpt.ExportToHttpResponse(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat, Response, False, "")
Response.Flush()
Response.Close()
End Sub
|
| Houdini – Tips and Tricks |
I'm no Houdini magician of coding, but here are a few tips and tricks that I've used over the years:
Tip #1: When you click the [Print] button on the "CR Viewer" toolbar, the "Print
Options" popup window will appear. Unfortunately, the default print page range is
"1 to 1" instead of "All", which drove me crazy. Also, step #1 under the "To Print"
area mentions, "select the 'Open this file' option," which is now obsolete. So,
here's a few steps to fix this confused popup window:
- Open the following file: "C:\Program Files\Common Files\Crystal Decisions\2.5\CrystalReportViewers10\js\export.js" (default location for CR 10 DE)
in your favorite JavaScript editor.
- Find the following code in the getExportDialog() function:

- Remove the "checked" attribute from "radio2" and place it
above in the "radio1" input type as such:

- Save and close the file.
- Open the strings_en.js (for English) file in the same directory and find
the following code:
var L_PrintStep1 = "1. In the next dialog that appears, select the \"Open this file\" option and click the OK button.";
And change the text to read:
var L_PrintStep1 = "1. Click the OK button below and the report will be displayed in Acrobat Reader.";
Tip #2: Since the "Print Options" popup window mentioned above is in fact a popup, end-users will need to disable popup blockers for this window.
Trick #1: On my reports, I like to include the User ID and date/time
stamp. To accomplish this, I place the CR Special Fields - [File Author], [Print
Date], and [Print Time] in the report footer. Here's the sample code to
set the [File Author] property in the "ReportViewer.safe.aspx.vb" code-behind safe class
so that it displays on the report:
...
Dim uu As New UserUtils
oRpt.SummaryInfo.ReportAuthor = uu.UserID
...
CrystalReportViewer1.ReportSource = oRpt
...
"UserUtils" is a little helper class that I created to get the User ID and other properties from the Session.
|
| Conclusion |
|
Well, here we are at the conclusion finally, and now you have a way to integrate Crystal Reports right into your Iron Speed Designer application. Putting all of the
code snippets together from this article, the [Page_Load] event of your "ReportViewer" page should look something like this:
Private Sub MyPage_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
Dim oRpt As New CrystalDecisions.CrystalReports.Engine.ReportDocument
If (Not Me.IsPostBack) Then
' Additional initialization can be performed here.
Dim rptFile As String = Request.QueryString.Item("ReportFile")
oRpt.Load(Request.MapPath(Request.ApplicationPath) & "\Reports\" & rptFile)
Dim conStr As String
Dim userID As String
Dim pw As String
Dim serverName As String
Dim dbName As String
Dim i As Integer
Dim j As Integer
' Get db conn settings
conStr = System.Configuration.ConfigurationSettings.AppSettings.Item("DatabaseNorthwind1")
' Get user id
i = InStr(1, conStr, "User Id") + 8
j = InStr(i, conStr, ";")
userID = Mid(conStr, i, j - i)
' Get pw
i = InStr(1, conStr, "Password") + 9
j = conStr.Length + 1
pw = Mid(conStr, i, j - i)
' Get server name
i = InStr(1, conStr, "Data Source") + 12
j = InStr(i, conStr, ";")
serverName = Mid(conStr, i, j - i)
' Get db name
i = InStr(1, conStr, "Database") + 9
j = InStr(i, conStr, ";")
dbName = Mid(conStr, i, j - i)
' Set DB login info
oRpt.SetDatabaseLogon(userID, pw, serverName, dbName)
' Set Record Selection Formula
Dim rsfVar As String = Request.QueryString.Item("RSFVar")
If oRpt.RecordSelectionFormula = "" Then
oRpt.RecordSelectionFormula = "{Customers.CustomerName} = '" & rptVar & "'"
Else
oRpt.RecordSelectionFormula += " and {Customers.CustomerName} = '" & rptVar & "'"
End If
' Set CR parameter
oRpt.SetParameterValue("CustomerName", CStr(Session("CustName")))
Dim uu As New UserUtils
oRpt.SummaryInfo.ReportAuthor = uu.UserID
CrystalReportViewer1.ReportSource = oRpt
Session("ReportDoc") = oRpt
Else
oRpt = CType(Session("ReportDoc"), CrystalDecisions.CrystalReports.Engine.ReportDocument)
CrystalReportViewer1.ReportSource = oRpt
End If
End Sub
When you're all done, your application "ReportViewer" page may display a report like this:

As you can see in the sample screen shot above, the "CR Viewer" is fairly generic, so impress your customers with a little magical creativity of your own.
Since it took Business Objects over 3 months to ship CR XI DE to me -- too
late -- I may write a brief update to this article regarding CR XI DE in the
near future. In the mean time, enjoy the "CR Viewer", and feel free to post any
questions in the Iron Speed Designer User Forums.
|
| About The Author |
Michael Sumption is President and CEO of GovIS, LLC, a software development and
consulting firm in Northern California. Michael has been developing applications
with Crystal Reports for 10+ years and has been using Iron Speed Designer for
one year.
www.govis.com
|
|
|
|