Now we have a project that has emedded files supported that we can retrieve pretty easily. The next thing, and perhaps most important thing we can do, is open and view a crystal report from code.
For the purposes of this tutorial I’ll be opening the blank report that we embedded in the previous post. This method can be used to open any .rpt file that can be handled by the CrystalReportViewer control. Which is to say, any report as far as I’m aware.
If you already have a library or folder full of working reports, this will allow you to serve them inside your C# .Net application.
I’ve added two class library projects to our solution. ReportDefinition and ReportGenerator. For the purposes of this post we will only need Report Generator. (If all you want to do is serve a report that already exists, you don’t really need a report generator class at all, as you’ll see. We’re building it now because we’ll be using it for the larger project.)

Open your ReportGenerator class and add the using statements for CrystalDecisions.ReportAppServer at the top.
1 2 3 4 5 6 7 8 9 | using ENGINE=CrystalDecisions.CrystalReports.Engine; using CrystalDecisions.ReportAppServer.ClientDoc; using CrystalDecisions.ReportAppServer.Controllers; using CrystalDecisions.ReportAppServer.DataDefModel; using CrystalDecisions.ReportAppServer.ReportDefModel; using CrystalDecisions.ReportAppServer.CommonObjectModel; using CrystalDecisions.ReportAppServer.ObjectFactory; using theogeer.EmbeddedFiles; |
Notice that I aliased the using line for CrystalDecisions.CrystalReports.Engine. One of the big challenges we face with Crystal is that their object model is spread out quite a bit with a great many dependencies and lots of interfaces and classes that look identical, but vary just enough to not work if you use the wrong one.
The two objects we’re concerned with at this stage are the ReportDocument object and the ReportClientDocument Object.
The ReportDocument is quite literally the container that holds the actual report you are running. The secret here, is that we don’t care. All the properties of ReportDocument are a pale imitation of the properties that belong to ReportClientDocument. If you add something to ReportDocument, nothing changes. If you add it to ReportClientDocument it bubbles up. As a result, the vast majority of the work we’ll be doing with the Crystal Object Model is actually going to be done within the ReportClientDocument object.
For now though, all we want to do is open a report, and load it into a Crystal Report Viewer. To do this we need our ReportGenerator class to open a report and give us access to the ReportSource object, which belongs to ReportClientDocument.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class ReportGenerator { #region Properties /// <summary> /// The Report Document is the top level object of any crystal report. /// </summary> private ENGINE.ReportDocument ReportDocument { get; private set; } /// <summary> /// The Report Client Document is a sub-object of the Report Document. /// It is the primary object that is used when laying out a report. /// </summary> public ISCDReportClientDocument ReportClientDocument { get; private set; } #endregion #region Constructors /// <summary> /// The ReportGenerator takes the ReportDefinition provided and translates it to a Crystal Report /// that can be run, or saved as an .RPT file. /// </summary> public ReportGenerator() { InitializeNewReport(); } #endregion #region Methods /// <summary> /// Accesses a blank report stored in EmbeddedFiles. Sets the Report Document /// and Report Client Document. /// </summary> private void InitializeNewReport() { EmbeddedFileRetriever fileRetriever = new EmbeddedFileRetriever("CrystalReport.rpt"); this.ReportDocument = new ENGINE.ReportDocument(); this.ReportDocument.Load(fileRetriever.TemporaryFilePath); this.ReportClientDocument = this.ReportDocument.ReportClientDocument; } #endregion } |
Take a close look at the definition for ReportClientDocument. It is actually defined as an ISCDReportClientDocument type. Technically this is an interface, not a class. However Crystal often treats interfaces just like classes. I’m not sure why. The actual ReportClientDocument class can not be accessed from ReportDocument, but ISCDReportClientDocument can.
Again, I’m opening a blank crystal report that I have embedded into the solution. If you’re opening an external report, pass in the file-Path to that .rpt file to open it instead.
Now we have a method of opening a report and accessing its ReportClientDocument. To display the report we need a form that contains a CrystalReportViewer. I created a simple windows form inside the console application that I use to test and run the application. No code needed to be written. Create a new Form and insert a CrystalReportViewer object onto it.
Note: In order to open a windows form from a console app you need to add System.Windows.Forms to the using statements, and change a couple of Application settings at the beginning of your program.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.IO; using CrystalDecisions.ReportAppServer.ClientDoc; using CrystalDecisions.ReportAppServer.Controllers; using CrystalDecisions.ReportAppServer.DataDefModel; using CrystalDecisions.ReportAppServer.ReportDefModel; using CrystalDecisions.ReportAppServer.CommonObjectModel; using CrystalDecisions.ReportAppServer.ObjectFactory; using CrystalDecisions.Windows.Forms; using theogeer.EmbeddedFiles; using theogeer.ReportGenerator; namespace main { class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); ReportGenerator gen = new ReportGenerator(); ReportViewerForm v = new ReportViewerForm(); CrystalReportViewer crv = (CrystalReportViewer)v.Controls["CrystalReportViewer"]; crv.ReportSource = gen.ReportClientDocument.ReportSource; Application.Run(v); } } } |
Here is the completed solution as it stands so far. Click here to download.