How to Build Multi-Tenant Reports in ASP.NET Core Applications
Quick Start Guide | |
---|---|
What You Will Need |
ActiveReports.NET (Installed and Licensed - Trial OK) Visual Studio 2022 or 2019 |
Controls Referenced | ActiveReports.NET JSViewer |
Tutorial Concept | Demonstrate how to dynamically switch a report’s data source at runtime based on the selected tenant in a multi-tenant ASP.NET Core Razor Pages application using ActiveReports.NET. |
Multi-tenant applications serve multiple customers (tenants) from a single application instance. A common requirement is to isolate each tenant’s data so that each tenant only sees their own information. In reporting scenarios, this often means each tenant might have a separate database or schema. The challenge is to use a single report definition (layout) but dynamically switch the data source based on the current tenant.
ActiveReports.NET supports this scenario – for example, you can supply a dynamic connection string when each tenant uses a separate database. In this guide, we will build a sample Razor Pages application to demonstrate how to change a report’s data source at runtime for different tenants.
What we will build: A Razor Pages app with the ActiveReports JSViewer. The front-end will have a dropdown to select a tenant and another to select a report. When a report is viewed, the application will intercept the report-loading process on the server and inject a tenant-specific database connection string before rendering the report. This approach is for demonstration; we’ll pass the tenant ID via an unsecured dropdown for simplicity. In a real production app, tenant identification should be handled via authentication/authorization.
If you’d like to download the pre-built sample application instead of following along, you can get it here.
Ready to check it out? Download ActiveReports.NET Today!
Project Setup
Before diving into code, ensure you have ActiveReports.NET installed and licensed (full developer license or trial is fine) on your development machine. You can use the NuGet package MESCIUS.ActiveReports.Aspnetcore.Viewer
to add ActiveReports JSViewer support to your ASP.NET Core project.
Start by creating a new ASP.NET Core Razor Pages project (using .NET 6 or later). Then follow these setup steps:
-
Add Viewer NuGet to the project: Install the ActiveReports ASP.NET Core Viewer NuGet package (
MESCIUS.ActiveReports.Aspnetcore.Viewer
) via the NuGet Package Manager. This provides the server-side viewer engine and middleware. -
Add JS Viewer scripts/css to the project: Install the ActiveReports.NET JS Viewer NPM package to the project using the command
npm i @mescius/activereportsnet-viewer
.-
Copy the “jsViewer.min.js” and “jsViewer.min.css” files installed in the “\node_modules\@mescius\activereportsnet-viewer\dist” folder to the “wwwroot\js” and “wwwroot\css” folders in the application, respectively
-
Note: You may need to open the project directory in the file explorer to see these files, as they may not show up in the Solution Explorer
-
-
-
Create a Reports folder: Add a folder named
Reports
in your project, and include your report definition files (.rdlx or .rdlx-master for page reports/RDL reports, .rpx for section reports, etc.).-
Set each report file’s Build Action to Embedded Resource.
-
In our sample, we’ll assume two page report files – e.g.,
Conditional Formatting.rdlx
andInvoice.rdlx
– embedded as resources under theReports
folder. -
If you would like to use our sample reports for testing, you can get those here.
-
With the project references in place and reports added, we can proceed to build the front-end and back-end components of the multi-tenant reporting demo.
Frontend UI: Index.cshtml
The frontend UI (Razor Page) provides a simple interface for users to select a tenant and a report, and then displays the report using the ActiveReports JSViewer. The key parts of this page are:
-
Dropdowns for Tenant and Report Selection: A
<select>
for tenant ID (for example, Tenant 1 and Tenant 2) and another for available reports. -
JSViewer Container: A
<div>
element that will host the ActiveReports JSViewer. -
ActiveReports Viewer Scripts: References to the JSViewer’s CSS and JS files, which are typically placed in wwwroot.
-
Initialization Script: JavaScript to create and configure the JSViewer, and to handle the selection events to load the chosen report.
Below is the full code for Index.cshtml
:
In this setup, we include the JSViewer's CSS and JavaScript files in our Razor page and define dropdowns for selecting a tenant and report, as well as a button to explicitly trigger report loading. The viewer is instantiated within a designated <div>
element and is configured to request reports from the backend at the default api/reporting
endpoint. When the "View Report" button is clicked, JavaScript constructs a report identifier combining the selected report and tenant (e.g., Invoice_Tenant1.rdlx
), prompting the viewer to request this specific report from the server.
Custom Report Provider
On the server side, ActiveReports allows us to plug in a custom report provider to intercept report loading. We’ll create a class MultiTenantReportProvider
that implements ActiveReports’ IReportProvider
interface. This provider’s job is to:
-
Load the requested report definition (our .rdlx report) from the embedded resources (or wherever the reports are stored).
-
Parse the tenant ID from the report name passed in (since we encoded the tenant in the reportId string).
-
Modify the report’s data source connection string to the one appropriate for that tenant.
-
Return the modified report definition to the ActiveReports engine for rendering.
Here’s the full code for MultiTenantReportProvider.cs
:
The MultiTenantReportProvider
implements ActiveReports' IReportProvider
interface with two key methods. The GetReport
method parses the incoming reportId
to extract the tenant ID, retrieves the corresponding embedded report definition, dynamically assigns a tenant-specific database connection string to the report’s data source, and returns the modified report as an RDL XML stream.
The GetReportDescriptor
method identifies the report type based on the file extension, ensuring the viewer correctly interprets report formats like .rdlx
, .rdf
, and .rpx
. Finally, this provider is registered within the ASP.NET Core pipeline to serve reports dynamically according to the selected tenant.
Note: The project is currently configured to target an instance of the Northwind sample database on localhost. If you happen to have a local instance of Northwind, it might work for you out of the box, but otherwise, you’ll need to either get one or swap the reports and connection strings out with some of your own to run the sample properly.
Wiring It Up: Program.cs
To use our custom provider, we need to configure ActiveReports to use it when the app starts. ActiveReports provides a middleware extension method UseReportViewer
for the JSViewer. We can call UseReportViewer
and specify our provider via UseReportProvider
. This setup is typically done in the Program.cs
(or Startup.cs
in older ASP.NET Core versions) during app configuration.
Open Program.cs and add the ActiveReports middleware configuration. For example:
In the provided setup, we configure ActiveReports' middleware by invoking UseReportViewer
after the static file middleware and before routing, explicitly registering our MultiTenantReportProvider
. This ensures that when the JSViewer requests reports through the built-in /api/reporting
endpoint, ActiveReports automatically uses our custom provider's GetReport
method to fetch and prepare reports dynamically. Notably, ActiveReports' middleware manages routing and HTTP handling internally, eliminating the need for a custom controller or explicit API setup.
At this stage, the application is fully wired. We have a front-end where the user can select a tenant and report, and a back-end that serves the report with the correct data source based on the tenant. You can run the application, and upon clicking "View Report," the JSViewer should render the report for the chosen tenant. For example, selecting Tenant 1 might show data from Tenant1’s database, and switching to Tenant 2 (with the same report) would show a different dataset (assuming your connection strings point to different databases with the same schema).
Security Note
In this demo, the tenant ID is provided via a dropdown on the UI purely for simplicity. This approach is not secure for a real application. In production, you would determine the tenant context from the authenticated user’s identity or subdomain/URL, not from user input. For instance, a user’s JWT or session might include a tenant ID claim, or you might use a multi-tenant library that resolves the tenant from the request hostname.
The server should then apply the appropriate connection string without trusting any client-side provided value. Always ensure that tenant switching is done on the server based on trusted context (and ideally isolate data per tenant at the database level). The dropdown method is only used here to easily demonstrate the concept of runtime data source switching.
Extending Further
We kept this example focused on embedded report definitions and basic string-based tenant IDs, but ActiveReports.NET is very flexible. A few ways you could extend or adapt this approach:
-
Different Report Storage: Instead of embedding reports as resources, you could load them from external files or even a database. ActiveReports provides helpers like
UseFileStore
to load reports from a folder, or you can useUseReportProvider
(as we did) to fetch reports from any custom location (for example, a report server or blob storage). In fact, ourMultiTenantReportProvider
could be extended to pull report definitions from a database or cache for each tenant, rather than from embedded resources. -
Dynamic Connection Strings via Parameters: ActiveReports supports using report parameters to dynamically set connection strings at runtime. We manually set the
ConnectString
in code for clarity, but you could design your reports to accept a connection string or tenant identifier as a parameter and use that in the data source connection string expression. The ActiveReports documentation covers techniques for dynamic data sources using parameters and theUserContext
for multi-tenant scenarios. -
Additional Tenant Context: In a complex scenario, you might want to not only switch the connection string but also filter data inside the report (for example, if tenants share a database but have tenant-specific fields). In this case, you could pass a tenant ID as a report parameter to filter queries.
For more information, refer to the official ActiveReports.NET documentation and samples. The ActiveReports docs provide guidance on JSViewer integration and custom report providers, as well as other storage options and security considerations. With these tools, you can confidently implement multi-tenant reporting, ensuring each tenant sees the right data, all while reusing common report layouts across your application.
Ready to check it out? Download ActiveReports.NET Today!