This example includes Web End-User Report Designer that uses an SQLite database to store reports.
The Web Report Designer uses the ReportStorageWebExtension to manage reports.
After you run the application, select a report in the list box. The list box displays the names of the reports stored in the database:
Click Run Designer to invoke the End-User Report Designer for the selected report. You can edit a report, save it to a database, and exit Designer to return to the report catalog.
Files to Review
- HomeController.cs (VB: DesignerController.vb)
- CustomReportStorageWebExtension.cs (VB: CustomReportStorageWebExtension.vb)
- ReportEntity.cs (VB: ReportEntity.vb)
- SessionFactory.cs (VB: SessionFactory.vb)
- Global.asax.cs (VB: Global.asax.vb)
- DesignModel.cs (VB: DesignModel.vb)
- IndexModel.cs (VB: IndexModel.vb)
- ReportModel.cs (VB: ReportModel.vb)
- Design.cshtml (VB: Design.vbhtml)
- Index.cshtml (VB: Index.vbhtml)
Documentation
More Examples
- How to Implement a Custom Report Storage
- Reporting for Web Forms - Report Designer with Report Storage and Custom Command
- Reporting for WPF - How to Implement a Report Storage
Example Code
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DevExpress.DataAccess.Sql;
using Mvc_DbStorage_Sample.Services.DAL;
using System.Web.Mvc;
using DevExpress.Xpo;
namespace Mvc_DbStorage_Sample.Controllers {
public class HomeController : Controller {
[HttpGet]
public ActionResult Index() {
using (var session = SessionFactory.Create()) {
var reports = session.Query<ReportEntity>()
.Select(x => new ReportModel {
Url = x.Url
})
.ToArray();
var firstReport = reports.FirstOrDefault();
var model = new IndexModel {
SelectedReportUrl = firstReport != null ? firstReport.Url : String.Empty,
Reports = reports
};
return View("Index", model);
}
}
[HttpPost]
public ActionResult Delete(string url) {
using (var session = SessionFactory.Create()) {
var report = session.GetObjectByKey<ReportEntity>(url);
session.Delete(report);
session.CommitChanges();
}
return Index();
}
[HttpPost]
public ActionResult Design(string url) {
return View("Designer", new DesignModel { Url = url, DataSource = CreateSqlDataSource() });
}
SqlDataSource CreateSqlDataSource() {
SqlDataSource ds = new SqlDataSource("NWindConnectionString");
ds.Name = "NWind";
SelectQuery categoriesQuery = SelectQueryFluentBuilder.AddTable("Categories").SelectAllColumns().Build("Categories");
ds.Queries.Add(categoriesQuery);
ds.RebuildResultSchema();
return ds;
}
}
}
Visual BasicImports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports DevExpress.DataAccess.Sql
Imports System.Web.Mvc
Imports DevExpress.Xpo
Imports Mvc_DbStorage_Sample_VB.Services.DAL
Namespace Mvc_DbStorage_Sample.Controllers
Public Class HomeController
Inherits Controller
<HttpGet>
Public Function Index() As ActionResult
Using session = SessionFactory.Create()
Dim reports = session.Query(Of ReportEntity)().Select(Function(x) New ReportModel With {.Url = x.Url}).ToArray()
Dim firstReport = reports.FirstOrDefault()
Dim model = New IndexModel With {
.SelectedReportUrl = If(firstReport IsNot Nothing, firstReport.Url, String.Empty),
.Reports = reports
}
Return View("Index", model)
End Using
End Function
<HttpPost>
Public Function Delete(ByVal url As String) As ActionResult
Using session = SessionFactory.Create()
Dim report = session.GetObjectByKey(Of ReportEntity)(url)
session.Delete(report)
session.CommitChanges()
End Using
Return Index()
End Function
<HttpPost>
Public Function Design(ByVal url As String) As ActionResult
Return View("Designer", New DesignModel With {
.Url = url,
.DataSource = CreateSqlDataSource()
})
End Function
Private Function CreateSqlDataSource() As SqlDataSource
Dim ds As New SqlDataSource("NWindConnectionString")
ds.Name = "NWind"
Dim categoriesQuery As SelectQuery = SelectQueryFluentBuilder.AddTable("Categories").SelectAllColumns().Build("Categories")
ds.Queries.Add(categoriesQuery)
ds.RebuildResultSchema()
Return ds
End Function
End Class
End Namespace
C#using System;
using System.Linq;
using System.Collections.Generic;
using DevExpress.Xpo;
using DevExpress.XtraReports.UI;
using DevExpress.XtraReports.Web.Extensions;
using System.IO;
using Mvc_DbStorage_Sample.Services.DAL;
namespace Mvc_DbStorage_Sample.Services {
public class CustomReportStorageWebExtension : ReportStorageWebExtension {
public override bool CanSetData(string url) {
// Check if the URL is available in the report storage.
using (var session = SessionFactory.Create()) {
return session.GetObjectByKey<ReportEntity>(url) != null;
}
}
public override byte[] GetData(string url) {
// Get the report data from the storage.
using (var session = SessionFactory.Create()) {
var report = session.GetObjectByKey<ReportEntity>(url);
return report.Layout;
}
}
public override Dictionary<string, string> GetUrls() {
// Get URLs and display names for all reports available in the storage
using (var session = SessionFactory.Create()) {
return session.Query<ReportEntity>().ToDictionary<ReportEntity, string, string>(report => report.Url, report => report.Url);
}
}
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.
return true;
}
public override void SetData(XtraReport report, string url) {
// Write a report to the storage under the specified URL.
using (var session = SessionFactory.Create()) {
var reportEntity = session.GetObjectByKey<ReportEntity>(url);
MemoryStream ms = new MemoryStream();
report.SaveLayoutToXml(ms);
reportEntity.Layout = ms.ToArray();
session.CommitChanges();
}
}
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.
if (CanSetData(defaultUrl))
SetData(report, defaultUrl);
else
using (var session = SessionFactory.Create()) {
MemoryStream ms = new MemoryStream();
report.SaveLayoutToXml(ms);
var reportEntity = new ReportEntity(session) {
Url = defaultUrl,
Layout = ms.ToArray()
};
session.CommitChanges();
}
return defaultUrl;
}
}
}
Visual BasicImports System
Imports System.Linq
Imports System.Collections.Generic
Imports DevExpress.Xpo
Imports DevExpress.XtraReports.UI
Imports DevExpress.XtraReports.Web.Extensions
Imports System.IO
Imports Mvc_DbStorage_Sample_VB.Services.DAL
Public Class CustomReportStorageWebExtension
Inherits ReportStorageWebExtension
Public Overrides Function CanSetData(ByVal url As String) As Boolean
' Check if the URL is available in the report storage.
Using session = SessionFactory.Create()
Return session.GetObjectByKey(Of ReportEntity)(url) IsNot Nothing
End Using
End Function
Public Overrides Function GetData(ByVal url As String) As Byte()
' Get the report data from the storage.
Using session = SessionFactory.Create()
Dim report = session.GetObjectByKey(Of ReportEntity)(url)
Return report.Layout
End Using
End Function
Public Overrides Function GetUrls() As Dictionary(Of String, String)
' Get URLs and display names for all reports available in the storage
Using session = SessionFactory.Create()
Return session.Query(Of ReportEntity)().ToDictionary(Function(report) report.Url, Function(report) report.Url)
End Using
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.
Return True
End Function
Public Overrides Sub SetData(ByVal report As XtraReport, ByVal url As String)
' Write a report to the storage under the specified URL.
Using session = SessionFactory.Create()
Dim reportEntity = session.GetObjectByKey(Of ReportEntity)(url)
Dim ms As New MemoryStream()
report.SaveLayoutToXml(ms)
reportEntity.Layout = ms.ToArray()
session.CommitChanges()
End Using
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.
If CanSetData(defaultUrl) Then
SetData(report, defaultUrl)
Else
Using session = SessionFactory.Create()
Dim ms As New MemoryStream()
report.SaveLayoutToXml(ms)
Dim reportEntity = New ReportEntity(session) With {
.Url = defaultUrl,
.Layout = ms.ToArray()
}
session.CommitChanges()
End Using
End If
Return defaultUrl
End Function
End Class
C#using DevExpress.Xpo;
namespace Mvc_DbStorage_Sample.Services.DAL {
[DeferredDeletion(false)]
public class ReportEntity : XPCustomObject {
string url;
string name;
byte[] layout;
public ReportEntity(Session session)
: base(session) {
}
[Key]
public string Url {
get { return url; }
set { SetPropertyValue("Url", ref url, value); }
}
public byte[] Layout {
get { return layout; }
set { SetPropertyValue("Layout", ref layout, value); }
}
}
}
Visual BasicImports DevExpress.Xpo
Namespace Services.DAL
<DeferredDeletion(False)>
Public Class ReportEntity
Inherits XPCustomObject
Private url_Renamed As String
Private name As String
Private layout_Renamed() As Byte
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
<Key>
Public Property Url() As String
Get
Return url_Renamed
End Get
Set(ByVal value As String)
SetPropertyValue("Url", url_Renamed, value)
End Set
End Property
Public Property Layout() As Byte()
Get
Return layout_Renamed
End Get
Set(ByVal value As Byte())
SetPropertyValue("Layout", layout_Renamed, value)
End Set
End Property
End Class
End Namespace
C#using DevExpress.Xpo;
using DevExpress.Xpo.Metadata;
using System.Web.Configuration;
namespace Mvc_DbStorage_Sample.Services.DAL {
public static class SessionFactory {
static readonly IDataLayer dataLayer;
static SessionFactory() {
var dictionary = new ReflectionDictionary();
dictionary.GetDataStoreSchema(typeof(SessionFactory).Assembly);
var connectionString = WebConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
var dataStore = XpoDefault.GetConnectionProvider(connectionString, DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
dataLayer = new ThreadSafeDataLayer(dictionary, dataStore);
}
public static UnitOfWork Create() {
return new UnitOfWork(dataLayer);
}
}
}
Visual BasicImports DevExpress.Xpo
Imports DevExpress.Xpo.Metadata
Imports System.Web.Configuration
Namespace Services.DAL
Public Module SessionFactory
Private ReadOnly dataLayer As IDataLayer
Sub New()
Dim dictionary = New ReflectionDictionary()
dictionary.GetDataStoreSchema(GetType(SessionFactory).Assembly)
Dim connectionString = WebConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString
Dim dataStore = XpoDefault.GetConnectionProvider(connectionString, DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema)
dataLayer = New ThreadSafeDataLayer(dictionary, dataStore)
End Sub
Public Function Create() As UnitOfWork
Return New UnitOfWork(dataLayer)
End Function
End Module
End Namespace
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using DevExpress.Web.Mvc;
using Mvc_DbStorage_Sample.Services;
namespace Mvc_DbStorage_Sample {
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication {
protected void Application_Start() {
DevExpress.XtraReports.Configuration.Settings.Default.UserDesignerOptions.DataBindingMode = DevExpress.XtraReports.UI.DataBindingMode.Expressions;
DevExpress.XtraReports.Web.WebDocumentViewer.Native.WebDocumentViewerBootstrapper.SessionState = System.Web.SessionState.SessionStateBehavior.Default;
DevExpress.XtraReports.Web.QueryBuilder.Native.QueryBuilderBootstrapper.SessionState = System.Web.SessionState.SessionStateBehavior.Default;
DevExpress.XtraReports.Web.ReportDesigner.Native.ReportDesignerBootstrapper.SessionState = System.Web.SessionState.SessionStateBehavior.Default;
DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension.RegisterExtensionGlobal(new CustomReportStorageWebExtension());
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
MVCxReportDesigner.StaticInitialize();
DevExpress.XtraReports.Web.ClientControls.LoggerService.Initialize((ex, message) => System.Diagnostics.Debug.WriteLine("[{0}]: Exception occurred. Message: '{1}'. Exception Details:\r\n{2}", DateTime.Now, message, ex));
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.DefaultBinder = new DevExpress.Web.Mvc.DevExpressEditorsBinder();
DevExpress.Web.ASPxWebControl.CallbackError += Application_Error;
}
protected void Application_Error(object sender, EventArgs e) {
Exception exception = System.Web.HttpContext.Current.Server.GetLastError();
//TODO: Handle Exception
}
}
}
Visual Basic' Note: For instructions on enabling IIS6 or IIS7 classic mode,
' visit http://go.microsoft.com/?LinkId=9394802
Imports System.Web.Http
Imports DevExpress.Web.Mvc
Imports Mvc_DbStorage_Sample_VB.Services
Public Class MvcApplication
Inherits System.Web.HttpApplication
Sub Application_Start()
DevExpress.XtraReports.Configuration.Settings.Default.UserDesignerOptions.DataBindingMode = DevExpress.XtraReports.UI.DataBindingMode.Expressions
DevExpress.XtraReports.Web.WebDocumentViewer.Native.WebDocumentViewerBootstrapper.SessionState = System.Web.SessionState.SessionStateBehavior.Default
DevExpress.XtraReports.Web.QueryBuilder.Native.QueryBuilderBootstrapper.SessionState = System.Web.SessionState.SessionStateBehavior.Default
DevExpress.XtraReports.Web.ReportDesigner.Native.ReportDesignerBootstrapper.SessionState = System.Web.SessionState.SessionStateBehavior.Default
DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension.RegisterExtensionGlobal(New CustomReportStorageWebExtension())
System.Net.ServicePointManager.SecurityProtocol = System.Net.ServicePointManager.SecurityProtocol Or System.Net.SecurityProtocolType.Tls12
MVCxReportDesigner.StaticInitialize()
DevExpress.XtraReports.Web.ClientControls.LoggerService.Initialize(Sub(ex, message)
System.Diagnostics.Debug.WriteLine("[{0}]: Exception occurred. Message: '{1}'. Exception Details:\r\n{2}", DateTime.Now, message, ex)
End Sub)
AreaRegistration.RegisterAllAreas()
GlobalConfiguration.Configure(AddressOf WebApiConfig.Register)
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)
RouteConfig.RegisterRoutes(RouteTable.Routes)
ModelBinders.Binders.DefaultBinder = new DevExpress.Web.Mvc.DevExpressEditorsBinder()
AddHandler DevExpress.Web.ASPxWebControl.CallbackError, AddressOf Application_Error
End Sub
Protected Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
Dim exception As Exception = System.Web.HttpContext.Current.Server.GetLastError()
'TODO: Handle Exception
End Sub
End Class
C#public class DesignModel {
public string Url { get; set; }
public object DataSource { get; set; }
}
Visual BasicPublic Class DesignModel
Public Property Url() As String
Public Property DataSource() As Object
End Class
C#public class IndexModel {
public ReportModel[] Reports { get; set; }
public string SelectedReportUrl { get; set; }
}
Visual BasicPublic Class IndexModel
Public Property Reports() As ReportModel()
Public Property SelectedReportUrl() As String
End Class
C#public class ReportModel {
public string Url { get; set; }
}
Visual BasicPublic Class ReportModel
Public Property Url() As String
End Class
Razor@model DesignModel
@{
ViewBag.Title = "Designer";
}
<link rel="stylesheet" type="text/css" href="~/Content/Designer.css" />
<script type="text/javascript">
function reportDesigner_ExitDesigner(s, e) {
window.location = '@Url.Action("Index")';
}
</script>
@{
var designer = Html.DevExpress().ReportDesigner(settings => {
settings.Name = "reportDesigner";
if (Model.DataSource != null) {
settings.DataSources.Add("Categories", Model.DataSource);
}
settings.ClientSideEvents.ExitDesigner = "reportDesigner_ExitDesigner";
});
if (Model.Url != null) {
designer.BindToUrl(Model.Url).Render();
}
else {
designer.Bind(new XtraReport()).Render();
}
}
Code@ModelType Mvc_DbStorage_Sample_VB.DesignModel
@Code
ViewBag.Title = "Designer"
End Code
<link rel="stylesheet" type="text/css" href="~/Content/Designer.css" />
<script type="text/javascript">
function reportDesigner_ExitDesigner(s, e) {
window.location = '@Url.Action("Index")';
}
</script>
@Code
Dim designer = Html.DevExpress().ReportDesigner(Sub(settings)
settings.Name = "reportDesigner"
If Not IsNothing(Model.DataSource) Then
settings.DataSources.Add("Categories", Model.DataSource)
End If
settings.ClientSideEvents.ExitDesigner = "reportDesigner_ExitDesigner"
End Sub)
If Model.Url IsNot Nothing Then
designer.BindToUrl(Model.Url).Render()
Else
designer.Bind(New XtraReport()).Render()
End If
End Code
Razor@model IndexModel
@{
ViewBag.Title = "Index";
}
<link rel="stylesheet" type="text/css" href="~/Content/Index.css" />
<h2>Report Catalog</h2>
@using (Html.BeginForm()) {
<span>Use the form below to manage reports in the catalog.</span>
@Html.DevExpress().ListBoxFor(x => x.SelectedReportUrl, settings => {
settings.Name = "Url";
settings.Properties.TextField = "Url";
settings.Properties.ValueField = "Url";
settings.Properties.ValueType = typeof(string);
settings.ControlStyle.CssClass = "reports";
settings.Width = 600;
}).BindList(Model.Reports).GetHtml()
@Html.DevExpress().Button(settings => {
settings.Name = "EditReportButton";
settings.RouteValues = new { Controller = "Home", Action = "Design" };
settings.UseSubmitBehavior = true;
settings.Text = "Run Designer";
settings.ControlStyle.CssClass = "buttonSeparator";
settings.Width = 100;
}).GetHtml()
@Html.DevExpress().Button(settings => {
settings.Name = "DeleteReportButton";
settings.RouteValues = new { Controller = "Home", Action = "Delete" };
settings.UseSubmitBehavior = true;
settings.Text = "Delete";
settings.Width = 100;
}).GetHtml()
}
Code@ModelType Mvc_DbStorage_Sample_VB.IndexModel
@Code
ViewBag.Title = "Index"
End Code
<link rel="stylesheet" type="text/css" href="~/Content/Index.css" />
<h2>Report Catalog</h2>
@Using Html.BeginForm()
ViewContext.Writer.Write("<span>Use the form below to manage reports in the catalog.</span>")
@Html.DevExpress().ListBoxFor(Function(x) x.SelectedReportUrl, Sub(settings)
settings.Name = "Url"
settings.Properties.TextField = "Url"
settings.Properties.ValueField = "Url"
settings.Properties.ValueType = GetType(String)
settings.ControlStyle.CssClass = "reports"
settings.Width = 600
End Sub).BindList(Model.Reports).GetHtml()
@Html.DevExpress().Button(Sub(settings)
settings.Name = "EditReportButton"
settings.RouteValues = New With {.Controller = "Home", .Action = "Design"}
settings.UseSubmitBehavior = True
settings.Text = "Run Designer"
settings.ControlStyle.CssClass = "buttonSeparator"
settings.Width = 100
End Sub).GetHtml()
@Html.DevExpress().Button(Sub(settings)
settings.Name = "DeleteReportButton"
settings.RouteValues = New With {.Controller = "Home", .Action = "Delete"}
settings.UseSubmitBehavior = True
settings.Text = "Delete"
settings.Width = 100
End Sub).GetHtml()
End Using