Open a Report in a Crystal Report Viewer from Code

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.)

VS2008_OpenAReport_Projects

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.

VS2008_OpenAReport_Form

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.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DotNetKicks
  • HackerNews
  • LinkedIn
  • StumbleUpon
  • Technorati
  • Tumblr
  • Twitter

Embedding a File in Visual Studio 2008

One of the most frustrating things about Crystal Reports is how challenging it is to create a report on the fly. I mean really on the fly. Like from scratch. Many people have said it can’t be done. With Crystal Reports 2008 (CR XII) it can be done, but only with the use of a report server. Or at least that is the best that I have been able to determine so far. The trouble is, most of us don’t have dedicated Crystal Report Servers, or the desire to set them up.

SAP says in it’s help files, that we can create a report on the fly using the Report Application Server (RAS), which runs on the client side. I don’t know if they’ve tried to actually do this, but nobody I know has gotten it to work. That said, there’s a very easy way of getting around this. Save a blank .rpt and embed it into your project. Now whenever you need to create a report, you simply use the embedded completely blank .rpt to start, and go from there.

Of course, this means you need to be able to embed a file, write it out to disk temporarily, and then read it back in. This page will walk you through this process.

For the purposes of this Tutorial I will assume the following things:

  1. You already have the file you want to embed and know it’s disk location.
  2. Your project will have access to a disk location to temporarily write the file to.

Create a project or a class to handle your embedded file.

Add your file to your project by right-clicking in the Solution Explorer panel where you’d like the file to be emedded. In my case it’s in my EmbeddedFiles project.

Select “Add”–>”Existing Item”

VS2008_Embedded_File_addFile

Navigate to the location of your File in the File Dialog. You may need to change the filter to All Documents .* in order to see your file.

VS2008_Embedded_File_addFile_Dialog

Select your file and click “Add”

Right-click on your new file and select properties.

VS2008_Embedded_File_fileProperties

Verify that the new file is set to the following properties:

Build Action: Embedded Resource
Copy to Output Directory: Do Not Copy

VS2008_Emvedded_File_fileProperties_Dialog

Add a Resource File to your project.

VS2008_Embedded_File_addResource

VS2008_Embedded_File_addResource_Dialog

Open your resource file and add a value baseFilePath with the namespace of your project up through the location of your file. For my project it’s “theogeer.EmeddedFiles.file.”. This will allow you to put as many files as you want in this location and call them using the same baseFilePath value.

VS2008_Embedded_File_Resource

Once you set all that up we can write our class. The concept is really simple. When the class is constructed it will take the embedded file and write it to disk. When the class is destructed it will delete the file. While the object you’ve created exists you can access it using System.IO.File as with any other file on disk.

Let’s start by setting up a string value to hold the FilePath of the temporary file on disk. I’ll use a private string for the defining path, and expose it publicly as TemporaryFilePath. In order to assure that we don’t mess ourselves up by calling the same embedded file twice we’ll add a timestamp to the file by using a method to set the temporary path.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <summary>
/// temporaryFilePath is the private string where we will write the temporary version of the file.
/// </summary>
private string temporaryFilePath;
/// <summary>
/// The TemporaryFilePath for the temporary version of the file.
/// </summary>
public string TemporaryFilePath
{
	get { return temporaryFilePath; }
}
/// <summary>
/// Set the temporary fileName with a unique timestamp to prevent a conflict
/// </summary>
/// <param name="fileName">The file name</param>
private void setTemporaryPath(string fileName)
{
	string folder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:\\", "");
	string timestamp = DateTime.Now.ToString("_yyyy_MM_dd_hh_mm_ss_ffff");
 
	fileName = fileName.Insert(fileName.LastIndexOf("."), timestamp);
 
	temporaryFilePath = string.Concat(folder, Path.DirectorySeparatorChar, fileName);
}

For our constructor we need to create a stream reader from our embedded file, and use a binary reader and writer to to write the embedded file out byte by byte.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// <summary>
/// Retrieve an Embedded File from the project
/// </summary>
/// <param name="fileName">The name of the embedded resource to write out to disk.</param>
public RetrieveEmeddedFile(string fileName)
{
	string internalPath = string.Concat(resource.baseFilePath, fileName);
	setTemporaryPath(fileName);
 
	FileStream fs = new FileStream(temporaryFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
	Stream reader = this.GetType().Assembly.GetManifestResourceStream(internalPath);
 
	BinaryReader br = new BinaryReader(reader);
	BinaryWriter bw = new BinaryWriter(fs);
 
	for (int i = 0; i < reader.Length; i++)
		bw.Write(br.ReadByte());
 
	br.Close();
	bw.Close();
}

Because we write a file to disk on Construction, we need to delete that file on Destruction.

1
2
3
4
5
~RetrieveEmeddedFile()
{
	if (File.Exists(temporaryFilePath))
		File.Delete(temporaryFilePath);
}

Below is the entirety of my class. We could add a lot more to this. I considered a CopyTo function that would implement File.Copy() but decided KISS was the way to go here.

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
 
namespace theogeer.EmbeddedFiles
{
	/// <summary>
	/// Get a temporary file from an embedded resource.
	/// </summary>
	public class RetrieveEmeddedFile
	{
		/// <summary>
		/// temporaryFilePath is the private string where we will write the temporary version of the file.
		/// </summary>
		private string temporaryFilePath;
		/// <summary>
		/// The TemporaryFilePath for the temporary version of the file.
		/// </summary>
		public string TemporaryFilePath
		{
			get { return temporaryFilePath; }
		}
		/// <summary>
		/// Set the temporary fileName with a unique timestamp to prevent a conflict
		/// </summary>
		/// <param name="fileName">The file name</param>
		private void setTemporaryPath(string fileName)
		{
			string folder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:\\", "");
			string timestamp = DateTime.Now.ToString("_yyyy_MM_dd_hh_mm_ss_ffff");
 
			fileName = fileName.Insert(fileName.LastIndexOf("."), timestamp);
 
			temporaryFilePath = string.Concat(folder, Path.DirectorySeparatorChar, fileName);
		}
		/// <summary>
		/// Retrieve an Embedded File from the project
		/// </summary>
		/// <param name="fileName">The name of the embedded resource to write out to disk.</param>
		public RetrieveEmeddedFile(string fileName)
		{
			string internalPath = string.Concat(resource.baseFilePath, fileName);
			setTemporaryPath(fileName);
 
			FileStream fs = new FileStream(temporaryFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
			Stream reader = this.GetType().Assembly.GetManifestResourceStream(internalPath);
 
			BinaryReader br = new BinaryReader(reader);
			BinaryWriter bw = new BinaryWriter(fs);
 
			for (int i = 0; i < reader.Length; i++)
				bw.Write(br.ReadByte());
 
			br.Close();
			bw.Close();
		}
		~RetrieveEmeddedFile()
		{
			if (File.Exists(temporaryFilePath))
				File.Delete(temporaryFilePath);
		}
	}
}

Here is the Visual Studio 2008 project file for the project this far.
Click here to download.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DotNetKicks
  • HackerNews
  • LinkedIn
  • StumbleUpon
  • Technorati
  • Tumblr
  • Twitter