DEV Community

Cover image for Add self-service ad hoc reporting to an ASP.NET Core app (without writing every report yourself)
Razi Syed
Razi Syed

Posted on

Add self-service ad hoc reporting to an ASP.NET Core app (without writing every report yourself)

By Razi Syed. Sample code: github.com/dotnetreport/dotnetreport-aspnetcore-quickstart

Every line-of-business app I've worked on eventually grows a Reports folder, and every one of those folders tells the same story. The first report was a nice Razor page with a grid. The tenth had four query-string parameters and an Excel export someone bolted on. By the thirtieth, "can you add a column to the sales report" was a two-day ticket, and the backlog of report requests was longer than the backlog of actual features.

The fix is not a better Reports folder. It's letting the people who want the reports build them — inside your app, over your data, without you in the loop. That's what a self-service (ad hoc) report builder does, and this article shows how to embed one in an ASP.NET Core app in a few steps.

I'll use Dotnet Report, which I work on (so, disclosure), because its integration is a NuGet package plus a few lines of config and I can show real code. The steps — install, configure, register services, expose your schema, hand it to users — are what any embedded builder needs.

What "self-service" actually means here

Concretely, after this is wired up, a non-developer in your app can:

  • pick tables and columns from the ones you've exposed (not raw SQL — a guided picker)
  • filter, group, sort, aggregate, and add sub-totals
  • turn the result into a chart or a dashboard
  • drill down into grouped rows
  • export to Excel/PDF, or schedule it to be emailed

And you never write those reports. You expose the schema once and get out of the way.

Step 1 — install the package

dotnet add package DotnetReport
Enter fullscreen mode Exit fullscreen mode

That's for .NET 6+. Legacy .NET Framework apps use DotnetReport.Mvc (MVC) or DotnetReport.aspx (Web Forms). The package drops in the report builder's controllers, Razor views and scripts; they live in your project, which means you can restyle them to match your app.

Step 2 — configuration

The builder needs three tokens from a Dotnet Report account (there's a free tier) and a connection string to the database users will report on. Add to appsettings.json:

{
  "dotNetReport": {
    "accountapiurl": "https://dotnetreport.com/portal/api",
    "apiurl": "https://dotnetreport.com/api",
    "accountApiToken": "YOUR-PUBLIC-ACCOUNT-API-TOKEN",
    "dataconnectApiToken": "YOUR-DATA-CONNECT-API-TOKEN",
    "privateApiToken": "YOUR-PRIVATE-API-TOKEN"
  },
  "ConnectionStrings": {
    "ConnectionKey": "Server=.;Database=YourDb;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}
Enter fullscreen mode Exit fullscreen mode

Two notes worth stating plainly. First, the connection string name is ConnectionKey. Second, don't commit real tokens — put them in user secrets or environment variables:

dotnet user-secrets set "dotNetReport:privateApiToken" "..."
Enter fullscreen mode Exit fullscreen mode

Step 3 — register the services it depends on

The report builder is plain MVC: controllers, Razor views, and jQuery/Knockout scripts. So it needs the things any MVC feature needs, plus session (it keeps the user/role context there) and an HttpClient to talk to the reporting service.

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

services.AddControllersWithViews();
services.AddHttpClient();
services.AddHttpContextAccessor();
services.AddSession(o =>
{
    o.IdleTimeout = TimeSpan.FromHours(8);
    o.Cookie.HttpOnly = true;
    o.Cookie.IsEssential = true;
});

// Any auth works — the report routes are [Authorize]-protected.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(o => o.LoginPath = "/Home/Login");

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();      // the builder's scripts and CSS
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();          // before endpoints that read session
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

app.Run();
Enter fullscreen mode Exit fullscreen mode

If you already have an app, you almost certainly have most of this; the additions are usually just AddHttpClient, AddHttpContextAccessor, AddSession/UseSession. Order matters in the middleware pipeline: routing, then auth, then session, then endpoints.

Step 4 — expose your schema

Run the app and open /dotnetsetup. This is a developer/admin screen: it connects with your ConnectionKey string, lists every table and view in the database, and lets you choose which ones end users may report on, with friendly names.

This is the one design decision that deserves thought. Expose views rather than raw tables where your schema is normalized or messy — a vw_SalesByCustomer view that already joins and names things sensibly makes users successful, whereas dumping 200 raw tables at them does not. You are curating a reporting surface, not opening the database.

Step 5 — hand it to users

Open /dotnetreport. That's the report builder. Users pick from the tables you exposed, build, chart, save to folders, share, export and schedule. Link to it from your app's navigation and you're done with the integration.

The next two things you'll need

Getting the builder on screen is the easy part; two follow-ups matter in real apps.

Scope it to the current user and tenant. Out of the box the builder doesn't know who your user is. A single server-side method, GetSettings(), is where you pass the current user id, tenant id, and roles — and, crucially, row-level-security filters the engine appends to every query so users only ever see their own rows. I wrote that up separately with code: Multi-tenant reporting in ASP.NET Core: row-level security your users can't bypass.

Turn on scheduled delivery. Users will immediately ask for "email me this every Monday." The package ships a Quartz.NET job for that; enabling it is one line plus SMTP config — see the scheduled reports sample.

When you shouldn't do this

Honest scoping helps: if you have three fixed reports that never change, a report builder is overkill — write the three Razor pages. Self-service earns its keep when the variety of report requests is the problem, when different customers want different views of the same data, or when you're shipping reporting as a feature to your own customers in a SaaS product. That last case is where it pays off most, because you're turning an endless internal backlog into a capability your users own.

Wrap-up

Install the package, add the config, register controllers/session/HttpClient, expose a curated schema at /dotnetsetup, and point users at /dotnetreport. The working host, sample config and a .gitignore that keeps tokens out of git are in the repo: dotnetreport/dotnetreport-aspnetcore-quickstart.


Razi Syed builds Dotnet Report, an embedded self-service reporting platform for .NET whose report-builder front-end is source-available on GitHub. He writes about adding reporting and analytics to SaaS products without rebuilding them from scratch.

Top comments (0)