Reporting for Blazor - How to use Document Viewer and Report Designer within your Blazor Apps

ASP.NET Team Blog
03 December 2019

As the title of this blog post suggests, we now offer a way for you to use DevExpress Reports within your Blazor apps.

In this post I'll show you how to integrate the HTML5 Document Viewer and End-User Report Designer, part of our excellent reporting tools, into your Blazor applications. I'll focus on integration details and create a sample reporting application as well. Let's get started.

Prerequisites

Here’s what you’ll need to use DevExpress Reporting (our HTML5 Document Viewer and End-User Report Designer) with the Blazor framework:

Source Code

You can find the source code of the sample below on GitHub.

How to Add Reporting to Blazor

To get started, you must first create a new Blazor application using the Blazor WebAssembly App template and check the ASP.NET Core hosted option (or run dotnet new blazorwasm –hosted command). If this template was not installed, please review the following document: Get started with ASP.NET Core Blazor.

This solution uses the ASP.NET Core backend (server-side__Blazor) to process requests from the Document Viewer and Report Designer. The client side defines the UI for these components and the logic to respond to UI updates.

DevExpress Reports for Blazor

Configure the Server Project

  1. Install the DevExpress.AspNetCore.Reporting NuGet package.

  2. Create a custom controller and add an action method to generate the Report Designer model based on the report URL and URIs of reporting controllers. This example uses the default DXXRD , DXXRDV and DXQB controllers of our Reporting tools to process requests from the Report Designer, Document Viewer, and Query Builder, respectively:

namespace BlazorReporting.Server.Controllers
{
    [Route("api/[controller]")]
    public class ReportingController : Controller
    {
        public ReportingController() { }
        [Route("[action]", Name = "getReportDesignerModel")]
        public object GetReportDesignerModel(string reportUrl)
        {
            string modelJsonScript = new ReportDesignerClientSideModelGenerator(HttpContext.RequestServices)
                .GetJsonModelScript(reportUrl, null, "/DXXRD", "/DXXRDV", "/DXQB");
            return new JavaScriptSerializer().Deserialize<object>(modelJsonScript);
        }
    }
}
  1. Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package to help handle sending the model to the client in the JSON format.

  2. Add server-side storage to save and load the reports. Create a new class (CustomReportStorageWebExtension), inherit it from the ReportStorageWebExtension class and define the methods. In this example, our reports are stored in memory, but you can add other storage types (database, file system, etc.) See Implement a Report Storage for more information.

  3. Open the Startup file and register the reporting services and storage:

using DevExpress.AspNetCore;
using DevExpress.AspNetCore.Reporting;
// ...
public void ConfigureServices(IServiceCollection services)
{
    services.AddDevExpressControls();
    services.AddMvc().AddNewtonsoftJson().AddDefaultReportingControllers();
    // ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension.RegisterExtensionGlobal(new CustomReportStorageWebExtension());
    app.UseStaticFiles();
    app.UseDevExpressControls();
    // ...
}

Configure the Client Project

  1. Add the package.json configuration file and list the following npm packages required by our Reporting components:
{
  "dependencies": {
    // ...
    "devexpress-reporting": "~19.2.3"
    "@devexpress/analytics-core": "~19.2.3",
    "devextreme": "~19.2.3",
  },
  // ...
}
  1. Run the npm install command to install these packages.

  2. Create the Viewer.razor file and use the dxReportViewer binding to render the Document Viewer:

@page "/viewer"
@inject IJSRuntime JSRuntime
@implements IDisposable

<div id="viewer" style="width:1000px; height: 800px" data-bind="dxReportViewer: $data"></div>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        JSRuntime.InvokeAsync<object>("JsFunctions.InitWebDocumentViewer");
    }

    public void Dispose()
    {
        JSRuntime.InvokeAsync<string>("JsFunctions.Dispose", "viewer");
    }
}
  1. Create the Designer.razor file and use the dxReportDesigner binding to render the Report Designer:
@page "/designer"
@inject IJSRuntime JSRuntime
@implements IDisposable

<div style="width:1000px; height:800px" id="designer" data-bind="dxReportDesigner: $data"></div>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        JSRuntime.InvokeAsync<object>("JsFunctions.InitReportDesigner");
    }

    public void Dispose()
    {
        JSRuntime.InvokeAsync<string>("JsFunctions.Dispose", "designer");
    }
}

You should use the OnAfterRender lifecycle method for both components for initialization and the Dispose method to release resources used by these components:

  1. Add the index.js file and implement the logic to initialize and dispose the components:
var ko = require('knockout');

require('devexpress-reporting/dx-reportdesigner');

window.JsFunctions = {
    _viewerOptions: {
        reportUrl: ko.observable("MyReport"),
        requestOptions: {
            invokeAction: "/DXXRDV"
        }
    },
    _designerOptions: {
        reportUrl: ko.observable("MyReport"),
        requestOptions: {
            getDesignerModelAction: "api/Reporting/getReportDesignerModel"
        }
    },
    InitWebDocumentViewer: function () {
        ko.applyBindings(this._viewerOptions, document.getElementById("viewer"));
    },
    InitReportDesigner: function () {
        ko.applyBindings(this._designerOptions, document.getElementById("designer"));
    },
    Dispose: function (elementId) {
        var element = document.getElementById(elementId);
        element && ko.cleanNode(element);
    }
}
  1. Create a new style.css file and import CSS styles:
@import url("node_modules/jquery-ui/themes/base/all.css");
@import url("node_modules/devextreme/dist/css/dx.common.css");
@import url("node_modules/devextreme/dist/css/dx.light.css");
@import url("node_modules/@devexpress/analytics-core/dist/css/dx-analytics.common.css");
@import url("node_modules/@devexpress/analytics-core/dist/css/dx-analytics.light.css");
@import url("node_modules/@devexpress/analytics-core/dist/css/dx-querybuilder.css");
@import url("node_modules/devexpress-reporting/dist/css/dx-webdocumentviewer.css");
@import url("node_modules/devexpress-reporting/dist/css/dx-reportdesigner.css");

Add a reference to this new style.css file in the index.js file:

require('devexpress-reporting/dx-reportdesigner');
import "./style.css";
// ...
  1. (Optional) You can create a bundle with Webpack as demonstrated in the GitHub example. Add a new webpack.config.js file, define the index.js file as the bundle's entry and the bundle.js file as the output file.
module.exports = {
    entry: './src/index.js',
    mode: "development",
    output: {
        path: path.resolve(__dirname, '../wwwroot/site'),
        filename: "bundle.js"
    },
    // ...
};

Run the webpack command within the console to create the bundle. Link resulting files in the index.html file.

Run and View Report

Run the solution and view our sample in your local browser:

Blazor Reporting - Web Report Designer

Please test this example and share your thoughts in the survey at the end of this post.

Your Feedback Counts

As always, we welcome your feedback. Please take a moment to answer the following survey question so we can better understand your Blazor reporting requirements.

5 comment(s)
Andrzej Wloszczynski
Andrzej Wloszczynski

Hello

First sentence in this blog:

As the title of this blog post suggests, we now offer a way for you to use DevExpress Reports within your server-side Blazor apps.

Then we can read:

To get started, you must first create a new Blazor application using the Blazor WebAssembly App template(...)

It looks that title is wrong.

Does the solution above address your Blazor Reporting requirements?

There is no simple answer. We (C# programmers) dream about internet applications without JavaScript, JS libraries like Knockout, JS tools like Node, Webpack, etc. Unfortunately it is clear that Blazor WASM is not ready for production yet and solution like presented above is better than nothing. I'm afraid that Blazor will never be a 100% JavaScript replacement. We will have to live with it and the only hope is that component vendors like you will hide JS from us.


3 December, 2019
YALIN SOFTWARE
YALIN SOFTWARE
I agree with Andrzej Wloszczynski.
4 December, 2019
Mehul Harry (DevExpress)
Mehul Harry (DevExpress)

@Andrew,

You're correct and I've updated the blog title and opening paragraph. Thanks for the contribution.

4 December, 2019
Sven Glöckner 1
Sven Glöckner 1

@Mehul

Thanks for this great post.

Do you mind creating another post about using Reporting for Blazor server-side? Or is it just the same?


6 December, 2019
Mario Blatarić
Mario Blatarić

Hi, 

first I'd like to say that seeing DevExpress commitment to Blazor brought a really nice smile to my face :-) 

I have completed the survey, but would like to leave a comment as well. 

I am completely in-line with Andrzej - our dreams were crushed with Silverlight death, and Javascript is - well - horrible abuse of any sane intellect in line-of-business apps :-) 

So, I would like to express my opinion like this:

  • in short terms - use wrappers to provide immediate extended features for Blazor which will make Blazor more attractive to start building apps and I am fine with adding extra code to making it work
  • this will also make amazing platform to build demo or mockups to demonstrate apps and features to our customers - it will look serious and completed, instead being obvious it is experimental tech
  • however, in the long term, plan to go native from the very start
  • prepare your code so you can move native as soon WebAseembly allows new functionalities and areas to go native
  • please, please, please hide AS MUCH Javascript from us, thank you so much
  • whenever we should write Javascript, please provide some C# extension method which hide Javascript completely and then turn this method to API as soon WebAssembly allows it to become native

I know everyone says WebAssembly is not replacement for Javascript (because every tech that had that ambition died - by Mozilla hand), but I think MANY of us are hoping WebAssembly (and especially out-of-browser WASM) will dismantle Javascript on quantum level so we forgot it even existed ... 

Large business apps in JS are abomination, forced down our throat without mercy - so if you manage to create JS-free environment for writing business apps - I think we can call you a saviour and start a religion ... 

9 December, 2019

Please login or register to post comments.