Use ASP.NET Core Middleware to Add Analytics to JavaScript Viewer Apps

Let's say that you built an ASP.NET Core application that displays reports using the ActiveReports JavaScript Viewer. Some reports use various parameters, and at some point, you need to collect the analytics of reporting usage, such as how often a report is requested, which parameters are passed to a report, how long it takes to render a report, etc. This task consists of 2 sub-tasks:

  • Gather the analytics data.
  • Visualize the collected data to get the required insight.

This article shows how to implement the first sub-task with ASP.NET Core middleware that intercepts requests from the ActiveReports JS Viewer to the ASP.NET Core back-end.

Let's go through the process step by step using a basic parametrized report.

  1. In Visual Studio 2022, create a new "ActiveReports 16 JS Viewer Core MVC" Application using the project template that ActiveReports provides out-of-the-box.ASP.NET Core Viewer Application
  2. Open the automatically created Reports\RdlReport1.rdlx report, activate the report explorer, add a report parameter, and drag-and-drop it to the report body: JavaScript Report Viewer
  3. Run the application in Debug Mode, enter the parameter value in the JS viewer, and make sure that the report displays as expectedASP.NET Core Viewer Application
  4. Note that by default, the application logs ASP.NET Core Diagnostics about requests being sent to the back-end. In particular, when the JS viewer needs to render a report, it sends the POST request "API/reporting/reports/RdlReport1.rdlx/render" to the back-end. JavaScript Report Viewer
  5. To access the report name and its parameters' values, the application should intercept the render request, extract the required information and save it somewhere, for example, in the database. The application should inject the branch into the middleware pipeline to achieve that. Let's use MapWhen branch.
  6. Add the following code at the beginning of the Startup class. We will use this regular expression to extract the Report Name. 

    private static Regex RenderRequestRegex = new Regex("/api/reporting/reports/(.+)/render");​
  7. Add the following class declaration after the Startup class. We will use this class to extract report parameters values from the request. 

    class RenderRequest
    {
        public Dictionary<string, object[]> Parameters { get; set; }
    }​
  8. Install the Newtonsoft.Json package into the project
  9. Modify the Configure method of the Startup class as follows. First, the application invokes the new middleware branch when the JS viewer sends the render request described above. Then, the code extracts the report name and parameters' values from the request's path and body, respectively. Note that for the sake of simplicity, this information is sent to the logger output using the "warning" level. The actual application could save the information in a database. 

     public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
               {
                   if (env.IsDevelopment())
                   {
                       app.UseDeveloperExceptionPage();
                   }
                   app.UseWhen(context => RenderRequestRegex.IsMatch(context.Request.Path.Value), (app) =>
                   {
                       app.Use(async (context, next) =>
                       {
                           // extract the 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}");
                           context.Request.EnableBuffering();
                           using (var reader = new StreamReader(context.Request.Body, 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();
                       });
                   });
                   app.UseReporting(settings =>
                   {
                       settings.UseEmbeddedTemplates(EmbeddedReportsPrefix, Assembly.GetAssembly(GetType()));
                       settings.UseCompression = true;
                   });
     
                   app.UseMvc();
               }
    ​
  10. Rerun the application, specify the parameter value, and ensure that the report renders as expected. Now the logger output contains the additional information, here is the corresponding excerpt highlighted:ASP.NET Core Viewer Application

  11. The same technique makes it possible to add authorization to a report rendering process, for example, to allow access to the specific reports for certain users groups only. Note that if the line await next.Invoke(); is not invoked as it's shown in the code from step 9, then the request processing is terminated. So, await next.Invoke() can be called conditionally.
  12. The complete code of the sample is available in the attached project.

 

comments powered by Disqus