RDLC reports in ASP.NET Core
ReportViewer’s web control is a Web Forms control, and LocalReport, which renders RDLC without it, belongs to
.NET Framework: neither runs in ASP.NET Core. This page shows how to serve the same reports from an ASP.NET Core
application on .NET 8, 9 or 10, as PDF or Excel, with the data your application already loads. For the options
besides this one, see ReportViewer and RDLC alternatives.
Convert the reports
Section titled “Convert the reports”-
Install the
taguacommand, and convert the folder that holds your.rdlcfiles. Each report becomes a.report.yamltemplate next to it, andtagua-import-summary.mdlists what, if anything, needs a look:Terminal window dotnet tool install --global Tagua.Clitagua import Reports -
Add the package to your ASP.NET Core project, and declare the license your company uses (see Licensing):
Terminal window dotnet add package Tagua -
Copy the templates, and their images, to the output with the application:
<ItemGroup><Content Include="Reports/**"CopyToOutputDirectory="PreserveNewest" /></ItemGroup>
Serve a report as PDF
Section titled “Serve a report as PDF”Compile each template once, at startup: a compiled report is immutable and serves any number of requests at once. Each request passes the rows and parameters, and gets a PDF back.
using System.Data.Common;using Tagua;using Tagua.Fill;using Tagua.Pipeline;using Tagua.Serialization;
TaguaSettings.License = LicenseType.Community;
var builder = WebApplication.CreateBuilder(args);var template = TemplateLoader.LoadFile("Reports/Invoice.report.yaml");builder.Services.AddSingleton(CompiledReport.Compile(template));var app = builder.Build();
app.MapGet("/invoices/{id:int}/pdf", async ( int id, CompiledReport invoice, DbDataSource db, CancellationToken ct) =>{ const string sql = """ select Description, Quantity, UnitPrice from InvoiceLines where InvoiceId = @id """; var inputs = new ReportInputs { Parameters = new Dictionary<string, object?> { ["InvoiceId"] = id }, Data = new Dictionary<string, IDataSet> { // The dataset's name in the report, as ReportDataSource named it. ["Lines"] = DataSources.FromDataReader(async token => { var command = db.CreateCommand(sql); var parameter = command.CreateParameter(); (parameter.ParameterName, parameter.Value) = ("@id", id); command.Parameters.Add(parameter); return await command.ExecuteReaderAsync(token); }), }, Resources = new DirectoryResourceResolver("Reports"), }; var pdf = new MemoryStream(); await Reports.RenderPdfAsync(invoice, inputs, pdf, cancellationToken: ct); pdf.Position = 0; return Results.File(pdf, "application/pdf", $"invoice-{id}.pdf");});
app.Run();The rows come straight from the reader, with no DataTable in between. Rows you already have in memory go in with
DataSources.FromRows, and a DataTable with
DataSources.FromDataReader(_ => Task.FromResult<DbDataReader>(table.CreateDataReader())).
From LocalReport to Tagua
Section titled “From LocalReport to Tagua”What you did with LocalReport has a direct counterpart:
ReportViewer (LocalReport) |
Tagua |
|---|---|
report.ReportPath = "Invoice.rdlc" |
CompiledReport.Compile(TemplateLoader.LoadFile("Invoice.report.yaml")), once |
report.DataSources.Add(new ReportDataSource("Lines", table)) |
Data = { ["Lines"] = DataSources.FromDataReader(...) } |
report.SetParameters(new ReportParameter("InvoiceId", "42")) |
Parameters = { ["InvoiceId"] = 42 }, typed |
report.Render("PDF") |
Reports.RenderPdfAsync(report, inputs, stream) |
report.Render("EXCELOPENXML") |
ExcelReports.RenderExcelAsync(report, inputs, stream), in Tagua.Excel |
SubreportProcessing event |
The subreport’s datasets, passed by name with the others |
It runs the same on Windows, Linux and macOS, in a container, and with native AOT: see RDLC reports on Linux and in Docker. The migration guide covers what converts and what needs a person, and two worked examples show reports converted and rendered.