In this article I will explain how to make Crystal Report work with a dynamic DataSource i.e. without any database connection at design time in ASP.Net using C# and VB.Net.
The idea is to make generic Typed DataSet as a DataSource for Crystal Report and then populate the DataSet with the data from any Table of any Database.
Database
Here I am making use of Microsoft’s Northwind Database. You can download it from here
1. Add Typed DataSet to the ASP.Net Website
Since I am using disconnected Crystal Reports we will make use of Typed DataSet to populate the Crystal Reports with data from database.
The use of Typed DataSet helps us avoid direct connection of Crystal Report with database, which is very necessary for building a Crystal Report with dynamic columns.
2. Adding DataTable to the Typed DataSet
Our next step would be to add a DataTable to the Type DataSet.
3. Adding Generic Columns or fields to DataTable
In the DataTable you will need to add as many columns as you need. For this article I am adding 5 columns DataColumn1 to DataColumn5. These generic columns will be used to fetch data from any column of any table.
Note: Since we are adding generic columns, the data type of all columns has to be of String type so that it works for all columns of different data types.
4. Add Crystal Report to the ASP.Net Website
Now you will need to add a Crystal Report to the ASP.Net Application. You can give it name as per your choice.
As soon as you click OK you get the following dialog. You must select Using the Report Wizard option.
Once you press OK in the above dialog, the Report Wizard starts and you get the following dialog where you need to choose the type of Database connection for your Crystal Report. Since we are using DataSet we will choose the Custom DataSet.
Next the Wizard will ask for the Columns or Fields from the DataSet you need to display on the Crystal Reports.
Note: You need to choose all columns as we will show and hide Columns in Crystal Report dynamically from the code.
Note: There are more steps in the Wizards but those are Optional hence are not included in this article.
Once you click Finish your Crystal Report should look as below. Here the Section 2 acts as the Header and Section 3 will records from the DataTable.
All the elements in the Section 2 are Crystal Report TextObject. The Text of these TextObjects will be replaced with the actual names of the Columns present in the Recordset returned by the SQL Query.
HTML Markup
The HTML Markup consists of an ASP.Net RadioButton containing names of 2 tables of the Northwind Database and a CrystalReportViewer control.
Tables:
<asp:RadioButtonList ID="rbTables" runat="server" RepeatDirection="Horizontal" AutoPostBack="true"
OnSelectedIndexChanged="Tables_Changed">
<asp:ListItem Text="Customers Table" Value="Customers" Selected="True"></asp:ListItem>
<asp:ListItem Text="Employees Table" Value="Employees"></asp:ListItem>
</asp:RadioButtonList>
<hr />
<CR:CrystalReportViewer ID="CrystalReportViewer1" runat="server" AutoDataBind="true"
Height="400" Width="580" BestFitPage="False" />
Namespaces
You will need to import the following namespaces.
C#
using System.Data;
using System.Linq;
using System.Configuration;
using System.Data.SqlClient;
using CrystalDecisions.CrystalReports.Engine;
VB.Net
Imports System.Data
Imports System.Linq
Imports System.Configuration
Imports System.Data.SqlClient
Imports CrystalDecisions.CrystalReports.Engine
Crystal Report with Dynamic DataSource without Database connection in ASP.Net
I am populating Crystal Reports from database in the Page Load event of the page using the BindReport function.
This function build a dynamic query based on the Table selected from the RadioButtonList and then passes the SQL Query to the GetData function along with one more parameter i.e. Crystal Report Document.
Inside the GetData function, the records are fetched from the database using SqlDataReader. Once the results are available first a loop is executed over the fields returned and then the names of the Columns are set in the corresponding TextObject in Section 2 discussed earlier.
The above part completes the dynamic header generation of Crystal Report. Now the next task is to populate data from DataReader into the DataTable of the Typed DataSet.
Finally the Typed DataSet is returned by the function and it is supplied as ReportSource property of the Crystal Report.
This BindReport function is also called inside the SelectedIndexChanged event handler of the RadioButtonList.
C#
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this.BindReport();
}
}
private void BindReport()
{
string query = "SELECT TOP 5 ";
if (rbTables.Items[0].Selected)
{
query += "CustomerId, ContactName, City, Country, PostalCode FROM Customers";
}
else
{
query += "EmployeeId, FirstName, LastName, City, Country FROM Employees";
}
ReportDocument crystalReport = new ReportDocument();
CrystalReportViewer1.DisplayGroupTree = false;
crystalReport.Load(Server.MapPath("~/CrystalReport.rpt"));
CustomDataSet dsCustom = GetData(query, crystalReport);
crystalReport.SetDataSource(dsCustom);
CrystalReportViewer1.ReportSource = crystalReport;
}
private CustomDataSet GetData(string query, ReportDocument crystalReport)
{
string conString = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
SqlCommand cmd = new SqlCommand(query);
using (SqlConnection con = new SqlConnection(conString))
{
CustomDataSet dsCustom = new CustomDataSet();
cmd.Connection = con;
con.Open();
using (SqlDataReader sdr = cmd.ExecuteReader())
{
//Get the List of all TextObjects in Section2.
List<TextObject> textObjects = crystalReport.ReportDefinition.Sections["Section2"].ReportObjects.OfType<TextObject>().ToList();
for (int i = 0; i < textObjects.Count; i++)
{
//Set the name of Column in TextObject.
textObjects[i].Text = string.Empty;
if (sdr.FieldCount > i)
{
textObjects[i].Text = sdr.GetName(i);
}
}
//Load all the data rows in the Dataset.
while (sdr.Read())
{
DataRow dr = dsCustom.Tables[0].Rows.Add();
for (int i = 0; i < sdr.FieldCount; i++)
{
dr[i] = sdr[i];
}
}
}
con.Close();
return dsCustom;
}
}
protected void Tables_Changed(object sender, EventArgs e)
{
this.BindReport();
}
VB.Net
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not Me.IsPostBack Then
Me.BindReport()
End If
End Sub
Private Sub BindReport()
Dim query As String = "SELECT TOP 5 "
If rbTables.Items(0).Selected Then
query += "CustomerId, ContactName, City, Country, PostalCode FROM Customers"
Else
query += "EmployeeId, FirstName, LastName, City, Country FROM Employees"
End If
Dim crystalReport As New ReportDocument()
CrystalReportViewer1.DisplayGroupTree = False
crystalReport.Load(Server.MapPath("~/CrystalReport.rpt"))
Dim dsCustom As CustomDataSet = GetData(query, crystalReport)
crystalReport.SetDataSource(dsCustom)
CrystalReportViewer1.ReportSource = crystalReport
End Sub
Private Function GetData(query As String, crystalReport As ReportDocument) As CustomDataSet
Dim conString As String = ConfigurationManager.ConnectionStrings("constr").ConnectionString
Dim cmd As New SqlCommand(query)
Using con As New SqlConnection(conString)
Dim dsCustom As New CustomDataSet()
cmd.Connection = con
con.Open()
Using sdr As SqlDataReader = cmd.ExecuteReader()
'Get the List of all TextObjects in Section2.
Dim textObjects As List(Of TextObject) = crystalReport.ReportDefinition.Sections("Section2").ReportObjects.OfType(Of TextObject)().ToList()
For i As Integer = 0 To textObjects.Count - 1
'Set the name of Column in TextObject.
textObjects(i).Text = String.Empty
If sdr.FieldCount > i Then
textObjects(i).Text = sdr.GetName(i)
End If
Next
'Load all the data rows in the Dataset.
While sdr.Read()
Dim dr As DataRow = dsCustom.Tables(0).Rows.Add()
For i As Integer = 0 To sdr.FieldCount - 1
dr(i) = sdr(i)
Next
End While
End Using
con.Close()
Return dsCustom
End Using
End Function
Protected Sub Tables_Changed(sender As Object, e As EventArgs)
Me.BindReport()
End Sub
Screenshot
The following Screenshot describes how the Crystal Report is being populated dynamically from different tables using dynamic DataSource.
Demo
Downloads