29 September 2007

Crystal Unclear Nightmares

by mo

Yesterday I had the privelage of creating my first report using Crystal Reports. I had never used it prior to then, and I thought “how hard could this be”, after all I was just generating a pdf. How hard should that be?

Well it turns out, that it was much harder then I expected. Crystal is one huge beast, with one of the craziest object models I’ve seen so far. Who the heck wrote this stuff?

It would be nice if I could just include the crystal libraries in our project and just xcopy the libraries without having to install anything, but that’s not the case. I can XCopy the following assembly’s:

  • CrystalDecisions.CrystalReports.Engine
  • CrystalDecisions.ReportSource
  • CrystalDecisions.Shared
  • CrystalDecisions.Windows.Forms

But the following assembly’s are stored up in the GAC, when you install Crystal to. (Perhaps I need to look into pulling assembly’s out of the GAC and just including them with the project, but something tells me that this might be a dead end.)

  • CrystalDecisions.Enterprise.Framework
  • CrystalDecisions.Enterprise.InfoStore

Oh fine… next up is poking at the object model. Whenever you create a new “Crystal Report” it generates an “.rpt” file and a code behind for that “.rpt” file. The code behind contains a definition for a class that represents that report. This class inherits from a type called “ReportClass”, which inherits from a type called “ReportDocument”.

  public class MyReport : ReportClass { ... }

The following photo kind of sums up the nightmare I had yesterday in learning Crystal Reports.

  • Report Document has a property of type DataDefinition.
  • DataDefinition has a propertycalled ParameterFields of type ParameterFieldDefinitions.
  • ParameterFieldDefinitions has a few indexers to access its collection of “ParameterFieldDefinition”.
  • ParameterFieldDefinition has a method called “ApplyCurrentValues(IParameterValues)” and an overload of the same method that takes in “ParameterValues”.

Why is this overload necessary? Well because ParameterValues does not implement IParameterValues. (Huh?)

So let’s dig into this a little bit deeper…. ParameterValues inherits from Arraylist.

  public class ParameterValues : ArrayList{}

IParameterValues implements IList.

  public class IParameterValues : IList{}

And ArrayList implement IList.

ParameterValues does not implement IParameterValues… so I CANT treat a ParameterValues type as an IParameterValues type, hence the overloaded method above. One takes in a ParameterValues type and the other a IParameterValues type. So how do I bridge this?

I create a type that implements IParameterValues and inherits from ParameterValues and I now have a single type that satisfies both, which in my opinion should have been done in the first place… but wait there’s more!

  internal class Parameters : ParameterValues, IParameterValues 
    public ParameterRangeValue AddRangeValue( object lowerBound, object upperBound, RangeBoundType lowerBoundType, RangeBoundType upperBoundType )
      throw new NotImplementedException( );

The IParameterValues interface has one additional method that is not defined in ParameterValues. (ooooh man!)

All of this has left my head spinning… Must things be so complicated?

Did I mention lack of strong typing, yes I know generics didn’t show up until .NET 2.0 but event still there were ways to create more strongly typed objects in the 1.1. days, that’s no excuse to use type object everywhere.

And please, please, consider using composition over inheritance, it simplifies things sooo very much. When I started inspecting Crystal in reflector, I first focused on the interfaces to get an overall sense for how the beast works, but if the types I expect to be implementing the interfaces aren’t, then what’s the point of having them? If this is your idea of open for extension, I think it’s rathersilly. But who am i to judge, i’m but a simple jr. developer.

Moving on… I was experiencing some funny business when I was binding my report to a crystal report viewer. (I don’t know if I mentioned this yet, but this is for a desktop application.) And the stack trace ended up leading me to a method called Load defined in the ReportClass. This is an override of a virtual Load method defined in the ReportDocument class.

This following line of code made me vomit a little bit in my mouth.

The load method is checking to see if the HttpContext.Current value is equal to null. (Remember this is running ina winforms application.) This means that crystal is loading the System.Web assembly up into my app domain so that it can check the HttpContext.Current value. This is not what I would expect, or want.

Well I have probably ranted enough for one morning… I can’t believe how long I have just spent crafting up this post, but I really don’t like Crystal Reports. At the end of the day, all I want to do is generate a pdf with text and images on it.

“Can It Be All So Simple” - Wu-Tang Clan