Lyo.Reporting.Postgres
2.0.0
dotnet add package Lyo.Reporting.Postgres --version 2.0.0
NuGet\Install-Package Lyo.Reporting.Postgres -Version 2.0.0
<PackageReference Include="Lyo.Reporting.Postgres" Version="2.0.0" />
<PackageVersion Include="Lyo.Reporting.Postgres" Version="2.0.0" />
<PackageReference Include="Lyo.Reporting.Postgres" />
paket add Lyo.Reporting.Postgres --version 2.0.0
#r "nuget: Lyo.Reporting.Postgres, 2.0.0"
#:package Lyo.Reporting.Postgres@2.0.0
#addin nuget:?package=Lyo.Reporting.Postgres&version=2.0.0
#tool nuget:?package=Lyo.Reporting.Postgres&version=2.0.0
Lyo.Reporting.Postgres
PostgreSQL schema (reporting), EF migrations, CSV/XLSX/JSON renderers, the ReportService generation pipeline, and ReportRetentionService cleanup.
HTTP endpoints live in Lyo.Reporting.Api (BuildReportingGroup). This package is service + EF only.
Examples
Wire into DI
services.AddLyoQueryServices();
services.AddLocalCache();
services.AddIOTempService();
services.AddPostgresReportingManagement(o => {
o.ConnectionString = cs;
o.EnableAutoMigrations = true;
o.MaxReportDataJsonBytes = 5_000_000;
o.MaxOutputFileBytes = 50_000_000;
o.AllowAdHocGeneration = false; // default; true accepts caller-authored ReportDataJson
o.MaxConcurrentGenerations = 4; // 0 = unlimited; saturation throws ReportBusyException (HTTP 503)
o.GenerationRetention = TimeSpan.FromDays(90); // null = retention cleanup disabled
});
services.AddReportingWebRenderer(); // optional HTML/PDF
services.AddReportDataProvider<MyProvider>();
services.AddReportingGenerationProfile("my-profile", p => p.DefaultFormat(ReportFormat.Csv));
services.AddReportingGenerationHooks(new ReportGenerationHooks {
AfterRenderAsync = async (ctx, ct) => {
var storage = ctx.Services.GetRequiredService<IFileStorageService>();
var saved = await storage.SaveFileAsync(
ctx.StagedFilePath!,
ctx.FileName,
pathPrefix: ctx.PathPrefix ?? ctx.Request.PathPrefix,
contentType: ctx.ContentType,
ct: ct);
ctx.OutputFileId = saved.Id;
},
// Called before a generation row is removed (retention cleanup, generation delete, or definition delete):
OnCleanupAsync = async (ctx, ct) => {
if (ctx.OutputFileId is Guid fileId) {
var storage = ctx.Services.GetRequiredService<IFileStorageService>();
await storage.DeleteFileAsync(fileId, ct: ct);
}
}
});
// HTTP: Lyo.Reporting.Api AddReportingApi / BuildReportingGroup
Run retention cleanup
// e.g. inside a scheduled job handler
var retention = scope.ServiceProvider.GetRequiredService<ReportRetentionService>();
var deleted = await retention.CleanupAsync(ct);
Design-time migrations
export REPORTING_CONNECTION_STRING="Host=localhost;Database=postgres;..."
dotnet ef migrations add YourMigration --project Plugins/Reporting/Lyo.Reporting.Postgres
Rendering
IEnumerable<IReportRenderer> is resolved from DI by ReportService. It does not project-reference Lyo.Reporting.Web.
| Format | Package | Registration |
|---|---|---|
| CSV | this package | AddPostgresReportingManagement → CsvReportRenderer (every table, appended below a blank separator and title row) |
| XLSX | this package | AddPostgresReportingManagement → XlsxReportRenderer (one worksheet per table) |
| JSON | this package | AddPostgresReportingManagement → JsonReportRenderer (composition JSON verbatim) |
| HTML / PDF | Lyo.Reporting.Web |
host calls AddReportingWebRenderer() (+ AddWebRenderer) |
Optional host IReportDataProvider / ReportingGenerationProfile (keyed by definition GenerationProfileKey) can supply domain data or pre-rendered files before render.
Hardening options
AllowAdHocGeneration(defaultfalse, breaking change — it used to default totrue): whenfalse,GenerateAsyncrequires a savedReportDefinitionIdand rejectsReportDataJson/OverrideReportDataJsonpayloads. Composition JSON authors report markup, so accepting it from a request hands rendering control to the caller. Reruns of stored snapshots remain allowed. Hosts that intentionally offer ad-hoc generation seto.AllowAdHocGeneration = true.PreRenderedFileRoots(default empty = the OS temp directory): filesystem roots a data provider'sPreRenderedFilePathmay live under. Generation copies that path into staging, so confinement stops a provider from publishing any file the host can read. Symlinks are resolved before the check and traversal outside a root is rejected withReportValidationException.- Rendered markup is sanitized:
HtmlandChartcontent blocks pass throughReportHtmlSanitizer(allowlist of elements and attributes) beforeReportViewerwrites them to the DOM, so composition JSON cannot carry script, event handlers, orjavascript:URLs. MaxConcurrentGenerations(default0= unlimited): gates the provider + render section via a process-wideReportGenerationThrottle. When saturated, generate waits briefly then throwsReportBusyException(mapped to HTTP 503 by the API).GenerationRetention(defaultnull= disabled): age after which terminal generations are eligible forReportRetentionServicecleanup.- Input hygiene: request file names are sanitized (directory segments,
.., invalid/control characters stripped; length capped) andReportDataJsonmust parse as JSON. Malformed payloads fail fast withReportValidationExceptionbefore a generation row is persisted. - Failure resilience:
Failedstatus is persisted withCancellationToken.None, so client disconnects can't strand generations inRunning. StuckGenerationTimeoutrecovery guards on liveness, not just age: generations executing in this process are skipped via the singletonReportGenerationLiveTracker, and whenGenerationTimeoutis longer than the stuck timeout the cutoff stretches toGenerationTimeoutplus a five-minute grace. Multi-host deployments should setGenerationTimeoutso recovery can bound a legitimate run.ReportGenerationRes.ReportDataJsonis null on generate and rerun responses unless the caller setsIncludeReportData(breaking change). The composition JSON reachesMaxReportDataJsonBytes(5 MB by default) and most callers only need the status and output identifiers; query and get responses still carry it.
Parameters
- Definition schema:
report_definition_parameter(Key, Type, default Value, Required, validation, EncryptedValue, Options JSON picker source, DefaultKind + DefaultTemplate for expression defaults) - Generation instance:
report_generation_parameter(Key, Type, Value, EncryptedValue) ReportService.GenerateAsyncmerges requestParametersover definition defaults, validates, persists generation rows, and passes typed params plus a synthesized Key→Value JSON map to providers/renderers for transition. After merge,IReportDataSourceResolverruns Query/Sproc parameter Options and fillsFromParameterand Query/Sproc tables so renderers still see static rows. Collection types already serialize as JSON arrays in that map ({"Tag":["a","b"]}). Generate writes via EF (not CRUD), so it bustsentity:reportgenerationandentity:reportdefinitionquery-cache tags after persist.- A parameter with
DefaultKind = Expressionputs a SmartFormat template inDefaultTemplateinstead of a literalValue.MergeParametersrenders it through theLyoTemplateResolverresolved from DI (registered byLyo.Formatter'sAddFormatterService) and normalizes the result to the declaredType, so aSystem.DateTimeparameter defaulting to{DateTime.UtcNow.AddDays(-1):yyyy-MM-dd}reaches providers as a date. A resolver that is missing, throws, or renders the wrong type fails the generate with a caller-facing message rather than silently dropping the parameter.
Parameter validation
- Values are JSON text validated via
LyoTypeInfo.TryValidateJsonagainst the stored CLR FullName (System.Int32,List<string>, …). Legacy enum names (Int,String,Json, …) still resolve.Enumis constrained viaAllowedValues. ValidationRegexruns with a 1-second match timeout and a 500-character pattern cap; a timeout or invalid pattern is reported as a validation error, never an unhandled exception (ReDoS-safe).- When generating from a definition, request parameter keys not declared on the definition are rejected with a clear error listing the unknown keys. Definition-less (ad-hoc) generates accept any keys.
- A required parameter is satisfied by either
ValueorEncryptedValue. - Write-time checks live in
ReportDefinitionWriteValidator, which runsLyoParameterValidator.ValidateSpec— including the rule that an expression default must carry a template and a literal default must not — so bad metadata fails on save instead of on every generate that needs the default. - Validation failures throw
ReportValidationException, which the API maps to HTTP 400.
Rerun
A past generation is replayed by ReportService.RerunAsync(generationId, createdBy) from its stored snapshot (composition JSON, format, file name, path prefix, parameters) into a new generation row. The saved definition is intentionally not re-read, so a since-changed or deactivated definition cannot alter the rerun.
Retention cleanup
Terminal (Succeeded/Failed) generations older than GenerationRetention are batch-deleted by ReportRetentionService.CleanupAsync, oldest first, keeping in-flight (Pending/ Running) rows. Before each row is removed, ReportGenerationHooks.OnCleanupAsync runs so the host can delete the persisted output blob; a hook failure logs and retains that row. Emits the reporting.generation.cleaned metric. The service is registered by AddPostgresReportingManagement but not scheduled. hosts trigger it themselves, e.g. via Lyo.Scheduler or a Lyo.Job interval job:
Generation cost
CsvReportRendererwrites to the output file in row chunks instead of materializing every formatted cell, so peak memory tracks the chunk size rather than the report size.- The
MaxConcurrentGenerationsslot covers the provider and render work only. It is released as soon as the output is staged, so storage uploads, the terminal status write, and the failure path no longer occupy a slot.
Dependencies
Generated from ProjectReference / PackageReference (same model as docs/Lyo.ProjectGraph.html).
Lyo.Api(direct, lyo)Lyo.Audit(direct, lyo)Lyo.Common.Metadata(direct, lyo)Lyo.Configuration(direct, lyo)Lyo.Csv(direct, lyo)Lyo.Exceptions(direct, lyo)Lyo.IO.Temp(direct, lyo)Lyo.Metrics(direct, lyo)Lyo.Parameters(direct, lyo)Lyo.Postgres(direct, lyo)Lyo.Query.Models(direct, lyo)Lyo.Reporting.Models(direct, lyo)Lyo.Xlsx(direct, lyo)Microsoft.EntityFrameworkCore10.0.5(direct, microsoft)Microsoft.EntityFrameworkCore.Design10.0.5(direct, microsoft)Lyo.Api.Models(transitive, lyo)Lyo.Cache(transitive, lyo)Lyo.Common.Core(transitive, lyo)Lyo.Common.Json(transitive, lyo)Lyo.Compression(transitive, lyo)Lyo.Csv.Models(transitive, lyo)Lyo.DataTable.Models(transitive, lyo)Lyo.DateAndTime(transitive, lyo)Lyo.Diagnostic(transitive, lyo)Lyo.Diagnostic.AspNetCore(transitive, lyo)Lyo.Diff(transitive, lyo)Lyo.Encryption(transitive, lyo)Lyo.EntityReference.Models(transitive, lyo)Lyo.Formatter(transitive, lyo)Lyo.Hashing(transitive, lyo)Lyo.Health(transitive, lyo)Lyo.KeyStore(transitive, lyo)Lyo.PackageMetadata(transitive, lyo)Lyo.Query(transitive, lyo)Lyo.Query.Evaluation(transitive, lyo)Lyo.Result(transitive, lyo)Lyo.Streams(transitive, lyo)Lyo.Validation(transitive, lyo)Lyo.Validation.Models(transitive, lyo)Lyo.Xlsx.Models(transitive, lyo)BouncyCastle.Cryptography2.6.2(transitive, third-party, netstandard2.0)ClosedXML0.105.0(transitive, third-party)DocumentFormat.OpenXml3.1.1(transitive, third-party)DynamicExpresso.Core2.19.3(transitive, third-party)EasyCompressor2.1.0(transitive, third-party)ExcelDataReader3.9.0(transitive, third-party)ExcelDataReader.DataSet3.9.0(transitive, third-party)Konscious.Security.Cryptography.Argon21.3.1(transitive, third-party)Microsoft.AspNetCore.OpenApi10.0.5(transitive, microsoft)Microsoft.Bcl.AsyncInterfaces10.0.5(transitive, microsoft, netstandard2.0)Microsoft.EntityFrameworkCore.Analyzers10.0.5(transitive, microsoft)Microsoft.EntityFrameworkCore.Relational10.0.5(transitive, microsoft)Microsoft.Extensions.Caching.Memory10.0.5(transitive, microsoft)Microsoft.Extensions.Configuration10.0.5(transitive, microsoft)Microsoft.Extensions.Configuration.Binder10.0.5(transitive, microsoft)Microsoft.Extensions.DependencyInjection10.0.5(transitive, microsoft)Microsoft.Extensions.DependencyInjection.Abstractions10.0.5(transitive, microsoft, net10.0, netstandard2.0)Microsoft.Extensions.Hosting.Abstractions10.0.5(transitive, microsoft)Microsoft.Extensions.Logging.Abstractions10.0.5(transitive, microsoft)Microsoft.Extensions.Options10.0.5(transitive, microsoft)Microsoft.Extensions.Options.ConfigurationExtensions10.0.5(transitive, microsoft)Npgsql.EntityFrameworkCore.PostgreSQL10.0.3(transitive, third-party)SmartFormat.NET3.6.1(transitive, third-party)System.Buffers4.6.1(transitive, microsoft, netstandard2.0)System.ComponentModel.Annotations5.0.0(transitive, microsoft)System.IO.Hashing10.0.5(transitive, microsoft, net10.0)System.Memory4.6.3(transitive, microsoft, netstandard2.0)System.Text.Encoding.CodePages10.0.5(transitive, microsoft)System.Text.Json10.0.5(transitive, microsoft, netstandard2.0)System.Threading.Tasks.Extensions4.6.3(transitive, microsoft, netstandard2.0)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Lyo.Api (>= 2.0.0)
- Lyo.Audit (>= 2.0.0)
- Lyo.Common.Metadata (>= 2.0.0)
- Lyo.Configuration (>= 2.0.0)
- Lyo.Csv (>= 2.0.0)
- Lyo.Exceptions (>= 2.0.0)
- Lyo.IO.Temp (>= 2.0.0)
- Lyo.Metrics (>= 2.0.0)
- Lyo.Parameters (>= 2.0.0)
- Lyo.Postgres (>= 2.0.0)
- Lyo.Query.Models (>= 2.0.0)
- Lyo.Reporting.Models (>= 2.0.0)
- Lyo.Xlsx (>= 2.0.0)
- Microsoft.EntityFrameworkCore (>= 10.0.5)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Lyo.Reporting.Postgres:
| Package | Downloads |
|---|---|
|
Lyo.Api.Reporting
HTTP API surface for Lyo Reporting: authenticated definition CRUD, read-only generations, and Generate. |
|
|
Lyo.Reporting.Api
HTTP API surface for Lyo Reporting: authenticated definition CRUD, read-only generations, and Generate. |
GitHub repositories
This package is not used by any popular GitHub repositories.