Implement a Report Storage

This document describes specifics of storing reports in the Web End-User Report Designer and demonstrates how to implement custom server-side report storage.

Overview

In real life web applications, you may need to save reports created in the Web Report Designer and re-open them afterwards for further changes. The Web Report Designer doesn't provide the default implementation of saving reports and requires you to explicitly define how the reports should be stored on the server side of your web application.

If report storage is not implemented, the Report Designer works in simple mode without the New, New via Wizard, Open and Save As menu commands. This mode does not allow end-users to work with Subreports either. I.e., double-clicking the subreports on the design surface will have no effect. However, you can perform custom saving by handling the server-side ASPxReportDesigner.SaveReportLayout event. This event fires when clicking the Save menu command and has the SaveReportLayoutEventArgs.ReportLayout argument of the byte array type that specifies the layout of a report currently being edited. So, in the event handler, you can save this byte array as required. Then, to restore the report from XML bytes, call the ASPxReportDesigner.OpenReportXmlLayout method.

If you want to provide the Web Report Designer with the aforementioned menu commands as well as enable end-users to work with subreports, implement custom report storage and register it in the application. This storage allows you to store reports at a desired location (in a database, file system, cloud, etc.) using specific identifiers (URLs). All storing logic is managed at the level of the SetData and SetNewData methods of a report storage class (so, there is no need to handle the SaveReportLayout event). The GetData method, in turn, returns the report layout by the specified URL. For more information on implementing the custom report storage, see the document section below.

Custom Report Storage Implementation

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

To do this, in the PROJECT menu of Visual Studio, choose Add New Item... (or press CTRL+SHIFT+A) to invoke the Add New Item dialog. In this dialog, select the DevExpress v18.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 in the invoked actions list.

Html5DocumentzViewer_AddReportStorage

This will add a new class inherited from the abstract ReportStorageWebExtension class implementing the following methods that need to be overridden.

  • ReportStorageWebExtension.IsValidUrl - Determines whether or not the URL passed to the current Report Storage is valid. For instance, implement your own logic to prohibit URLs that contain white spaces or some other special characters. This method is called before the CanSetData and GetData methods.
  • ReportStorageWebExtension.CanSetData - Determines whether or not it is possible to store a report by a given URL. For instance, make the CanSetData method return false for reports that should be read-only in your storage. This method is called only for valid URLs (i.e., if the IsValidUrl method returns true) before the SetData method is called.
  • ReportStorageWebExtension.SetData - Stores the specified report to the Report Storage using the specified URL (i.e, saves existing reports only). This method is called only after the IsValidUrl and CanSetData methods are called.
  • ReportStorageWebExtension.SetNewData - Stores the specified report using a new URL (i.e., saves new reports only). The IsValidUrl and CanSetData methods are never called before this method. You can validate and correct the specified URL directly in the SetNewData method implementation and return the resulting URL used to save the report in your 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 of existing report URLs and display names. This method is called when running the Report Designer, before the Open Report and Save Report dialogs are shown and after a new report is saved to the storage.

To register the implemented web report storage, its instance is created and passed to the static ReportStorageWebExtension.RegisterExtensionGlobal method at the application startup.

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

Report Storage Example

This example demonstrates how to implement a custom web report storage to store web reports in a DataSet object.

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using DevExpress.XtraReports.UI;
using SimpleWebReportCatalog.CatalogDataTableAdapters;
// ...

namespace SimpleWebReportCatalog {
    public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension {
        private CatalogData catalogDataSet;
        private DataTable reportsTable;
        private ReportsTableAdapter reportsTableAdapter;


        public CustomReportStorageWebExtension() {
            catalogDataSet = new CatalogData();
            reportsTableAdapter = new ReportsTableAdapter();
            reportsTableAdapter.Fill(catalogDataSet.Reports);
            reportsTable = catalogDataSet.Tables["Reports"];
        }


        public override bool CanSetData(string url) {
            // Check if the URL is available in the report storage.
            return reportsTable.Rows.Find(int.Parse(url)) != null;
        }


        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() {
          // Get URLs and display names for all reports available in the storage.
          return reportsTable.AsEnumerable()
                .ToDictionary<DataRow, string, string>(dataRow => ((Int32)dataRow["ReportID"]).ToString(),
                                                       dataRow => (string)dataRow["DisplayName"]);         
        }


        public override bool IsValidUrl(string url) {
            // Check if the specified URL is valid for the current report storage.
            // In this example, a URL should be a string containing a numeric value that is used as a data row primary key.
            int n;
            return int.TryParse(url, out n);
        }


        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(catalogDataSet);
                catalogDataSet.AcceptChanges();
            }
        }


        public override string SetNewData(XtraReport report, string defaultUrl) {
            // Save a report to the storage under a new URL. 
            // The defaultUrl parameter contains the report display name specified by a user.
            DataRow row = reportsTable.NewRow();

            row["DisplayName"] = defaultUrl;
            using(MemoryStream ms = new MemoryStream()) {
                report.SaveLayoutToXml(ms);
                row["LayoutData"] = ms.GetBuffer();
            }

            reportsTable.Rows.Add(row);
            reportsTableAdapter.Update(catalogDataSet);
            catalogDataSet.AcceptChanges();

            // Refill the dataset to obtain the actual value of the new row's autoincrement key field.
            reportsTableAdapter.Fill(catalogDataSet.Reports);
            return catalogDataSet.Reports.FirstOrDefault(x => x.DisplayName == defaultUrl).ReportID.ToString();
        }
    }
}

Accessing the ASP.NET Session

The End-User Report Designer for ASP.NET uses a separate HTTP handler module to open/save a report to custom report storage. This is why the HttpContext.Session and HttpContext.User properties are not available when executing methods of your custom report storage.

To access the HttpContext/Session values by using a ReportStorageWebExtension descendant when the report is opened/saved in the Web Report Designer, use the approach illustrated in the following topic: Access HttpContext.Session in Services.

See Also