Skip to main content
All docs
V24.1

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.

View Example

Create a New Project from a Template

  1. In Microsoft Visual Studio, create a new BlazorWasmReportingApp Blazor WebAssembly App project from a template:

    Create New Project From WASM Template

    Configure Your New Project

  2. In the next step, check the ASP.NET Core hosted check box because the reporting application relies on a server-side backend:

    New Project Additional Information

  3. 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.

  1. Switch to the Solution Explorer window, select the BlazorWasmReportingApp.Server project, right-click the Dependencies node, and select Manage NuGet Packages.

    Blazor WASM Install NuGet Command

  2. In the invoked window, click the Browse tab, select the DevExpress 24.1 Local package source, and install the DevExpress.AspNetCore.Reporting NuGet package.

    Blazor Install NuGet Package Native Viewer

  3. Switch to the Solution Explorer window, select the BlazorWasmReportingApp.Client project, right-click the Dependencies node, and select Manage NuGet Packages.

    Blazor WASM Install NuGet Command

  4. 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.

    Blazor Install NuGet Package Native Viewer

    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.
  5. 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.

  1. 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.

    Add a New Report

    Name the report TestReport.cs and click Add.

  2. The Report Wizard window appears:

    Report Wizard Select Report Type

    Select Blank In the invoked Report Wizard page and click Finish.

  3. 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!:

    Edit a Report in a VS Designer

For more information on how to create reports, review the following help topics:

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) {
        }
    }
}

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.

<div class="nav-item px-3">
    <NavLink class="nav-link" href="viewer">
        <span class="oi oi-plus" aria-hidden="true"></span> Viewer
    </NavLink>
</div>
<div class="nav-item px-3">
    <NavLink class="nav-link" href="designer">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Designer
    </NavLink>
</div>

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.