Skip to main content
A newer version of this page is available. .

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 (a database, file system, cloud, etc.) to save and load reports. You should explicitly implement a report storage class to store reports and register this storage in your application.

Note

Without a report storage, the following limitations apply:

Tip

You can handle the ASPxReportDesigner.SaveReportLayout event to save a report if you do not specify report storage. The event arguments’ 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 v19.2 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 inherited from the bstract ReportStorageWebExtension class is created and added to the project.

Implement Class Methods

Implement the following methods of the newly created class:

  • IsValidUrl - - Determines whether the url (report identifier) passed to the current report storage is valid. You can implement your own logic to prohibit identifiers that contain spaces or other specific characters. When the user saves a modified report, Web Report Designer calls this method before the CanSetData and SeData methods. When the user opens a report, Web Report Designer calls this method before the GeData method.
  • CanSetData - Determines whether a report with the specified identifier can be saved. Add a custom logic and return false for reports that should be read-only. This method is called only for valid identifiers (if the IsValidUrl method returns true) before the SetData method.
  • SetData - Updates an existing report. Uses the specified identifier to store the specified report.
  • SetNewData - Saves a new report. Uses a new identifier to store the specified report. You can validate and correct the specified identifier in the SetNewData method implementation and return the resulting identifier to save the report.
  • GetData - Returns report layout data stored in the Report Storage using the specified identifier. This method is called only for valid identifiers after the IsValidUrl method is called.
  • GetUrls - Returns a dictionary that contains report identifiers 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.

The following table shows the method call sequence:

Action Call Sequence
Save a newly created report GetUrls -> invoke the Save Report dialog -> SetNewData
Open a report GetUrls -> invoke the Open Report dialog -> IsValidUrl -> GetData
Save a modified report IsValidUrl -> CanSetData -> SetData

Register Storage

To register the web report storage, instantiate it and pass the storage instance to the static ReportStorageWebExtension.RegisterExtensionGlobal method at the application start, 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

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

    Note

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

    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 GetUrls()[url].Contains("ReadOnly") ? false : 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)
            {
                // Append "1" if a new report name already exists.
                if (GetUrls().ContainsValue(defaultUrl)) defaultUrl = string.Concat(defaultUrl,"1");
    
                // 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();
            }
        }
    }
    
  • Report storage that stores reports in a file system:

    Note

    A project created with the DevExpress template and uses report storage to store reports in a file system is described in the Create an ASP.NET Web Forms Application with a Report Designer topic.

    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));
                    }
                    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)
                                         .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/save a report to custom report storage. The HttpContext.Session and HttpContext.User properties are not available when you execute custom report storage methods.

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