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 PDF 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):

  1. ReportRequestOptions (per-request)
  2. ConfigureReport (per-report)
  3. 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

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

Version Downloads Last updated
1.0.1 1 30/03/2026
1.0.0 2 25/03/2026