Crystal Reports Extension Methods


Very frequently I field Crystal Reports questions and often most of the questions revolve around how to update the database connection at runtime from code. Over the last 8 years, I’ve come up with some useful code to help me along that I’ve now turned into extension methods to make working with Crystal Reports ReportDocument objects much simpler. Each method has XML comments that describe what it does. Provided are ways to change the database connection at run time, change the database name at runtime, easily see if a parameter exists, apply a parameter string that will be converted into Crystal parameters and applied. In a few cases better Exceptions are thrown when errors occur that will point you to why (as opposed to the unhelpful general COM errors that get thrown in a lot of cases by Crystal).

My code was in VB.Net. For convenience, I’m going to run it through a code converter and share the C# output also (though the c# may require slight tweaks and won’t be thoroughly tested by me). It should get your 99% of the way there though. When including this code in your project, you’ll want to “Import Extensions” in VB or “using Extensions” in C# to make it available from wherever you’re calling it.

Brief example of how to use this code to change a connection string at runtime:

Using rd As New ReportDocument
rd.Load("C:TempCrystalReportsInternalAccountReport.rpt")
rd.ApplyNewServer("serverName or DSN", "databaseUsername", "databasePassword")
rd.ApplyParameters("AccountNumber=038PQRX922;", True)
rd.ExportToDisk(ExportFormatType.PortableDocFormat, "c:temptest.pdf")
rd.Close()
End Using

System.Diagnostics.Process.Start("c:temptest.pdf")

VB.Net

Imports System.Runtime.CompilerServices
Imports CrystalDecisions.CrystalReports.Engine
Imports CrystalDecisions.Shared
Imports CrystalDecisions.CrystalReports
Namespace Extensions

    ''' <summary>
    ''' A set of extension methods to make manually working with Crystal Reports easier.
    ''' </summary>
    ''' <remarks>
    ''' Pieces of this code started in March of 2004 and have evolved over the last 8 years.
    ''' </remarks>
    Public Module CrystalReportExtensions
        '*********************************************************************************************************************
        '
        '            Module:  CrystalReportExtensions
        '      Initial Date:  03/26/2004
        '      Last Updated:  05/22/2012
        '    Public Release:  1
        '     Programmer(s):  Blake Pell, blakepell@hotmail.com, http://www.blakepell.com
        '
        '                     This code is free to use for all personal and commercial uses as long as this header remains
        '                     in tact.  
        '
        '*********************************************************************************************************************

        ''' <summary>
        ''' Applies a new server name, SQL username and password to a ReportDocument.  This method can be used with any number
        ''' of database providers.
        ''' </summary>
        <Extension()> _
        Public Sub ApplyNewServer(ByVal report As ReportDocument, serverName As String, username As String, password As String)
            For Each subReport As ReportDocument In report.Subreports
                For Each crTable As Table In subReport.Database.Tables
                    Dim loi As TableLogOnInfo = crTable.LogOnInfo
                    loi.ConnectionInfo.ServerName = serverName
                    loi.ConnectionInfo.UserID = username
                    loi.ConnectionInfo.Password = password
                    crTable.ApplyLogOnInfo(loi)
                Next
            Next
            'Loop through each table in the report and apply the new login information (in our case, a DSN)
            For Each crTable As Table In report.Database.Tables
                Dim loi As TableLogOnInfo = crTable.LogOnInfo
                loi.ConnectionInfo.ServerName = serverName
                loi.ConnectionInfo.UserID = username
                loi.ConnectionInfo.Password = password
                crTable.ApplyLogOnInfo(loi)
                'If your DatabaseName is changing at runtime, specify the table location. 
                'crTable.Location = ci.DatabaseName & ".dbo." & crTable.Location.Substring(crTable.Location.LastIndexOf(".") + 1)
            Next
        End Sub

        ''' <summary>
        ''' Applies a new server name to the ReportDocument.  This method is SQL Server specific if integratedSecurity is True.
        ''' </summary>
        ''' <param name="report"></param>
        ''' <param name="serverName">The name of the new server.</param>
        ''' <param name="integratedSecurity">Whether or not to apply integrated security to the ReportDocument.</param>
        <Extension()> _
        Public Sub ApplyNewServer(report As ReportDocument, serverName As String, integratedSecurity As Boolean)
            For Each subReport As ReportDocument In report.Subreports
                For Each crTable As Table In subReport.Database.Tables
                    Dim loi As TableLogOnInfo = crTable.LogOnInfo
                    loi.ConnectionInfo.ServerName = serverName
                    If integratedSecurity = True Then
                        loi.ConnectionInfo.IntegratedSecurity = True
                    End If
                    crTable.ApplyLogOnInfo(loi)
                Next
            Next
            'Loop through each table in the report and apply the new login information (in our case, a DSN)
            For Each crTable As Table In report.Database.Tables
                Dim loi As TableLogOnInfo = crTable.LogOnInfo
                loi.ConnectionInfo.ServerName = serverName
                If integratedSecurity = True Then
                    loi.ConnectionInfo.IntegratedSecurity = True
                End If
                crTable.ApplyLogOnInfo(loi)
                'If your DatabaseName is changing at runtime, specify the table location. 
                'crTable.Location = ci.DatabaseName & ".dbo." & crTable.Location.Substring(crTable.Location.LastIndexOf(".") + 1)
            Next
        End Sub

        ''' <summary>
        ''' Applies a new database name to all of the tables in the Crystal Report.  If you do not wish to use a schemaName, pass
        ''' a blank string in for it.
        ''' </summary>
        ''' <param name="report">The Crystal Report document.</param>
        ''' <param name="databaseName">The name of the database.</param>
        ''' <param name="schemaName">The schema name if necessary.  If this is not needed, pass a blank in.</param>
        ''' <remarks>Depending on your database server, this may require a schema also.  For instance, in SQL Server
        ''' you may need NorthWind.dbo. </remarks>
        <Extension()> _
        Public Sub ApplyNewDatabaseName(ByVal report As ReportDocument, databaseName As String, schemaName As String)
            Dim prefix As String = ""
            If schemaName <> "" Then
                prefix = String.Format("{0}.{1}.", databaseName, schemaName)
            Else
                prefix = String.Format("{0}.", databaseName)
            End If
            'Loop through each table in the report and apply the new database name
            For Each crTable As Table In report.Database.Tables
                'If your DatabaseName is changing at runtime, specify the table location. 
                crTable.Location = String.Format("{0}{1}", prefix, crTable.Location.Substring(crTable.Location.LastIndexOf(".") + 1))
            Next
        End Sub

        ''' <summary>
        ''' Sets the Username, Password and ServerName property and/or the UseTrustedConnection property with the values listed in the
        ''' provided connection string.  Currently, only Sql Server is supported for automatically loading values from a connection
        ''' string.
        ''' </summary>
        ''' <param name="sqlConnectionString"></param>
        <Extension()> _
        Public Sub ApplyCredentialsFromConnectionString(report As ReportDocument, ByVal sqlConnectionString As String)
            ' Apply the connection information from the web.config file.
            Dim cb As New System.Data.SqlClient.SqlConnectionStringBuilder(sqlConnectionString)
            If cb.IntegratedSecurity = False Then
                ApplyNewServer(report, cb.DataSource, cb.UserID, cb.Password)
            Else
                ApplyNewServer(report, cb.DataSource, True)
            End If
        End Sub

        ''' <summary>
        ''' Checks to see if a parameter name exists in the reports parameter fields.  This only checks the top level of
        ''' the report.  The top level should propagate down any parameters that need to be passed down.
        ''' </summary>
        ''' <param name="paramName"></param>
        ''' <param name="report"></param>
        <Extension()> _
        Public Function DoesParameterExist(ByVal report As ReportDocument, ByVal paramName As String) As Boolean
            If report Is Nothing Or report.ParameterFields Is Nothing Then
                Return False
            End If
            For Each param As ParameterField In report.ParameterFields
                If paramName = param.Name Then
                    Return True
                End If
            Next
            Return False
        End Function

        ''' <summary>
        ''' Takes a parameter string and places them in the corresponding parameters for the report.  The parameter string must 
        ''' be semi-colon delimited with the parameter inside of that delimited with an equal sign.  E.g.<br /><br />
        ''' 
        ''' <code>
        ''' lastName=Pell;startDate=1/1/2012;endDate=1/7/2012
        ''' </code>
        ''' 
        ''' </summary>
        ''' <param name="report">The Crystal Reports ReportDocument object.</param>
        ''' <param name="parameters">A parameter string representing name/values.  See the summary for usage.</param>
        <Extension()> _
        Public Sub ApplyParameters(report As ReportDocument, parameters As String)
            ApplyParameters(report, parameters, False)
        End Sub

        ''' <summary>
        ''' Takes a parameter string and places them in the corresponding parameters for the report.  The parameter string must 
        ''' be semi-colon delimited with the parameter inside of that delimited with an equal sign.  E.g.<br /><br />
        ''' 
        ''' <code>
        ''' lastName=Pell;startDate=1/1/2012;endDate=1/7/2012
        ''' </code>
        ''' 
        ''' </summary>
        ''' <param name="report">The Crystal Reports ReportDocument object.</param>
        ''' <param name="parameters">A parameter string representing name/values.  See the summary for usage.</param>
        ''' <param name="removeInvalidParameters">If True, parameters that don't exist in the Crystal Report will
        ''' be removed.  If False, these parameters will be left in and an exception will be thrown listing
        ''' the offending parameter name.</param>
        <Extension()> _
        Public Sub ApplyParameters(report As ReportDocument, parameters As String, removeInvalidParameters As Boolean)
            ' No parameters (or valid parameters) were provided.
            If String.IsNullOrEmpty(parameters) = True Or parameters.Contains("=") = False Then
                Exit Sub
            End If
            ' Get rid of any trailing or leading semi-colons that would mess up the splitting.
            parameters = parameters.Trim(";")
            ' The list of parameters split out by the semi-colon delimiter
            Dim parameterList As String() = parameters.Split(Chr(Asc(";")))
            For Each parameter As String In parameterList
                ' nameValue(0) = Parameter Name, nameValue(0) = Value
                Dim nameValue As String() = parameter.Split(Chr(Asc("=")))
                ' Validate that the parameter exists and throw a legit exception that describes it as opposed to the
                ' Crystal Report COM Exception that gives you little detail.  
                If report.DoesParameterExist(nameValue(0)) = False And removeInvalidParameters = False Then
                    Throw New Exception(String.Format("The parameter '{0}' does not exist in the Crystal Report.", nameValue(0)))
                ElseIf report.DoesParameterExist(nameValue(0)) = False And removeInvalidParameters = True Then
                    Continue For
                End If
                ' The ParameterFieldDefinition MUST be disposed of otherwise memory issues will occur, that's why
                ' we're going the "using" route.  Using should Dispose of it even if an Exception occurs.
                Using pfd As ParameterFieldDefinition = report.DataDefinition.ParameterFields.Item(nameValue(0))
                    Dim pValues As ParameterValues
                    Dim parm As ParameterDiscreteValue
                    pValues = New ParameterValues
                    parm = New ParameterDiscreteValue
                    parm.Value = nameValue(1)
                    pValues.Add(parm)
                    pfd.ApplyCurrentValues(pValues)
                End Using
            Next
        End Sub

    End Module

End Namespace    

C# - Note: The C# version was run through a code converter and not tested, it may require slight tweaks (or it may not).

using System.Runtime.CompilerServices;
using CrystalDecisions.CrystalReports.Engine;
using CrystalDecisions.Shared;
using CrystalDecisions.CrystalReports;
namespace Extensions
{
    /// <summary>
    /// A set of extension methods to make manually working with Crystal Reports easier.
    /// </summary>
    /// <remarks>
    /// Pieces of this code started in March of 2004 and have evolved over the last 8 years.
    /// </remarks>
    public static class CrystalReportExtensions
    {
        //*********************************************************************************************************************
        //
        //            Module:  CrystalReportExtensions
        //      Initial Date:  03/26/2004
        //      Last Updated:  05/22/2012
        //    Public Release:  1
        //     Programmer(s):  Blake Pell, blakepell@hotmail.com, http://www.blakepell.com
        //
        //                     This code is free to use for all personal and commercial uses as long as this header remains
        //                     in tact.  
        //
        //*********************************************************************************************************************
        
        /// <summary>
        /// Applies a new server name, SQL username and password to a ReportDocument.  This method can be used with any number
        /// of database providers.
        /// </summary>
        public static void ApplyNewServer(this ReportDocument report, string serverName, string username, string password)
        {
            foreach (ReportDocument subReport in report.Subreports)
            {
                foreach (Table crTable in subReport.Database.Tables)
                {
                    TableLogOnInfo loi = crTable.LogOnInfo;
                    loi.ConnectionInfo.ServerName = serverName;
                    loi.ConnectionInfo.UserID = username;
                    loi.ConnectionInfo.Password = password;
                    crTable.ApplyLogOnInfo(loi);
                }
            }

            //Loop through each table in the report and apply the new login information (in our case, a DSN)
            foreach (Table crTable in report.Database.Tables)
            {
                TableLogOnInfo loi = crTable.LogOnInfo;
                loi.ConnectionInfo.ServerName = serverName;
                loi.ConnectionInfo.UserID = username;
                loi.ConnectionInfo.Password = password;
                crTable.ApplyLogOnInfo(loi);
                //If your DatabaseName is changing at runtime, specify the table location. 
                //crTable.Location = ci.DatabaseName & ".dbo." & crTable.Location.Substring(crTable.Location.LastIndexOf(".") + 1)
            }
        }

        /// <summary>
        /// Applies a new server name to the ReportDocument.  This method is SQL Server specific if integratedSecurity is True.
        /// </summary>
        /// <param name="report"></param>
        /// <param name="serverName">The name of the new server.</param>
        /// <param name="integratedSecurity">Whether or not to apply integrated security to the ReportDocument.</param>
        public static void ApplyNewServer(this ReportDocument report, string serverName, bool integratedSecurity)
        {
            foreach (ReportDocument subReport in report.Subreports)
            {
                foreach (Table crTable in subReport.Database.Tables)
                {
                    TableLogOnInfo loi = crTable.LogOnInfo;
                    loi.ConnectionInfo.ServerName = serverName;

                    if (integratedSecurity == true)
                    {
                        loi.ConnectionInfo.IntegratedSecurity = true;
                    }

                    crTable.ApplyLogOnInfo(loi);
                }
            }

            //Loop through each table in the report and apply the new login information (in our case, a DSN)
            foreach (Table crTable in report.Database.Tables)
            {
                TableLogOnInfo loi = crTable.LogOnInfo;
                loi.ConnectionInfo.ServerName = serverName;

                if (integratedSecurity == true)
                {
                    loi.ConnectionInfo.IntegratedSecurity = true;
                }

                crTable.ApplyLogOnInfo(loi);
                //If your DatabaseName is changing at runtime, specify the table location. 
                //crTable.Location = ci.DatabaseName & ".dbo." & crTable.Location.Substring(crTable.Location.LastIndexOf(".") + 1)
            }
        }

        /// <summary>
        /// Applies a new database name to all of the tables in the Crystal Report.  If you do not wish to use a schemaName, pass
        /// a blank string in for it.
        /// </summary>
        /// <param name="report">The Crystal Report document.</param>
        /// <param name="databaseName">The name of the database.</param>
        /// <param name="schemaName">The schema name if necessary.  If this is not needed, pass a blank in.</param>
        /// <remarks>Depending on your database server, this may require a schema also.  For instance, in SQL Server
        /// you may need NorthWind.dbo. </remarks>
        public static void ApplyNewDatabaseName(this ReportDocument report, string databaseName, string schemaName)
        {
            string prefix = "";

            if (!string.IsNullOrEmpty(schemaName))
            {
                prefix = string.Format("{0}.{1}.", databaseName, schemaName);
            }
            else
            {
                prefix = string.Format("{0}.", databaseName);
            }

            //Loop through each table in the report and apply the new database name
            foreach (Table crTable in report.Database.Tables)
            {
                //If your DatabaseName is changing at runtime, specify the table location. 
                crTable.Location = string.Format("{0}{1}", prefix, crTable.Location.Substring(crTable.Location.LastIndexOf(".") + 1));
            }
        }

        /// <summary>
        /// Sets the Username, Password and ServerName property and/or the UseTrustedConnection property with the values listed in the
        /// provided connection string.  Currently, only Sql Server is supported for automatically loading values from a connection
        /// string.
        /// </summary>
        /// <param name="sqlConnectionString"></param>
        public static void ApplyCredentialsFromConnectionString(this ReportDocument report, string sqlConnectionString)
        {
            // Apply the connection information from the web.config file.
            System.Data.SqlClient.SqlConnectionStringBuilder cb = new System.Data.SqlClient.SqlConnectionStringBuilder(sqlConnectionString);

            if (cb.IntegratedSecurity == false)
            {
                ApplyNewServer(report, cb.DataSource, cb.UserID, cb.Password);
            }
            else
            {
                ApplyNewServer(report, cb.DataSource, true);
            }
        }

        /// <summary>
        /// Checks to see if a parameter name exists in the reports parameter fields.  This only checks the top level of
        /// the report.  The top level should propagate down any parameters that need to be passed down.
        /// </summary>
        /// <param name="paramName"></param>
        /// <param name="report"></param>
        public static bool DoesParameterExist(this ReportDocument report, string paramName)
        {
            if (report == null | report.ParameterFields == null)
            {
                return false;
            }

            foreach (ParameterField param in report.ParameterFields)
            {
                if (paramName == param.Name)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Takes a parameter string and places them in the corresponding parameters for the report.  The parameter string must 
        /// be semi-colon delimited with the parameter inside of that delimited with an equal sign.  E.g.<br /><br />
        /// 
        /// <code>
        /// lastName=Pell;startDate=1/1/2012;endDate=1/7/2012
        /// </code>
        /// 
        /// </summary>
        /// <param name="report">The Crystal Reports ReportDocument object.</param>
        /// <param name="parameters">A parameter string representing name/values.  See the summary for usage.</param>
        public static void ApplyParameters(this ReportDocument report, string parameters)
        {
            ApplyParameters(report, parameters, false);
        }

        /// <summary>
        /// Takes a parameter string and places them in the corresponding parameters for the report.  The parameter string must 
        /// be semi-colon delimited with the parameter inside of that delimited with an equal sign.  E.g.<br /><br />
        /// 
        /// <code>
        /// lastName=Pell;startDate=1/1/2012;endDate=1/7/2012
        /// </code>
        /// 
        /// </summary>
        /// <param name="report">The Crystal Reports ReportDocument object.</param>
        /// <param name="parameters">A parameter string representing name/values.  See the summary for usage.</param>
        /// <param name="removeInvalidParameters">If True, parameters that don't exist in the Crystal Report will
        /// be removed.  If False, these parameters will be left in and an exception will be thrown listing
        /// the offending parameter name.</param>
        public static void ApplyParameters(this ReportDocument report, string parameters, bool removeInvalidParameters)
        {
            // No parameters (or valid parameters) were provided.
            if (string.IsNullOrEmpty(parameters) == true | parameters.Contains("=") == false) {
                return;
            }

            // Get rid of any trailing or leading semi-colons that would mess up the splitting.
            parameters = parameters.Trim(";");

            // The list of parameters split out by the semi-colon delimiter
            string[] parameterList = parameters.Split(Strings.Chr(Strings.Asc(";")));

            foreach (string parameter in parameterList) {
                // nameValue(0) = Parameter Name, nameValue(0) = Value
                string[] nameValue = parameter.Split(Strings.Chr(Strings.Asc("=")));
                // Validate that the parameter exists and throw a legit exception that describes it as opposed to the
                // Crystal Report COM Exception that gives you little detail.  
                if (report.DoesParameterExist(nameValue(0)) == false & removeInvalidParameters == false) {
                    throw new Exception(string.Format("The parameter '{0}' does not exist in the Crystal Report.", nameValue(0)));
                } else if (report.DoesParameterExist(nameValue(0)) == false & removeInvalidParameters == true) {
                    continue;
                }
                // The ParameterFieldDefinition MUST be disposed of otherwise memory issues will occur, that's why
                // we're going the "using" route.  Using should Dispose of it even if an Exception occurs.
                using (ParameterFieldDefinition pfd = report.DataDefinition.ParameterFields.Item(nameValue(0))) {
                    ParameterValues pValues = default(ParameterValues);
                    ParameterDiscreteValue parm = default(ParameterDiscreteValue);
                    pValues = new ParameterValues();
                    parm = new ParameterDiscreteValue();
                    parm.Value = nameValue(1);
                    pValues.Add(parm);
                    pfd.ApplyCurrentValues(pValues);
                }
            }
        }
    }
}

Leave a comment

Please note that we won't show your email to others, or use it for sending unwanted emails. We will only use it to render your Gravatar image and to validate you as a real person.