The requested page is not available for the requested platform. You are viewing the content for Default platform.

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 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 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.2 Report Storage Web item and click Add.

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

This option is also available for the Web Report Designer in the ASP.NET MVC extension wizard.

report-mvc-extension-storage

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

The following example demonstrates how to store reports in a database:

Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.IO
Imports System.Linq
Imports DevExpress.XtraReports.UI
Imports SimpleWebReportCatalog.CatalogDataTableAdapters
' ...

Namespace SimpleWebReportCatalog
    Public Class CustomReportStorageWebExtension
        Inherits DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension

        Private catalogDataSet As CatalogData
        Private reportsTable As DataTable
        Private reportsTableAdapter As ReportsTableAdapter


        Public Sub New()
            catalogDataSet = New CatalogData()
            reportsTableAdapter = New ReportsTableAdapter()
            reportsTableAdapter.Fill(catalogDataSet.Reports)
            reportsTable = catalogDataSet.Tables("Reports")
        End Sub


        Public Overrides Function CanSetData(ByVal url As String) As Boolean
            ' Check if the URL is available in the report storage.
            Return reportsTable.Rows.Find(Integer.Parse(url)) IsNot Nothing
        End Function


        Public Overrides Function GetData(ByVal url As String) As Byte()
            ' Get the report data from the storage.
            Dim row As DataRow = reportsTable.Rows.Find(Integer.Parse(url))
            If row Is Nothing Then
                Return Nothing
            End If

            Dim reportData() As Byte = DirectCast(row("LayoutData"), Byte())
            Return reportData
        End Function


        Public Overrides Function GetUrls() As Dictionary(Of String, String)
          ' Get URLs and display names for all reports available in the storage.
          Return reportsTable.AsEnumerable().ToDictionary(Function(dataRow) CInt((dataRow("ReportID"))).ToString(), Function(dataRow) CStr(dataRow("DisplayName")))
        End Function


        Public Overrides Function IsValidUrl(ByVal url As String) As Boolean
            ' 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.
            Dim n As Integer = Nothing
            Return Integer.TryParse(url, n)
        End Function


        Public Overrides Sub SetData(ByVal report As XtraReport, ByVal url As String)
            ' Write a report to the storage under the specified URL.
            Dim row As DataRow = reportsTable.Rows.Find(Integer.Parse(url))

            If row IsNot Nothing Then
                Using ms As New MemoryStream()
                    report.SaveLayoutToXml(ms)
                    row("LayoutData") = ms.GetBuffer()
                End Using
                reportsTableAdapter.Update(catalogDataSet)
                catalogDataSet.AcceptChanges()
            End If
        End Sub


        Public Overrides Function SetNewData(ByVal report As XtraReport, ByVal defaultUrl As String) As String
            ' Save a report to the storage under a new URL. 
            ' The defaultUrl parameter contains the report display name specified by a user.
            Dim row As DataRow = reportsTable.NewRow()

            row("DisplayName") = defaultUrl
            Using ms As New MemoryStream()
                report.SaveLayoutToXml(ms)
                row("LayoutData") = ms.GetBuffer()
            End Using

            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(Function(x) x.DisplayName = defaultUrl).ReportID.ToString()
        End Function
    End Class
End Namespace

Accessing the ASP.NET Session

The End-User Report Designer 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.