KNM.Reporting 1.0.1
KNM.Reporting
Unified reporting abstraction for .NET 10 that supports multiple report engines (SSRS, FastReport, QuestPDF, ClosedXML) with fluent DI configuration, automatic discovery, batch generation, caching, and a post-processing pipeline.
Supported Engines
| Engine | Type | Output Formats | Use Case |
|---|---|---|---|
| SSRS | Server-based | PDF, Excel, Word, HTML, CSV, Image | SQL Server Reporting Services (REST & SOAP) |
| FastReport | Template-based | PDF, Excel, HTML, CSV, Image | .frx template reports from filesystem or database |
| QuestPDF | Code-first | Programmatic PDF generation with C# | |
| ClosedXML | Code-first | Excel (.xlsx) | Data exports, spreadsheet reports |
Installation
# Add the KoNiMa private NuGet feed (one-time)
dotnet nuget add source https://nuget.konima.it/v3/index.json -n KoNiMa
# Install the package
dotnet add package KNM.Reporting
Quick Start
1. Register services
// Program.cs
builder.Services.AddKnmReporting(r => r
.WithQuestPdf()
.WithDefaultFormat(ReportFormat.Pdf));
2. Implement a report
public class InvoiceReport : IQuestPdfReport
{
public string ReportId => "invoice";
public string Name => "Invoice";
public string Description => "Customer invoice report";
public IReadOnlyList<ReportParameterInfo> Parameters =>
[
new() { Name = "InvoiceId", DisplayName = "Invoice ID", ValueType = typeof(int), Required = true }
];
public IDocument Build(ReportParameters parameters)
{
var invoiceId = parameters.Get<int>("InvoiceId");
return Document.Create(container =>
{
container.Page(page =>
{
page.Content().Text($"Invoice #{invoiceId}");
});
});
}
}
3. Register the report and generate
// Register the report implementation
builder.Services.AddTransient<IQuestPdfReport, InvoiceReport>();
// Generate
var result = await reportingService.GenerateAsync("invoice",
new ReportParameters().Set("InvoiceId", 42));
if (result.Status == Status.Ok)
{
var bytes = result.Data!.Content; // byte[]
var mime = result.Data!.MimeType; // "application/pdf"
var fileName = result.Data!.FileName; // "invoice.pdf"
}
Configuration
The fluent builder provides full control over engines, defaults, caching, and per-report overrides:
builder.Services.AddKnmReporting(r => r
// Engines
.WithSsrs(ssrs => ssrs
.UseRestApi()
.WithBaseUrl("https://reports.example.com/ReportServer")
.WithWindowsAuth())
.WithFastReport(fr => fr
.WithTemplatePath("Reports/Templates"))
.WithQuestPdf()
.WithClosedXml()
// Global defaults
.WithDefaultFormat(ReportFormat.Pdf)
.WithDefaultTimeout(TimeSpan.FromSeconds(90))
// Caching
.WithCaching(cache =>
{
cache.Enabled = true;
cache.DefaultDuration = TimeSpan.FromMinutes(10);
})
// Progress reporting
.WithProgressReporting()
// Per-report overrides
.ConfigureReport("monthly-revenue", cfg => cfg
.UseEngine(ReportEngine.Ssrs)
.WithFormat(ReportFormat.Excel)
.WithTimeout(TimeSpan.FromMinutes(5))
.WithCaching(TimeSpan.FromHours(1))
.WithPostProcessor<WatermarkPostProcessor>())
);
Engines
SSRS (SQL Server Reporting Services)
Supports both the modern REST API (SSRS 2017+ / Power BI Report Server) and the legacy SOAP API.
// REST API with Windows authentication
.WithSsrs(ssrs => ssrs
.UseRestApi()
.WithBaseUrl("https://reports.example.com/ReportServer")
.WithWindowsAuth("DOMAIN"))
// SOAP API with Basic credentials
.WithSsrs(ssrs => ssrs
.UseSoapApi()
.WithBaseUrl("https://reports.example.com/ReportServer")
.WithCredentials("user", "password", "DOMAIN")
.WithTimeout(TimeSpan.FromMinutes(3)))
Authentication options:
WithWindowsAuth(domain?)— uses the app pool identity or current user credentials (Negotiate)WithCredentials(username, password, domain?)— explicit Basic credentials
Supported formats: PDF, Excel, Word, HTML, CSV, Image
FastReport
Template-based reporting using FastReport Open Source. Templates can be loaded from the filesystem or a custom storage backend (e.g., database, blob storage).
// Filesystem templates
.WithFastReport(fr => fr
.WithTemplatePath("Reports/Templates")
.WithFileExtension(".frx"))
// Database templates (custom loader)
.WithFastReport(fr => fr
.WithTemplateLoader(
loader: async (reportId, ct) =>
{
// Load .frx template bytes from database
return await dbContext.Templates
.Where(t => t.ReportId == reportId)
.Select(t => t.Content)
.FirstOrDefaultAsync(ct);
},
lister: async ct =>
{
// List all available report IDs
return await dbContext.Templates
.Select(t => t.ReportId)
.ToListAsync(ct);
}))
Supported formats: PDF, Excel, HTML, CSV, Image
QuestPDF (Code-First PDF)
Create PDF reports entirely in C# by implementing IQuestPdfReport. Implementations registered in DI are auto-discovered.
public class MonthlyRevenueReport : IQuestPdfReport
{
public string ReportId => "monthly-revenue";
public string Name => "Monthly Revenue";
public string Description => "Revenue breakdown by month";
public IReadOnlyList<ReportParameterInfo> Parameters =>
[
new() { Name = "Year", DisplayName = "Year", ValueType = typeof(int), Required = true },
new() { Name = "Month", DisplayName = "Month", ValueType = typeof(int), Required = true }
];
public IDocument Build(ReportParameters parameters)
{
var year = parameters.Get<int>("Year");
var month = parameters.Get<int>("Month");
return Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Header().Text($"Revenue Report - {month:00}/{year}").FontSize(20);
page.Content().Column(col =>
{
col.Item().Text("Revenue data goes here...");
});
page.Footer().AlignCenter().Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
x.Span(" of ");
x.TotalPages();
});
});
});
}
}
// Register in DI
builder.Services.AddTransient<IQuestPdfReport, MonthlyRevenueReport>();
Output format: PDF only
ClosedXML (Code-First Excel)
Create Excel reports entirely in C# by implementing IClosedXmlReport. Implementations registered in DI are auto-discovered.
public class UserExportReport : IClosedXmlReport
{
public string ReportId => "user-export";
public string Name => "User Export";
public string Description => "Export all active users to Excel";
public IReadOnlyList<ReportParameterInfo> Parameters =>
[
new() { Name = "IncludeDisabled", DisplayName = "Include Disabled", ValueType = typeof(bool),
Required = false, DefaultValue = false }
];
public XLWorkbook Build(ReportParameters parameters)
{
var includeDisabled = parameters.Get<bool>("IncludeDisabled");
var workbook = new XLWorkbook();
var sheet = workbook.Worksheets.Add("Users");
// Header row
sheet.Cell(1, 1).Value = "Name";
sheet.Cell(1, 2).Value = "Email";
sheet.Cell(1, 3).Value = "Active";
sheet.Row(1).Style.Font.Bold = true;
// Data rows...
return workbook;
}
}
// Register in DI
builder.Services.AddTransient<IClosedXmlReport, UserExportReport>();
Output format: Excel (.xlsx) only
Report Discovery
Discover all available reports across all registered engines:
// Get all available reports
var result = await reportingService.GetAvailableReportsAsync();
if (result.Status == Status.Ok)
{
foreach (var report in result.Data!)
{
Console.WriteLine($"{report.ReportId}: {report.Name} ({report.Engine})");
Console.WriteLine($" Formats: {string.Join(", ", report.SupportedFormats)}");
foreach (var param in report.Parameters)
Console.WriteLine($" Param: {param.Name} ({param.ValueType.Name}, required: {param.Required})");
}
}
// Get info for a specific report
var info = await reportingService.GetReportInfoAsync("monthly-revenue");
Batch Generation
Generate multiple reports in parallel with configurable parallelism:
var requests = new[]
{
("monthly-revenue", new ReportParameters().Set("Year", 2026).Set("Month", 1)),
("monthly-revenue", new ReportParameters().Set("Year", 2026).Set("Month", 2)),
("monthly-revenue", new ReportParameters().Set("Year", 2026).Set("Month", 3)),
};
var options = new ReportRequestOptions { MaxParallelism = 4 };
var batchResult = await reportingService.GenerateBatchAsync(requests, options);
if (batchResult.Status == Status.Ok)
{
var batch = batchResult.Data!;
Console.WriteLine($"Success: {batch.SuccessCount}, Failed: {batch.FailureCount}");
Console.WriteLine($"Total time: {batch.TotalGenerationTimeMs}ms");
foreach (var item in batch.Items)
{
if (item.IsSuccess)
File.WriteAllBytes($"report_{item.Index}.pdf", item.Result!.Content);
else
Console.WriteLine($"Item {item.Index} failed: {item.ErrorMessage}");
}
}
Stream Support
For large reports, use GenerateStreamAsync to avoid loading the entire content into memory:
var streamResult = await reportingService.GenerateStreamAsync("large-export",
new ReportParameters().Set("DateFrom", DateTime.Today.AddYears(-1)));
if (streamResult.Status == Status.Ok)
{
await using var stream = streamResult.Data!;
await using var fileStream = File.Create("large-export.xlsx");
await stream.CopyToAsync(fileStream);
}
Progress Reporting
Track long-running report generation with IProgress<ReportProgress>:
// Enable globally
.WithProgressReporting()
// Use when generating
var progress = new Progress<ReportProgress>(p =>
{
Console.WriteLine($"[{p.Percentage}%] {p.Phase}: {p.Message}");
});
var result = await reportingService.GenerateAsync("monthly-revenue",
new ReportParameters().Set("Year", 2026).Set("Month", 3),
progress: progress);
Progress phases: Rendering, PostProcessing
Post-Processing Pipeline
Transform report output after generation using IReportPostProcessor. Post-processors execute in Order (lower values first).
Implementing a post-processor
public class WatermarkPostProcessor : IReportPostProcessor
{
public int Order => 10;
public async Task<ReportResult> ProcessAsync(ReportResult input, CancellationToken ct = default)
{
// Transform input.Content (e.g., add watermark to PDF)
input.Metadata["Watermarked"] = true;
return input;
}
}
Registering post-processors
// Per-report post-processor
.ConfigureReport("draft-invoice", cfg => cfg
.WithPostProcessor<WatermarkPostProcessor>())
// Per-request post-processor
var options = new ReportRequestOptions();
options.PostProcessors.Add(typeof(WatermarkPostProcessor));
var result = await reportingService.GenerateAsync("invoice", parameters, options);
Per-Report Configuration
Override global defaults for specific reports:
.ConfigureReport("heavy-report", cfg => cfg
.UseEngine(ReportEngine.Ssrs)
.WithFormat(ReportFormat.Excel)
.WithTimeout(TimeSpan.FromMinutes(10))
.WithCaching(TimeSpan.FromHours(2))
.WithPostProcessor<CompressionPostProcessor>())
Configuration priority (highest wins):
ReportRequestOptions(per-request)ConfigureReport(per-report)- Global defaults (
WithDefaultFormat,WithDefaultTimeout)
Caching
Enable optional report result caching backed by IAppCache (from KNM.Core):
.WithCaching(cache =>
{
cache.Enabled = true;
cache.DefaultDuration = TimeSpan.FromMinutes(5);
})
// Override per report
.ConfigureReport("static-report", cfg => cfg
.WithCaching(TimeSpan.FromHours(24)))
// Override per request
var options = new ReportRequestOptions { CacheDuration = TimeSpan.FromMinutes(1) };
Cache keys are computed from the report ID, parameters, and format. Subsequent calls with the same inputs return the cached result without re-rendering.
API Reference
Core Interfaces
| Interface | Description |
|---|---|
IReportingService |
Main consumer-facing service. Orchestrates generation, caching, and post-processing. |
IReportProvider |
Engine-specific report provider. Each engine implements this. |
IReportDiscovery |
Per-engine report discovery. Provides metadata about available reports. |
IReportPostProcessor |
Post-processor that transforms a ReportResult after generation. |
Code-First Interfaces
| Interface | Description |
|---|---|
IQuestPdfReport |
Implement to create a code-first PDF report using QuestPDF. |
IClosedXmlReport |
Implement to create a code-first Excel report using ClosedXML. |
IReportingService Methods
| Method | Returns | Description |
|---|---|---|
GenerateAsync(reportId, parameters?, options?, progress?, ct) |
Result<ReportResult> |
Generates a report and returns content bytes. |
GenerateStreamAsync(reportId, parameters?, options?, progress?, ct) |
Result<Stream> |
Generates a report and returns a stream (for large reports). |
GenerateBatchAsync(requests, options?, progress?, ct) |
Result<ReportBatchResult> |
Generates multiple reports in parallel. |
GetAvailableReportsAsync(ct) |
Result<IReadOnlyList<ReportInfo>> |
Discovers all available reports across engines. |
GetReportInfoAsync(reportId, ct) |
Result<ReportInfo> |
Gets detailed info about a specific report. |
IReportProvider Methods
| Method | Returns | Description |
|---|---|---|
Engine |
ReportEngine |
The engine this provider handles. |
GenerateAsync(request, progress?, ct) |
Result<ReportResult> |
Generates a report from a ReportRequest. |
GenerateStreamAsync(request, progress?, ct) |
Result<Stream> |
Generates a report as a stream. |
IReportDiscovery Methods
| Method | Returns | Description |
|---|---|---|
Engine |
ReportEngine |
The engine this discovery handles. |
DiscoverAsync(ct) |
Result<IReadOnlyList<ReportInfo>> |
Discovers all available reports for this engine. |
GetInfoAsync(reportId, ct) |
Result<ReportInfo> |
Gets info about a specific report by ID. |
IQuestPdfReport Members
| Member | Type | Description |
|---|---|---|
ReportId |
string |
Unique report identifier. |
Name |
string |
Display name. |
Description |
string |
Human-readable description. |
Parameters |
IReadOnlyList<ReportParameterInfo> |
Parameter definitions. |
Build(parameters) |
IDocument |
Builds the QuestPDF document. |
IClosedXmlReport Members
| Member | Type | Description |
|---|---|---|
ReportId |
string |
Unique report identifier. |
Name |
string |
Display name. |
Description |
string |
Human-readable description. |
Parameters |
IReadOnlyList<ReportParameterInfo> |
Parameter definitions. |
Build(parameters) |
XLWorkbook |
Builds the ClosedXML workbook. |
IReportPostProcessor Members
| Member | Type | Description |
|---|---|---|
Order |
int |
Execution order (lower = first). |
ProcessAsync(input, ct) |
Task<ReportResult> |
Transforms the report result. |
Models
| Model | Description |
|---|---|
ReportResult |
Generated report output: Content (byte[]), FileName, MimeType, Format, GenerationTimeMs, Metadata. |
ReportRequest |
Internal request model: ReportId, Parameters, Format, Timeout, Metadata. |
ReportParameters |
Typed dictionary wrapper with Get<T>(key), Set(key, value), ContainsKey(key), ToDictionary(). |
ReportRequestOptions |
Per-request overrides: Format, Timeout, CacheDuration, PostProcessors, MaxParallelism. |
ReportProgress |
Progress model: Percentage (0-100), Message, Phase. |
ReportInfo |
Report metadata: ReportId, Name, Description, Engine, Parameters, SupportedFormats. |
ReportParameterInfo |
Parameter metadata: Name, DisplayName, ValueType, Required, DefaultValue, AllowedValues. |
ReportBatchResult |
Batch result: Items, SuccessCount, FailureCount, TotalGenerationTimeMs. |
ReportBatchItem |
Per-item result: Index, Result, ErrorMessage, IsSuccess. |
Enums
| Enum | Values |
|---|---|
ReportEngine |
Ssrs, FastReport, QuestPdf, ClosedXml |
ReportFormat |
Pdf, Excel, Word, Html, Csv, Image |
Configuration Classes
| Class | Description |
|---|---|
ReportingOptions |
Global options: DefaultFormat, DefaultTimeout, ProgressReportingEnabled, Cache, Reports. |
CacheOptions |
Cache settings: Enabled, DefaultDuration. |
ReportConfiguration |
Per-report overrides: Engine, Format, Timeout, CacheDuration, PostProcessors. |
SsrsOptions |
SSRS settings: BaseUrl, Protocol, Timeout, Username, Password, Domain, UseWindowsAuth. |
FastReportOptions |
FastReport settings: Source, TemplatePath, FileExtension, TemplateLoader, TemplateLister. |
QuestPdfOptions |
QuestPDF settings (extension point for future configuration). |
ClosedXmlOptions |
ClosedXML settings (extension point for future configuration). |
Extension Methods
| Method | Description |
|---|---|
IServiceCollection.AddKnmReporting(Action<ReportingBuilder>) |
Registers all KNM.Reporting services with fluent configuration. |
ReportFormat.ToMimeType() |
Maps a format to its MIME type string. |
ReportFormat.ToFileExtension() |
Maps a format to a file extension (e.g., .pdf). |
Dependencies
- KNM.Core >= 1.5.0 — Infrastructure (Result<T>, caching, progress tracking, event bus)
- FastReport.OpenSource — Template-based report engine
- QuestPDF — Programmatic PDF generation
- ClosedXML — Excel generation
- System.ServiceModel.Http — WCF client for SSRS SOAP
Changelog
1.0.1
- Updated KNM.Core dependency from 1.3.1 to 1.5.0
1.0.0
- Initial release with SSRS, FastReport, QuestPDF, and ClosedXML engines
- Fluent DI configuration
- Report discovery, batch generation, stream support
- Caching, progress reporting, post-processing pipeline
License
Commercial product by KoNiMa S.r.l. — Not open source.
Showing the top 20 packages that depend on KNM.Reporting.
| Packages | Downloads |
|---|---|
|
KNM.Reporting.Designer
Plug and play Blazor admin panel for report generation, management, and monitoring in the KoNiMa ecosystem
|
0 |
.NET 10.0
- ClosedXML (>= 0.105.0)
- FastReport.OpenSource (>= 2026.1.8)
- FastReport.OpenSource.Export.PdfSimple (>= 2026.1.8)
- KNM.Core (>= 1.5.0)
- QuestPDF (>= 2026.2.4)
- System.ServiceModel.Http (>= 10.0.652802)
- System.ServiceModel.Primitives (>= 10.0.652802)