Reporting — How to Manage Report Data Sources at Runtime

Reporting Team Blog
29 July 2022

For some of our users, reports initially created within the DevExpress Visual Studio Report Designer require modifications at runtime prior to display in either our royalty-free End-User Report Designer or Document Viewer.

Specifically, runtime modifications (or configuration) is often required for a report’s data source. For example, your app might need to change default report settings or replace a report data source with another.

In this blog post, we would like to describe common runtime modification/configuration options for report data sources and also describe how to address these tasks using the DataSourceManager class.

The DataSourceManager Class

This static class contains five methods that allow you to execute basic operations with report data sources at runtime: 

By using these methods, you can address different runtime modification/configuration tasks for your report data source. Some of these tasks and their solutions are described in the first four sections of this blog post. The last section (Platform-Specific Code Samples) contains code snippets that demonstrate the use of DataSourceManager class methods within reporting applications targeting WinForms, WPF, and ASP.NET Core platforms. 

Add Data Sources to a Report

In certain situations, you may want to add data sources to the End-User Report Designer at runtime so that data sources are displayed in the Field List and can be used in a report immediately after the designer is invoked.

To solve this requirement, you can use our AddDataSources method. Pass the report to which you want to add the data sources as the first argument, and then specify the data sources.

The following code sample adds two JSON data sources to a report:

	var report = new XtraReport1();

var jsonDataSource1 = new JsonDataSource { /* ... */ };
var jsonDataSource2 = new JsonDataSource { /* ... */ };

DataSourceManager.AddDataSources(report, jsonDataSource1, jsonDataSource2);
Please refer to the following article to learn how to display data from different data sources in the same report: Bind a Report to Multiple Data Sources.

Replace a Report Data Source

Another common requirement is to replace a report’s data source with another (for example, in order to switch from mock data to real data at runtime).

If your new data source is of the same type as the original report data source, you can simply update settings associated with the initial data source. However, if types (between your new and original data sources) are different, we recommend that you create a new data source and assign it to a report using the ReplaceDataSource method.

The following code sample replaces a report’s original data source with a JSON data source:

	var report = new XtraReport1();

var jsonDataSource = new JsonDataSource {/* ... */};

DataSourceManager.ReplaceDataSource(report, report.DataSource, jsonDataSource);

Make sure that the schema of the new data source matches the schema of the original report data source. Otherwise, bindings in your report will not work as expected, and report data will be displayed differently (not like that displayed in the original report/original data source.

Update All Report Data Source Settings

Assume that you want to bind your report and its elements (subreports, controls, bands, or parameters) to SQL databases and use different settings for these databases in your development and production environments. Here is an obvious question: how can you access these data sources at runtime and update settings? 

The answer is to retrieve all SQL data sources associated with the report and update settings as needed (the same approach can be used for any other data source type).

The following code template uses the GetDataSources method to retrieve all SQL data sources and update query parameters for each data source:

	var report = new XtraReport1();

var sqlDataSources = DataSourceManager.GetDataSources<SqlDataSource>(
    report: report,
    includeSubReports: true
);

foreach (var sqlDataSource in sqlDataSources) {
    foreach (var query in sqlDataSource.Queries) {
        // Access and change query parameters here.
        query.Parameters["paramName"].Value = 32;
    }
}

Update Report and Subreport Data Source Settings

One more common task is to update a report data source and sub report data sources at runtime.

The solution for this requirement is much like the previous example. The difference is that you don’t need to access all report data sources but only the data sources for your subreports and the report itself. As you would expect, you cannot simply call the GetDataSources method - since it can return a data source whose bound control type differs from XtraReport (for example, DetailReportBand or XRCrossTab).

The correct solution is to call the GetDataSourceAssignables<T> method with T = XtraReport to get the report itself alongside all its subreports. Once called, you can iterate through retrieved report objects, access data sources, and update settings as needed.

The following code sample shows how to retrieve a report and all its subreports (elements of the XtraReport type) and update settings (the ConnectionName property) of their data sources. In this example, we assume that the type of each data source is SqlDataSource.

	var report = new XtraReport1();

var dataBoundReports = DataSourceManager.GetDataSourceAssignables<XtraReport>(
    report: report,
    includeSubReports: true
);

foreach (var report in dataBoundReports) {
    (dataBoundReport.DataSource as SqlDataSource).ConnectionName = "nwind";
}

Platform-Specific Code Samples (WinForms, WPF, ASP.NET Core)

Depending on your target platform, you might need to use different strategies to modify/configure data source settings at runtime.

If you wish to add data sources to your report so that they are displayed in the End-User Report Designer's Field List once the designer is invoked, simply call the AddDataSources method before you invoke the designer. 

If you want to change data source settings before displaying a report preview, you will need to complete a few more steps (advanced configuration of the End-User Report Designer). Please see the sections below for detailed examples. 

Add Data Sources to a Report when Invoking the Report Designer

WinForms

	private void Form1_Load(object sender, EventArgs e) {
    var report = new XtraReport1();
    var reportDesignTool = new ReportDesignTool(report);

    var jsonDataSource1 = new JsonDataSource { /* ... */ };
    var jsonDataSource2 = new JsonDataSource { /* ... */ };
    DataSourceManager.AddDataSources(report, jsonDataSource1, jsonDataSource2);

    reportDesignTool.ShowRibbonDesigner();
}

WPF

	private void Window_Loaded(object sender, RoutedEventArgs e) {
    var report = new XtraReport1();

    var jsonDataSource1 = new JsonDataSource { /* ... */ };
    var jsonDataSource2 = new JsonDataSource { /* ... */ };
    DataSourceManager.AddDataSources(report, jsonDataSource1, jsonDataSource2);

    reportDesigner.OpenDocument(report);
}

ASP.NET Core

In an ASP.NET Core reporting application, add data sources to a report in a service method that retrieves the report from storage and returns this report to the designer. The following code sample uses the GetData method of the ReportStorageWebExtension service:

	public override byte[] GetData(string url) {
    //...

    if (ReportsFactory.Reports.ContainsKey(url)) {
        using var ms = new MemoryStream();
        using XtraReport report = ReportsFactory.Reports[url]();

        var jsonDataSource1 = new JsonDataSource { /* ... */ };
        var jsonDataSource2 = new JsonDataSource { /* ... */ };
        DataSourceManager.AddDataSources(report, jsonDataSource1, jsonDataSource2);
        
        report.SaveLayoutToXml(ms);
        return ms.ToArray();
    }
    
    //...
}

Update Data Source Settings when Showing Report Preview in the End-User Report Designer

WinForms

	private void Form1_Load(object sender, EventArgs e) {
    // Create a ReportDesignTool instance and
    // implement a DesignPanelLoaded event handler as
    // shown below. After that, invoke the designer.
    
    var reportDesignTool = new ReportDesignTool(new XtraReport1());
    reportDesignTool.DesignRibbonForm.DesignMdiController.DesignPanelLoaded += DesignMdiController_DesignPanelLoaded;
    reportDesignTool.ShowRibbonDesigner();
}

private void DesignMdiController_DesignPanelLoaded(object sender, DevExpress.XtraReports.UserDesigner.DesignerLoadedEventArgs e) {
    ReportTabControl tabControl = ((XRDesignPanel) sender).GetService(typeof(ReportTabControl)) as ReportTabControl;
    tabControl.PreviewReportCreated += TabControl_PreviewReportCreated;
}

private void TabControl_PreviewReportCreated(object sender, EventArgs e) {
    var reportTabControl = sender as ReportTabControl;
    var report = reportTabControl.PreviewReport;
    
    // Update data source settings of the retrieved report here.
    var jsonDataSource = new JsonDataSource { /* ... */ };
    DataSourceManager.ReplaceDataSource(report, report.DataSource, jsonDataSource);
}

WPF

	public MainWindow() {
    InitializeComponent();
    
    // Implement the designer's ActiveDocumentChanged event handler as shown below.
    reportDesigner.ActiveDocumentChanged += reportDesigner_ActiveDocumentChanged;
}

private void reportDesigner_ActiveDocumentChanged(object sender, DependencyPropertyChangedEventArgs e) {
    if (reportDesigner.ActiveDocument != null) {
        reportDesigner.ActiveDocument.ReportCloned += ActiveDocument_ReportCloned;
    }
}

private void ActiveDocument_ReportCloned(object? sender, DevExpress.Xpf.Reports.UserDesigner.ReportClonedEventArgs e) {
    var report = e.Cloned;
    
    // Update data source settings of the retrieved report here.
    var jsonDataSource = new JsonDataSource { /* ... */ };
    DataSourceManager.ReplaceDataSource(report, report.DataSource, jsonDataSource);
}

ASP.NET Core - Basic Approach

In an ASP.NET Core reporting application, inherit from the PreviewReportCustomizationService class and override its CustomizeReport method. In this method, update settings of report data sources as needed.

	public class CustomPreviewReportCustomizationService : PreviewReportCustomizationService {
    public override void CustomizeReport(XtraReport report) {
        var jsonDataSource = new JsonDataSource {/* ... */};
        DataSourceManager.ReplaceDataSource(report, report.DataSource, jsonDataSource);
    }
}

Add this class to the services collection in the ConfigureServices method of the Startup.cs file:

	public void ConfigureServices(IServiceCollection services) {
    //...
    services.AddScoped<PreviewReportCustomizationService, CustomPreviewReportCustomizationService>();
    //...
}

ASP.NET Core - Advanced Example

Assume that you wish to implement an ASP.NET Core reporting application that meets the following requirements:

  • You want to create and edit reports in the End-User Report Designer and display a preview.

  • You can get report data only from a custom data provider - specifically, from a service method stored in the ServiceContainer collection.

To address these requirements, you should use the ObjectDataSource component.

  • This component can generate a schema for your data and serialize data types. It allows you to create and edit reports in the designer without populating the component with actual data.

  • You can populate this component with data from a custom data provider when switching to the report preview.

To address the 2nd task, you should:

  1. Inherit from the PreviewReportCustomizationService class and override its CustomizeReport method.
  2. In this method, get a data object of a required type from the ServiceContainer collection.
  3. Assign the object to the component's DataSource property. To find the required component, call the DataSourceManager.GetDataSources method.
public override void CustomizeReport(XtraReport report) {
    var objectDataSources = DataSourceManager.GetDataSources<ObjectDataSource>(report, includeSubReports: true);
    foreach (var ods in objectDataSources) {
        if (ods.DataSource is Type dataSourceType) {
            ods.DataSource = ServiceProvider.GetRequiredService(dataSourceType);
        }
    }
}

Refer to the following example for more information in this regard: Reporting for ASP.NET Core – Inject Data from the Entity Framework Core DbContext into a Report Using the Object Data Source.

Code samples are available in the following files:

Your Feedback Counts

As always, we would love your feedback. Do you need to manage report data sources at runtime? If so, does the DataSourceManager class address your requirements? 

    Free DevExpress Products - Get Your Copy Today

    The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.