Data binding establishes a connection between a report and the data it displays. ActiveReports.NET allows you to bind data at design time, but there are times when you need to bind data at run time, for example:
To support these scenarios, ActiveReports.NET provides the ability to bind data at runtime. This article suggests a simple framework for designing reports using mock data at design time and replacing them with the actual data at runtime. The described procedures are only applicable for RDL and Page Reports.
Suppose you are working on a call center software that provides a monthly performance report to a manager. For example, the manager needs to know the number of calls received by each employee, the number of answered calls, the average call duration, and the resolution rate. The UX team provided the report design, including colors, fonts, and formatting. Here is the example of such a report:
The application's data layer allows to query the data source for call records obtained from a given period, and the result is the list of objects like the following:
public class CallRecord
{
/// <summary>
/// Gets or sets the start date of the call
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// Gets or sets duration of the call in seconds
/// </summary>
public float Duration { get; set; }
/// <summary>
/// Gets or sets the name of agent handling the call
/// </summary>
public string AgentName { get; set; }
/// <summary>
/// Gets or sets the value indicating whether the call was answered
/// </summary>
public bool Answered { get; set; }
/// <summary>
/// Gets or sets the value indicating whether the call was resolved
/// </summary>
public bool Resolved { get; set; }
}
The data layer interface is as follows:
public interface IDataLayer
{
IEnumerable<CallRecord> GetPerformanceData(DateTime startDate, DateTime endDate);
}
First off, you need to design a report according to the UX guidelines. To do that, you don't need to access the actual data. It is convenient to use mock data so that you can quickly switch between the report design and its output in the Visual Studio Integrated Designer without having to run your application to generate the report output.
ActiveReports.NET supports the CSV data provider that you can use to bind a report to mock data. To generate simulated data, you can use any available CSV data generator, for example, the one from extendclass. Once the data are ready, you can save them into a local file. For example, the following snippet shows the fragment of such a file.
StartDate
|
Duration
|
AgentName
|
Answered
|
Resolved
|
---|---|---|---|---|
2021-01-28 | 110.68 | Greg | False | True |
2021-01-22 | 15.14 | Jim | True | True |
2021-01-28 | 20.17 | Jim | False | True |
2021-01-20 | 44.8 | Martha | False | False |
To create a new report that is bound to the mock data, follow these steps:
The report designer will automatically create the new DataSet and populate its fields. So now you can design the report, check its output in the Preview tab, get back to the Design tab, adjust if it's needed, and so on.
In the actual application, the data comes from the data layer that returns the CallRecord object list. ActiveReports.NET offers the Object data provider that accepts any object that implements the IEnumerable interface as the input. So, the first thing you need to do is modify the report's data source type.
If you preview the report in the Preview tab, you will see that it shows the error message:
GrapeCity.ActiveReports.ReportException: An unexpected error occurred. Additional information: 'No data has been set. Please specify IEnumerable to use
The application code should provide the data for the Object data provider in the LocateDataSource event handler. For example, the following code loads the report definition from the application resources, adds the LocateDataSource event handler, and exports the report output to the PDF document saving it in the memory.
internal System.IO.Stream GenerateReportOutput(IDataLayer dataLayer, System.DateTime startDate, System.DateTime endDate)
{
using (var reportStream =GetType().Assembly.GetManifestResourceStream("RuntimeDataBinding.CallCenterPerformace.rdlx"))
using (var reader = new System.IO.StreamReader(reportStream))
{
var rpt = new GrapeCity.ActiveReports.PageReport(reader);
var pdfRe = new GrapeCity.ActiveReports.Export.Pdf.Page.PdfRenderingExtension();
var output = new GrapeCity.ActiveReports.Rendering.IO.MemoryStreamProvider();
rpt.Document.LocateDataSource += (sender, args) =>
{
args.Data = dataLayer.GetPerformanceData(startDate, endDate);
};
rpt.Document.Render(pdfRe, output);
return output.GetPrimaryStream().OpenStream();
}
}
The LocateDataSource event is the foundation of the runtime data binding. The event handler is called by the report engine when the report needs to obtain the data. The event handler should set the Data property of the LocateDataSourceEventArgs instance to the data object that the report will use as the data source. Several data providers support runtime data binding. The following table shows the list of these data providers along with the acceptable data types and conditions for LocateDataSource occurrence.
Data Provider
|
LocateDataSource event occurs
|
Acceptable Data Types
|
---|---|---|
OBJECT | Always | IEnumerable |
XML | ConnectionString of the Data Source is empty | XmlReader, XmlDocument, IXPathNavigable |
DATASET | Always | DataTable, DataView, DataSet |
CSV | ConnectionString of the Data Source is empty | String containing CSV data |
JSON | ConnectionString of the Data Source is empty | String containing JSON data |
The LocateDataSourceEventArgs instance that is passed to the event handler also exposes the following useful properties when the same LocateDataSource handler is called for several reports. For instance, if a report contains a subreport, then the LocateDataSource event handler will be called for each subreport instance in addition to the main report.
Property
|
Description
|
---|---|
Parameters | The list of Report DataSet parameters, including Name and Value. |
DataSet | The Report Data Set that the data are requested for. |
Report | The Report definition that the data are requested for. |
Finally, if you use the JSViewer and want to supply the data at runtime, then the LocateDataSource event handler is set on for the UseReporting middleware, for example:
app.UseReporting(settings =>
{
settings.SetLocateDataSource((args) =>
{
var startDate = (DateTime)args.Parameters[0].Value;
var endDate = (DateTime)args.Parameters[1].Value;
return _dataLayer.GetPerformanceData(startDate, endDate);
});
});