Create a Hosted Blazor WebAssembly Reporting Application Using Microsoft Template
- 11 minutes to read
Tip
To create a Blazor Reporting application, use preconfigured DevExpress templates to create an application with minimal effort and maximum efficiency. For more information, review the following help topics:
This tutorial describes how to begin with the Microsoft Blazor WebAssembly template and create a sample reporting application that contains Document Viewer and Report Designer controls.
Create a New Project from a Template
In Microsoft Visual Studio, create a new BlazorWasmReportingApp Blazor WebAssembly App project from a template:
In the next step, check the ASP.NET Core hosted check box because the reporting application relies on a server-side backend:
Click Create.
Note
The hosted Blazor WebAssembly project template isn’t available in ASP.NET Core 8.0 or later. To create a hosted Blazor WebAssembly app, a Framework option earlier than .NET 8.0 must be selected with the ASP.NET Core Hosted checkbox.
For more information, refer to the following article: Tooling for ASP.NET Core Blazor.
Install NuGet Packages
The following NuGet packages are required:
- The DevExpress.AspNetCore.Reporting package in the BlazorWasmReportingApp.Server project
- The DevExpress.Blazor.Reporting.JSBased.WebAssembly package in the BlazorWasmReportingApp.Client project
For more information on how to install NuGet packages for DevExpress components, review the following help topic: Choose Between Offline and Online DevExpress NuGet Feeds.
The following steps show how you can obtain and install required dependencies as NuGet packages. You will need to configure a local NuGet feed.
Switch to the Solution Explorer window, select the BlazorWasmReportingApp.Server project, right-click the Dependencies node, and select Manage NuGet Packages.
In the invoked window, click the Browse tab, select the DevExpress 24.1 Local package source, and install the
DevExpress.AspNetCore.Reporting
NuGet package.Switch to the Solution Explorer window, select the BlazorWasmReportingApp.Client project, right-click the Dependencies node, and select Manage NuGet Packages.
In the invoked window, click the Browse tab, select the DevExpress 24.1 Local package source, and install the
DevExpress.Blazor.Reporting.JSBased.WebAssembly
NuGet package.Note
The following NuGet packages are available from the local NuGet feed if you have DevExpress products installed locally, and in the DevExpress NuGet Gallery at https://nuget.devexpress.com/:
- DevExpress.Blazor.Reporting.Viewer
- Contains the Report Viewer native component for Blazor.
- DevExpress.Blazor.Reporting.JSBasedControls
- Contains the Document Viewer and Report Designer JavaScript-based components for Blazor Server and standalone Blazor WebAssembly apps.
- DevExpress.Blazor.Reporting.JSBasedControls.WebAssembly
- Contains the Document Viewer and Report Designer JavaScript-based components for hosted Blazor WebAssembly apps.
- DevExpress.Blazor.Reporting.JSBasedControls.Common
- Contains common settings and client-side resources for the JavaScript-based Blazor Reporting components.
Build the project.
Create a Sample Report
Note
To perform this step, you should install DevExpress Reporting on your machine. Refer to the following topic for more information: Run the Installation Wizard - DevExpress Unified Component Installer.
Switch to the BlazorWasmReportingApp.Server project. Select Add → New Item to invoke the Add New Item dialog. Navigate to the Reporting node and select the DevExpress v.24.1 Report item template.
Name the report
TestReport.cs
and click Add.The Report Wizard window appears:
Select Blank In the invoked Report Wizard page and click Finish.
The newly created report is loaded in the Visual Studio Report Designer where you can modify it. Add the XRLabel control from the Toolbox and type Hello, World!:
For more information on how to create reports, review the following help topics:
- Create a Report from A to Z
- Create Reports in the Visual Studio Report Designer
- Create a Report in Code
Add Document Viewer
If the Document Viewer control is not part of your application, you may skip this section and navigate to the following section: Add Report Designer.
Implement the Report Name Resolution Service
The Document Viewer control loads a report specified by name and needs a service to translate a report name to a report instance. You should create a class that implements the IReportProvider interface, and register it at application startup.
The IReportProvider interface has the GetReport method that gets a report name and returns a report instance. In this example, the GetReport method calls the static ReportFactory
class.
The ReportFactory
class contains the dictionary of report names and report instances.
ReportFactory class
Add the ReportFactory.cs
class file to the BlazorWasmReportingApp.Server project:
using BlazorWasmReportingApp.Server;
using DevExpress.XtraReports.UI;
public static class ReportFactory {
public static Dictionary<string, Func<XtraReport>> Reports = new Dictionary<string, Func<XtraReport>>() {
["TestReport"] = () => new TestReport()
};
}
IReportProvider Implementation
Add the ReportProvider.cs
class file to the BlazorWasmReportingApp.Server project:
using DevExpress.XtraReports.Services;
using DevExpress.XtraReports.UI;
namespace BlazorWasmReportingApp.Server {
public class ReportProvider : IReportProvider {
XtraReport IReportProvider.GetReport(string id, ReportProviderContext context) {
if (ReportFactory.Reports.ContainsKey(id)) {
return ReportFactory.Reports[id]();
}
else
throw new DevExpress.XtraReports.Web.ClientControls.FaultException
(string.Format("Could not find report '{0}'.", id));
}
}
}
The ReportProvider
service should be registered at application startup as described later in the following section: Modify Startup Files.
Create a Page with the Document Viewer
In the BlazorWasmReportingApp.Client/Pages
folder, create a new Razor component (select Project → Add New Item → Razor Component). Save it as the Viewer.razor file, and open and overwrite its content with the following:
@page "/viewer"
<DxWasmDocumentViewer ReportName="TestReport" Height="700px" Width="100%">
<DxDocumentViewerExportSettings UseSameTab=false>
</DxDocumentViewerExportSettings>
<DxWasmDocumentViewerRequestOptions InvokeAction="DXXRDV">
</DxWasmDocumentViewerRequestOptions>
</DxWasmDocumentViewer>
The DxWasmDocumentViewer
component invokes the default WebDocumentViewerController that processes requests from the Document Viewer and loads the TestReport
report to create a document.
Add Report Designer
If the Report Designer control is not a part of your application, you may skip this section and navigate to the following section: Modify Startup Files.
Implement the Report Storage
The Report Designer requires a report storage to load and save reports. To implement the report storage that uses the file system to store reports, in the BlazorWasmReportingApp.Server project, add the ReportStorage
class file with the following content:
using DevExpress.XtraReports.UI;
namespace BlazorWasmReportingApp.Server {
public class ReportStorage : DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension {
readonly string ReportDirectory;
const string FileExtension = ".repx";
public ReportStorage(IWebHostEnvironment env) {
ReportDirectory = Path.Combine(env.ContentRootPath, "Reports");
if (!Directory.Exists(ReportDirectory)) {
Directory.CreateDirectory(ReportDirectory);
}
}
public override bool CanSetData(string url) {
// 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 returned true) before the SetData method is called.
return true;
}
public override bool IsValidUrl(string url) {
// 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.
return Path.GetFileName(url) == url;
}
public override byte[] GetData(string url) {
// Returns report layout data stored in a Report Storage using the specified URL.
// This method is called only for valid URLs after the IsValidUrl method is called.
try {
if (Directory.EnumerateFiles(ReportDirectory).Select(Path.GetFileNameWithoutExtension).Contains(url)) {
return File.ReadAllBytes(Path.Combine(ReportDirectory, url + FileExtension));
}
if (ReportFactory.Reports.ContainsKey(url)) {
using (MemoryStream ms = new MemoryStream()) {
ReportFactory.Reports[url]().SaveLayoutToXml(ms);
return ms.ToArray();
}
}
}
catch (Exception ex) {
throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Could not get report data.", ex);
}
throw new DevExpress.XtraReports.Web.ClientControls.FaultException(string.Format("Could not find report '{0}'.", url));
}
public override Dictionary<string, string> GetUrls() {
// Returns a dictionary of the 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 a storage.
return Directory.GetFiles(ReportDirectory, "*" + FileExtension)
.Select(Path.GetFileNameWithoutExtension)
.Union(ReportFactory.Reports.Select(x => x.Key))
.ToDictionary<string, string>(x => x);
}
public override void SetData(XtraReport report, string url) {
// Stores the specified report to a Report Storage using the specified URL.
// This method is called only after the IsValidUrl and CanSetData methods are called.
var resolvedUrl = Path.GetFullPath(Path.Combine(ReportDirectory, url + FileExtension));
if (!resolvedUrl.StartsWith(ReportDirectory + Path.DirectorySeparatorChar)) {
throw new DevExpress.XtraReports.Web.ClientControls.FaultException("Invalid report name.");
}
report.SaveLayoutToXml(resolvedUrl);
}
public override string SetNewData(XtraReport report, string defaultUrl) {
// Stores the specified report using a new URL.
// 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 a report in your storage.
SetData(report, defaultUrl);
return defaultUrl;
}
}
}
You should register the storage at application startup as described later in the following section: Modify Startup Files.
Create a Page With the Report Designer
In the BlazorWasmReportingApp.Client/Pages
folder, create a new Razor component (select Project → Add New Item → Razor Component). Save it as the Designer.razor file, and open and overwrite its content with the following:
@page "/designer"
<DxWasmReportDesigner ReportName="TestReport" Height="700px" Width="100%" DevelopmentMode=true>
<DxWasmReportDesignerRequestOptions GetDesignerModelAction="DXXRD/GetReportDesignerModel">
</DxWasmReportDesignerRequestOptions>
<DxReportDesignerModelSettings AllowMDI=true>
<DxReportDesignerWizardSettings UseFullscreenWizard=true/>
</DxReportDesignerModelSettings>
</DxWasmReportDesigner>
The DxWasmReportDesigner
component loads the TestReport
report.
Modify Startup Files
This section contains code necessary for both the Document Viewer and the Report Designer. This code registers the DevExpress Blazor internal services, the report name resolution service for the Document Viewer, and the report storage service for the Report Designer.
Note that if your application contains only the Document Viewer, you should not register the ReportStorage
service (the ReportStorageWebExtension descendant). If your application includes the Document Viewer and Report Designer, or only the Report Designer, you should not register the ReportProvider
service (with the IReportProvider interface).
Modified startup file content is shown below. The highlighted code is code that you should add at this step. Note that the order in which you call methods is important.
Server Part
using BlazorWasmReportingApp.Server;
using DevExpress.AspNetCore;
using DevExpress.AspNetCore.Reporting;
using DevExpress.XtraReports.Web.Extensions;
using Microsoft.AspNetCore.ResponseCompression;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddDevExpressControls();
// Register the name resolution service for the Document Viewer.
//builder.Services.AddScoped<DevExpress.XtraReports.Services.IReportProvider, ReportProvider>();
// Register the service that works for the Document Viewer (name resolution)
// and Report Designer (loads and saves reports).
builder.Services.AddScoped<ReportStorageWebExtension, ReportStorage>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseReporting(builder => {
builder.UserDesignerOptions.DataBindingMode =
DevExpress.XtraReports.UI.DataBindingMode.ExpressionsAdvanced;
});
app.UseDevExpressControls();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");
string contentPath = app.Environment.ContentRootPath;
AppDomain.CurrentDomain.SetData("DXResourceDirectory", contentPath);
app.Run();
Client Part
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using BlazorWasmReportingApp.Client
@using BlazorWasmReportingApp.Client.Shared
@using DevExpress.Blazor.Reporting
Implement Controllers
In the BlazorWasmReportingApp.Server
project’s Controllers
folder, add a new ReportingControllers.cs
class file with the content shown below.
If the Report Designer control is not part of your application, you need only the WebDocumentViewerController descendant.
using DevExpress.AspNetCore.Reporting.QueryBuilder;
using DevExpress.AspNetCore.Reporting.QueryBuilder.Native.Services;
using DevExpress.AspNetCore.Reporting.ReportDesigner;
using DevExpress.AspNetCore.Reporting.ReportDesigner.Native.Services;
using DevExpress.AspNetCore.Reporting.WebDocumentViewer;
using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services;
using DevExpress.DataAccess.Sql;
using DevExpress.XtraReports.UI;
using DevExpress.XtraReports.Web.ReportDesigner;
using Microsoft.AspNetCore.Mvc;
namespace BlazorWasmReportingApp.Server.Controllers {
// This controller is required for the Document Viewer and Report Designer.
public class CustomWebDocumentViewerController : WebDocumentViewerController {
public CustomWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService) : base(controllerService) {
}
}
// This controller is required for the Report Designer.
public class CustomReportDesignerController : ReportDesignerController {
public CustomReportDesignerController(IReportDesignerMvcControllerService controllerService) : base(controllerService) {
}
[HttpPost("[action]")]
public object GetReportDesignerModel(
[FromForm] string reportUrl,
[FromForm] ReportDesignerSettingsBase designerModelSettings,
[FromServices] IReportDesignerClientSideModelGenerator designerClientSideModelGenerator) {
Dictionary<string, object> dataSources = new Dictionary<string, object>();
SqlDataSource ds = new SqlDataSource("NWindConnectionString");
dataSources.Add("sqlDataSource1", ds);
ReportDesignerModel model;
if (string.IsNullOrEmpty(reportUrl))
model = designerClientSideModelGenerator.GetModel(new XtraReport(), dataSources, "/DXXRD", "/DXXRDV", "/DXXQB");
else
model = designerClientSideModelGenerator.GetModel(reportUrl, dataSources, "/DXXRD", "/DXXRDV", "/DXXQB");
model.WizardSettings.EnableSqlDataSource = true;
model.Assign(designerModelSettings);
var modelJsonScript = designerClientSideModelGenerator.GetJsonModelScript(model);
return Content(modelJsonScript, "application/json");
}
}
// This controller is required for the Report Designer.
public class CustomQueryBuilderController : QueryBuilderController {
public CustomQueryBuilderController(IQueryBuilderMvcControllerService controllerService) : base(controllerService) {
}
}
}
Add Navigation Links
Modify the NavMenu.razor file to include links to the newly created pages. If you skip the Add Document Viewer or Add Report Designer section, do not add a navigation link to the related page.
Run the Project
Run the project. The Document Viewer and Report Designer components load the TestReport.repx
report:
Troubleshooting
The following article lists common issues that can occur in a Web Reporting application and describes solutions: Troubleshooting
For information on how to identify the cause of an issue, refer to the following topic: Reporting Application Diagnostics.