Implementing and embedding Crystal Reports in the .Net environment is extremely easy to do. Once the tools are downloaded from the businessobject website creating and editing reports from Visual Studio is a breeze. We are going to be covering the caching functionality that the Crystal Report framework provides through the ICachedReport interface.
What you will learn
- What is the ICachedReport interface.
- System generated classes and when to modify them.
- Adding extensions to support ConnectionInfo binding.
- Putting it all together.
ICachedReport Interface
The ICachedReport interface will work like a flag to signal the Crystal Report framework that the report should be cache. It works by creating a layer ontop of the Asp.net Cache object to accommodate the needs of the report. The interface is found in the CrystalDecisions.ReportSource namespace.
System Generated Classes (Benefits of embedding)
Using Visual Studio to add a report as a new item will generate a report wrapper class with the name of the report. The second class will be the management class named Cached[ReportName]. Visual Studio will generate both classes in the same file (ReportName.cs). Below you will see an example of a generated class for a report called SalesDirectory. For the most part this class will expose everything needed to work with the report without any changes. In some cases when using the Cached class properties will need to be added to support parameters.
namespace Optimized.Reports {
using
System; using
System.ComponentModel; using
CrystalDecisions.Shared; using
CrystalDecisions.ReportSource; using
CrystalDecisions.CrystalReports.Engine;
public class
SalesDirectory : ReportClass
{…}
[System.Drawing.ToolboxBitmapAttribute(typeof(CrystalDecisions.Shared.ExportOptions), "report.bmp")] public class CachedSalesDirectory
: Component, IcachedReport
{…}
}
|
Extension Methods for Report
What you will often find is that if the report is not properly authenticated, it will prompt the user everytime the report is loaded. What we will do here is leverage the ConnectionInfo object and create an extension method for the Tables inside the report.
using
CrystalDecisions.CrystalReports.Engine;
using
CrystalDecisions.Shared;
namespace Core.Util
{
public static class Extensions
{
/// <summary>
/// Set Crystal Report ConnectionInfo.
/// </summary>
/// <param
name="tables">CrystalDecisions.CrystalReports.Engine.Tables</param>
public
static void
SetLocation(this Tables
tables)
{
ConnectionInfo
connectionInfo = new ConnectionInfo();
connectionInfo.ServerName =
ConfigurationManager.AppSettings["CrystalServerName"].ToString();
connectionInfo.DatabaseName =
ConfigurationManager.AppSettings["CrystalDatabaseName"].ToString();
connectionInfo.UserID =
ConfigurationManager.AppSettings["CrystalUserID"].ToString();
connectionInfo.Password =
ConfigurationManager.AppSettings["CrystalPassword"].ToString();
connectionInfo.IntegratedSecurity
=
Convert.ToBoolean(
ConfigurationManager.AppSettings["CrystalIntegratedSecurity"]);
foreach
(CrystalDecisions.CrystalReports.Engine.Table
table in tables)
{
TableLogOnInfo
tableLogOnInfo = table.LogOnInfo;
tableLogOnInfo.ConnectionInfo
= connectionInfo;
table.ApplyLogOnInfo(tableLogOnInfo);
}
}
}
}
|
In the example the values are kept in the WebConfig, but it is not a requirement. If the namespace for the Extension class and the pages that have the controls are not the same-it must be added in order for the method to show.
Putting It Together
Now that we have our SalesDirectory report with the wrapper and utility class, we are going to create a page to hold a report viewer. Below is the code listing for adding the directive to the page and immediately after the declaration for the control.
<%@
Register assembly="CrystalDecisions.Web, Version=13.0.2000.0,
Culture=neutral, PublicKeyToken=692fbea5521e1304" namespace="CrystalDecisions.Web"
tagprefix="CR"
%>
<CR:CrystalReportViewer ID="CrystalReportViewer"
runat="server"
AutoDataBind="true" Visible="true" />
|
With the report viewer in place the last thing we need to do is create and bind the report to the viewer. We need to cover the difference in how you would setup both approaches so that we can compare performance and also because leveraging the caching management class requires an extra step.
// Without Caching -
To be able to compare performance differences.
private void DisplayDirectoryReport()
{
SalesDirectory
directoryReport = new SalesDirectory();
directoryReport.Database.Tables.SetLocation(); // Set Connection
// Set the location for any subreport
foreach
(CrystalDecisions.CrystalReports.Engine.ReportDocument
rDocument in
directoryReport.Subreports)
rDocument.Database.Tables.SetLocation();
CrystalReportViewer.ReportSource =
directoryReport;
CrystalReportViewer.DataBind();
}
|
// Implementing the
caching management class.
private void DisplayDirectoryReport()
{
Reports.CachedSalesDirectory
cachedSalesDirectory = new
Reports.CachedSalesDirectory();
// Extra step
(part of the interface and for the most part will have all the code
// needed.
cachedSalesDirectory.CreateReport();
CrystalReportViewer.ReportSource =
cachedSalesDirectory;
CrystalReportViewer.DataBind();
}
// Inside CachedSalesDirectory
code in red is the code that needs to be added.
public virtual CrystalDecisions.CrystalReports.Engine.ReportDocument CreateReport() {
SalesDirectory rpt = new
SalesDirectory();
rpt.Site = this.Site;
rpt.Database.Tables.SetLocation();
foreach
(CrystalDecisions.CrystalReports.Engine.ReportDocument rDocument in
rpt.Subreports)
rDocument.Database.Tables.SetLocation();
return rpt;
}
|
The code listing above covers most cases, but what if the report contains parameters. The last listing is going to show how parameters could be streamlined into the Cached[ReportName] class generated for you. Following the example of the SalesDirectory report, we are going to add two properties to the CachedSalesDirectory class.
[System.Drawing.ToolboxBitmapAttribute(typeof(CrystalDecisions.Shared.ExportOptions), "report.bmp")]
public class CachedSalesDirectory
: Component, ICachedReport
{
public string
Counties { get; set;
}
public
int StateID { get;
set; }
…}
|
Next
step is to set the new properties before calling create.
// Implementing the
caching management class.
private void DisplayDirectoryReport(string counties, int StateID)
{
Reports.CachedSalesDirectory
cachedSalesDirectory = new
Reports.CachedSalesDirectory();
cachedSalesDirectory.Counties
= counties;
cachedSalesDirectory.StateID = StateID;
cachedSalesDirectory.CreateReport();
CrystalReportViewer.ReportSource =
cachedSalesDirectory;
CrystalReportViewer.DataBind();
}
|
Finally
we will change the CreateReport function to account for the parameters.
// Inside CachedSalesDirectory
code in red is the code that needs to be added.
public virtual CrystalDecisions.CrystalReports.Engine.ReportDocument CreateReport() {
SalesDirectory rpt = new
SalesDirectory();
rpt.Site = this.Site;
rpt.SetParameterValue("@Counties",
Counties);
rpt.SetParameterValue("@StateID",
StateID);
foreach
(CrystalDecisions.CrystalReports.Engine.ReportDocument
rDocument in
rpt.Subreports)
rDocument.Database.Tables.SetLocation();
return rpt;
}
|
In conclusion, we have covered a feature of the Crystal Report and .Net framework that allows for faster loading, paging, and grouping of Crystal Report.
Implementing both solutions will prove the efficiency of leveraging the caching functionality.