KNM.Reporting 1.0.2

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.AI
AI conversational agent for KNM.Reporting with Ollama tool use, session management, and multi-DbContext support
0
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.2 0 04/04/2026
1.0.1 1 30/03/2026
1.0.0 2 25/03/2026