I’m currently working on a client’s project on migrating an important management application initially developed in VB6 to C# by using decompilation since the client lost his source code. This is my first .Net related post.

The application uses many external components, including the powerful Crystal Report component.

While I was working on this new version I faced a problem with the .Net implementation of Crystal Report.

Actually, with Visual Basic 6.0 it was an easy task to add one or many sort fields to a report document in order to sort the result by field (for example by “Lastname”, “Firstname”, “Id”, or other).

It’s not the case with .Net implementation of Crystal Report Document, on which you cannot dynamically add sort field to your report. You must add the sort field when you’re editing your report template. It’s not really convenient when you don’t know how many fields must be sorted when creating the document.

The reason of this missing is not really clear since it’s an existing feature on Visual Basic 6.0. We will learn here how to add many SortField to the ReportDocument of a CrystalReportViewer, using reflection.

Imagine you have to sort the request’s result by “Firstname”, “Lastname”, and “Id” fields, using the Crystal report document with the variable “Filename” as the report document path, and the variable “srcDataSet” dataset as the data source:

string[] sortFieldNames = new string[] { "Firstname", "Lastname", "ID" };

ReportDocument reportDocument = new ReportDocument();

reportDocument.FileName = String.Format("rassdk://{0}", Filename);

reportDocument.SetDataSource(srcDataSet.Tables[0]);

SortFields targetSortField = reportDocument.DataDefinition.SortFields;

 

Here we can modify the sort field (only if existing in the document):

DatabaseFieldDefinition fieldDef = reportDocument.Database.Tables["REPORTS"].Fields["FIRSTNAME"];

targetSortField[0].Field = fieldDef;

targetSortField[0].SortDirection = CrystalDecisions.Shared.SortDirection.AscendingOrder;

 

But impossible to add directly a new field to that SortFields collection because no method allows performing action:

pic1

Actually, it’s possible to add a new sort field using the mother inherited class of each used component.

First we get the “SortFields” mother’s class of type (CrystalDecisions.ReportAppServer.DataDefModel.SortsClass ) by using the RasSorts private accessor of the SortFields object:

MethodInfo getRasSorts = targetSortField.GetType().GetMethod("get_RasSorts", BindingFlags.NonPublic | BindingFlags.Instance);

object rasSorts = getRasSorts.Invoke(targetSortField, System.Type.EmptyTypes);

 

It return an object of type CrystalDecisions.ReportAppServer.DataDefModel.SortsClass.

Now we can access to the private “Add” method of the mother’s class:

MethodInfo addSort = rasSorts.GetType().GetMethod("Add");

 

The description of that method show that it accept one parameter of type CrystalDecisions.ReportAppServer.DataDefModel.ISCRSort.

pic2

ISCRSort is an interface, so we have to create a new instance of a class that implement the ISCRSort interface. That class is called “SortClass” and come from the “CrystalDecisions.ReportAppServer.DataDefModel” assembly.

Assembly rasAssembly = getRasSorts.ReturnType.Assembly;

ConstructorInfo ciRasSort = rasAssembly.GetType("CrystalDecisions.ReportAppServer.DataDefModel.SortClass").GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, System.Type.EmptyTypes, null);

object rasSort = ciRasSort.Invoke(System.Type.EmptyTypes);

 

Our “rasSort” object now instantiate the SortClass class, implementing ISCRSort.
Once the SortClass object is created, we must complete it with valid field, else it will return an error on execution.

The SortClass class uses the “RasField” accessor to define the field which will be sorted.

MethodInfo setSortField = rasSort.GetType().GetMethod("set_SortField", BindingFlags.Public |BindingFlags.Instance);

 

But this method only accepts an object implementing the ISCRField interface:
Void set_SortField(CrystalDecisions.ReportAppServer.DataDefModel.ISCRField);

So we have to extract the object of our field object (called fieldDef) that implement the ISCRField interface, it can be done with the “RasField” accessor of our DatabaseFieldDefinition object called “fieldDef”:

MethodInfo getRasField = fieldDef.GetType().GetMethod("get_RasField", BindingFlags.NonPublic |BindingFlags.Instance);

object rasField = getRasField.Invoke(fieldDef, System.Type.EmptyTypes);

 

And now, the final touch, we add it to the collection:

addSort.Invoke(rasSorts, new object[] { rasSort });

 

Your new sort field has been adding to the SortFields collection, and you can now modify it:

int n = reportDocument.DataDefinition.SortFields.Count - 1;

reportDocument.DataDefinition.SortFields[n].SortDirection = CrystalDecisions.Shared.SortDirection.AscendingOrder;
reportDocument.DataDefinition.SortFields[n].Field = fieldDef;

 

That’s all.