Implement a Report Storage

  • 10 minutes to read

This document describes how to store reports in the Web Report Designer application and contains server-side report storage examples.

Overview

The Web Report Designer requires storage (database, file system, cloud, etc.) to save and load reports. You should explicitly implement a report storage class to store reports using specific identifiers (URLs) and register this storage in your application.

NOTE

Without a report storage, the following limitations apply:

  • New, New via Wizard..., Open... and Save As... menu commands are not available.
  • End users cannot work with Subreports.
TIP

You can handle the ASPxReportDesigner.SaveReportLayout event to save a report. The SaveReportLayoutEventArgs.ReportLayout property contains a report layout. To restore a report, call the ASPxReportDesigner.OpenReportXmlLayout method.

How to Implement a Custom Report Storage

To provide server-side storage for web reports, implement a custom report storage class.

Create a Report Storage Class from Template

  • In the PROJECT menu of Visual Studio, select Add New Item... (or press CTRL+SHIFT+A) to invoke the Add New Item dialog. In this dialog, select the DevExpress v20.1 Report Storage Web item and click Add.

    item-template-devexpress-report-storage-web-application

  • Alternatively, you can click the Report Designer's smart tag and select Add report storage.

    Html5DocumentzViewer_AddReportStorage

A new class is created and added to the project. That class inherits from the abstract ReportStorageWebExtension class.

NOTE

The template generates a sample storage (a ReportStorageWebExtension descendant) for demonstration purposes only. It should not be used in production code.

Implement Class Methods

Implement the following methods of the newly created class:

  • IsValidUrl - Determines whether the URL is valid.
  • CanSetData - Determines whether the report can be saved.
  • SetData - Updates an existing report. Stores the specified report in the Report Storage using the specified URL.
  • ReportStorageWebExtension.SetNewData - Saves a new report. Stores the specified report using a new URL. You can validate and correct the specified URL in the SetNewData method implementation and return the resulting URL to save the report in the storage.
  • ReportStorageWebExtension.GetData - Returns report layout data stored in the Report Storage using the specified URL. This method is called only for valid URLs after the IsValidUrl method is called.
  • ReportStorageWebExtension.GetUrls - Returns a dictionary that contains report URLs and display names. This method is called when running the Report Designer, before the Open Report and Save Report dialogs, and after a new report is saved to the storage.

Register a Storage

To register the web report storage, instantiate it and pass it to the static ReportStorageWebExtension.RegisterExtensionGlobal method at application startup, as shown in the following code snippet:

public class Global : System.Web.HttpApplication {
    protected void Application_Start(object sender, EventArgs e) {
        // ...
        DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension.RegisterExtensionGlobal(new CustomReportStorageWebExtension()); 
    }
    // ...
}

Report Storage Examples

  • A report storage that stores reports in a Microsoft SQL Server database:

    NOTE

    The complete sample project is available in the following DevExpress Examples repository on GitHub: How to Integrate Web Report Designer in a Web Application.

    Show content
    using DevExpress.XtraReports.UI;
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.SqlClient;
    using System.IO;
    using System.Linq;
    
    namespace SimpleWebReportCatalog
    {
        public class CustomReportStorageWebExtension : 
            DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension
        {
            private DataTable reportsTable = new DataTable();
            private SqlDataAdapter reportsTableAdapter;
            string connectionString = "Data Source=localhost;Initial Catalog=Reports;Integrated Security=True";
            public CustomReportStorageWebExtension()
            {
                reportsTableAdapter = 
                    new SqlDataAdapter("Select * from ReportLayout", new SqlConnection(connectionString));
                SqlCommandBuilder builder = new SqlCommandBuilder(reportsTableAdapter);
                reportsTableAdapter.InsertCommand = builder.GetInsertCommand();
                reportsTableAdapter.UpdateCommand = builder.GetUpdateCommand();
                reportsTableAdapter.DeleteCommand = builder.GetDeleteCommand();
                reportsTableAdapter.Fill(reportsTable);
                DataColumn[] keyColumns = new DataColumn[1];
                keyColumns[0] = reportsTable.Columns[0];
                reportsTable.PrimaryKey = keyColumns;
            }
            public override bool CanSetData(string url)
            {
                return true;
            }
            public override byte[] GetData(string url)
            {
                // Get the report data from the storage.
                DataRow row = reportsTable.Rows.Find(int.Parse(url));
                if (row == null) return null;
    
                byte[] reportData = (Byte[])row["LayoutData"];
                return reportData;
            }
            public override Dictionary<string, string> GetUrls()
            {
                reportsTable.Clear();
                reportsTableAdapter.Fill(reportsTable);
                // Get URLs and display names for all reports available in the storage.
                var v = reportsTable.AsEnumerable()
                      .ToDictionary<DataRow, string, string>(dataRow => ((Int32)dataRow["ReportId"]).ToString(),
                                                             dataRow => (string)dataRow["DisplayName"]);
                return v;
            }
            public override bool IsValidUrl(string url)
            {
                return true;
            }
            public override void SetData(XtraReport report, string url)
            {
                // Write a report to the storage under the specified URL.
                DataRow row = reportsTable.Rows.Find(int.Parse(url));
                if (row != null)
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        report.SaveLayoutToXml(ms);
                        row["LayoutData"] = ms.GetBuffer();
                    }
                    reportsTableAdapter.Update(reportsTable);
                }
            }
            public override string SetNewData(XtraReport report, string defaultUrl)
            {
                // Save a report to the storage with a new URL. 
                // The defaultUrl parameter is the report name that the user specifies.
                DataRow row = reportsTable.NewRow();
                row["ReportId"] = 0;
                row["DisplayName"] = defaultUrl;
                using (MemoryStream ms = new MemoryStream())
                {
                    report.SaveLayoutToXml(ms);
                    row["LayoutData"] = ms.GetBuffer();
                }
                reportsTable.Rows.Add(row);
                reportsTableAdapter.Update(reportsTable);
                // Refill the dataset to obtain the actual value of the new row's autoincrement key field.
                reportsTable.Clear();
                reportsTableAdapter.Fill(reportsTable);
                return reportsTable.AsEnumerable().
                    FirstOrDefault(x => x["DisplayName"].ToString() == defaultUrl)["ReportId"].ToString();
            }
        }
    }
    
  • A report storage that stores reports in a file system:

    NOTE

    A report storage that stores reports in a file system is used in a project created with the DevExpress template as described in the following help topic: Create an ASP.NET Web Forms Application with a Report Designer.

    Show content
    using DevExpress.XtraReports.UI;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.ServiceModel;
    
    namespace DXWebApplication1.Services
    {
        public class ReportStorageWebExtension1 : 
            DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension
        {
            readonly string reportDirectory;
            const string FileExtension = ".repx";
            public ReportStorageWebExtension1(string reportDirectory) {
                if (!Directory.Exists(reportDirectory)) {
                    Directory.CreateDirectory(reportDirectory);
                }
                this.reportDirectory = reportDirectory;
            }
            public override bool CanSetData(string url) {
                return true;
            }
            public override bool IsValidUrl(string url) {
                return true;
            }
            public override byte[] GetData(string url) {
                // Gets the report layout data stored in a Report Storage by the specified URL. 
                try {
                    if (Directory.EnumerateFiles(reportDirectory).Select(Path.GetFileNameWithoutExtension).Contains(url))
                    {
                        return File.ReadAllBytes(Path.Combine(reportDirectory, url + FileExtension));
                    }
                    if (PredefinedReports.ReportsFactory.Reports.ContainsKey(url))
                    {
                        using (MemoryStream ms = new MemoryStream()) {
                            PredefinedReports.ReportsFactory.Reports[url]().SaveLayoutToXml(ms);
                            return ms.ToArray();
                        }
                    }
                    throw new FaultException(new FaultReason(string.Format("Could not find report '{0}'.", url)), new FaultCode("Server"), "GetData");
                } catch (Exception) {
                    throw new FaultException(new FaultReason(string.Format("Could not find report '{0}'.", url)), new FaultCode("Server"), "GetData");
                }
            }
            public override Dictionary<string, string> GetUrls() {
                // Returns a dictionary that contains report URLs and display names. 
                // This method is called when running the Report Designer, 
                // before the Open Report and Save Report dialogs and after a new report is saved to a storage.
    
                return Directory.GetFiles(reportDirectory, "*" + FileExtension)
                                         .Select(Path.GetFileNameWithoutExtension)
                                         .Concat(PredefinedReports.ReportsFactory.Reports.Select(x => x.Key))
                                         .ToDictionary<string, string>(x => x);
            }
            public override void SetData(XtraReport report, string url) {
                // Saves a report to the Report Storage with the specified URL. 
                report.SaveLayoutToXml(Path.Combine(reportDirectory, url + FileExtension));
            }
            public override string SetNewData(XtraReport report, string defaultUrl) {
                // Stores the report with a new URL. . 
                // Validate and correct the specified URL in the SetNewData method
                // and return the resulting URL that is used to save a report in the storage.
                SetData(report, defaultUrl);
                return defaultUrl;
            }
        }
    }
    

Access the ASP.NET Session

The ASP.NET End-User Report Designer uses a separate HTTP handler module to open or save a report from or to a custom report storage. For this reason, the HttpContext.Session and HttpContext.User properties are not available when executing methods of the custom report storage.

Review the Access HttpContext.Session in Services topic for more information.