Using ASP.NET Core Middleware to add Analytics JavaScript Viewer-Based Applications

Suppose you built an ASP.NET Core application that displays reports in ActiveReports JavaScript Viewer, and you need to collect the reporting usage analytics. Possible usage parameters include how often a report is requested, which parameters are passed to a report, and how long it takes to render a report.

Collecting analytics requires 2 sub-tasks:

  • Gather analytics data
  • Visualize the collected data and find the desired insight

In our example, ASP.NET Core middleware intercepts requests from JS Viewer to the ASP.NET Core back-end. This article shows how to implement these sub-tasks:

  1. In Visual Studio 2019 create a new "ActiveReports 14 JS Viewer Core MVC" Application. This is the project template for an ASP.NET Core 3.1 application that displays reports with JS Viewer.

  2. Open the automatically added Reports\RdlReport1.rdlx report

    • Add a parameter to the report
    • Display this parameter in the report's body:


  1. Run the application in Debug Mode
    • Enter the parameter value in JS viewer
    • Make sure that the report has been rendered

By default, the application logs ASP.NET Core Diagnostics about requests being sent to the back-end. Before a report is rendered, the POST request "api/reporting/reports/RdlReport1.rdlx/render" is sent to the back-end.

IMAGE 2 In order to access the report name and its parameters, the application intercepts the "render" request, extracts the required information, and saves it somewhere like the database.

To intercept the request, the application injects the branch into the middleware pipeline (use MapWhen branch).

  1. Add the following code at the beginning of the startup class. This regular expression intercepts the render request and extracts the report name.
private static Regex RenderRequestRegex = new Regex("/api/reporting/reports/(.+)/render");
  1. Add the following class declaration after the Startup class (this class extracts report parameters values from the request).
     class RenderRequest
         public Dictionary<string, object[]> Parameters { get; set; }
  2. In Configure method, before the app.UseReporting call, add the following code.

This middleware branch is called when the viewer sends the "render" request described above. The report name and report parameters are extracted from the request's path and body respectively.

Note: for simplicity, this information is sent to the logger output with the level "warning". In the actual application, it is saved to the database.

app.UseWhen(context => RenderRequestRegex.IsMatch(context.Request.Path.Value), (app) =>
                app.Use(async (context, next) =>
                    // extract report name from the request
                    var reportName = RenderRequestRegex.Match(context.Request.Path.Value).Groups[1].Value;
                    logger.LogWarning($"Report {reportName} has been requested at {DateTime.Now:dd-MM-yyyy HH:mm:ss}");
                    using (var reader = new StreamReader(
                        encoding: Encoding.UTF8,
                        detectEncodingFromByteOrderMarks: false,
                        bufferSize: 1024,
                        leaveOpen: true))
                        var body = await reader.ReadToEndAsync();
                        // extract parameters values from the body request
                        var renderRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<RenderRequest>(body);
                        foreach(var kv in renderRequest.Parameters)
                            logger.LogWarning($"Parameter {kv.Key} has values {string.Join(",", kv.Value)}");

                        context.Request.Body.Position = 0;
                    await next.Invoke();
  1. Run the application again, specify the parameter value, and make sure that the report is rendered. IMAGE

Using the same technique, it is possible to add authorization to the report rendering processes, for example, granting certain user groups access to specific reports.

Note that if the line await next.Invoke(); is not invoked as it's shown in the code at step 8 then the request processing is terminated. So, await next.Invoke() can be called conditionally.

The full sample code is available in the attached project.

Sergey Abakumoff

Product Engineer
comments powered by Disqus