NETReporter.Client 2.2.0

NETReporter.Client

Tenký klient pro ReportServer: stejné rozhraní jako knihovna NETReporter, ale report se vykreslí na serveru. Aplikace nemusí nést QuestPDF, Roslyn, OxyPlot ani ZXing; přihlašuje se API tokenem, který si vygenerujete v UI ReportServeru.

Klient implementuje IReportRenderer a ITemplateTools z balíčku NETReporter.Abstractions, takže mezi lokálním a vzdáleným renderem přepíná jen registrace v DI. Kód, který renderuje, se nemění.

Instalace

dotnet nuget add source https://nuget.advisor-soft.com/v3/index.json --name AdvisorSoft
dotnet add package NETReporter.Client

Požadavky: .NET 10. Balíčky NETReporter, NETReporter.Abstractions a NETReporter.Client se vydávají společně se stejnou verzí.

API token

  1. Přihlaste se do UI ReportServeru a otevřete stránku API tokeny.
  2. Zadejte název (a případně dobu platnosti) a token vytvořte. Otevřený text se ukáže jen jednou, server si pamatuje jen hash; uložte ho do tajných hodnot (proměnná prostředí, user secrets, trezor), ne do repozitáře.
  3. Token má stejná práva jako jeho vlastník. Odvolat ho může vlastník i správce; deaktivace vlastníka ho vyřadí hned.

Rychlý start s DI

using Microsoft.Extensions.DependencyInjection;
using NetReporter;

services.AddNetReporterClient(options =>
{
    options.BaseAddress = new Uri("https://reports.example.com/api");   // adresa API včetně předpony cesty
    options.ApiToken = configuration["NetReporter:ApiToken"];           // nrp_…
});

// kdekoli v aplikaci
public sealed class InvoiceService(IReportRenderer renderer)
{
    public async Task<byte[]> CreatePdfAsync(Stream template, Stream data)
    {
        await using var pdf = await renderer.RenderAsync(template, data);
        using var buffer = new MemoryStream();
        await pdf.CopyToAsync(buffer);
        return buffer.ToArray();
    }
}

Nastavení jde číst z konfigurace: services.AddNetReporterClient(configuration) čte sekci NetReporterClient s klíči BaseAddress, ApiToken a Timeout. Chybějící adresa nebo token zastaví aplikaci při startu srozumitelnou chybou.

Bez DI

using NetReporter.Client;

using var http = NetReporterClient.CreateHttpClient(new NetReporterClientOptions
{
    BaseAddress = new Uri("https://reports.example.com/api"),
    ApiToken = Environment.GetEnvironmentVariable("NETREPORTER_API_TOKEN")
});

IReportRenderer renderer = new ReportServerRenderer(http);
ITemplateTools tools = new ReportServerTemplateTools(http);

Co klient umí

  • Render libovolné šablony (ad-hoc) do všech formátů: PDF, HTML, JSON, SVG, WebP, PNG, JPEG, i po stránkách (RenderSvgPagesAsync, RenderWebpPagesAsync, …) a se všemi volbami RenderOptions (PDF/A, přílohy, šifrování, vodoznak, rozlišení…).
  • Diagnostiky bindingů přes RenderOptions.OnBindingDiagnostics a pozorovatele pipeline.
  • Nástroje nad šablonou (ITemplateTools): validace šablony a dat, datový kontrakt a ukázková data.
  • Mimo rozsah: správa uložených šablon, render podle uložené šablony a historie jobů (ty zůstávají v UI a REST API).

Čím se vzdálený render liší od lokálního

  • Soubory z disku se na serveru nenačítají: obrázky, SVG ani podklady PDF z cesty k souboru nefungují (použijte data URI nebo adresu http/https).
  • Server omezuje počet stránek, rozlišení obrazů a velikost příloh; diagnostik bindingů vrací nejvýše 50 různých.
  • RenderOptions.Icons se nepoužije: server vykresluje ikony vlastním katalogem.
  • Pozorovatel pipeline dostane zjednodušené události (parsování proběhlo na serveru).

Chyby

Situace Výjimka
Chyba v šabloně ReportServerException (kód TEMPLATE_VALIDATION_FAILED, Diagnostics s JSON cestami)
Vadné volby ArgumentException (ParamName == "options"), u lokálních kontrol bez volání serveru
Překročený limit stránek RenderPageLimitExceededException
Zrušení volajícím OperationCanceledException
Neplatný, odvolaný nebo prošlý token ReportServerException (AUTH_UNAUTHORIZED)
Server nedostupný nebo překročený Timeout ReportServerException (REPORTSERVER_UNAVAILABLE nebo REPORTSERVER_TIMEOUT)

ReportServerException dědí z InvalidOperationException, takže ji zachytí i kód napsaný pro lokální render. Seznam změn je v souboru CHANGELOG.md v balíčku.

No packages depend on NETReporter.Client.

- Template editor: the look of the current template can be saved as an own **theme**. The card Motiv has the new buttons **Uložit jako motiv…** and **Moje motivy…**. A theme takes the palette of the template (`meta.palette`) and from every named style only what a theme may set (font, size, colours, line height; borders and padding are left out so applying it elsewhere never causes NRP020 or NRP021); colours written directly in the styles are turned into palette entries for the theme first, on a copy, so the template does not change. The id of a theme is the slug of its name, unique among the own themes (the same name is never overwritten, it gets `-2`). The picker groups the themes: *Dodávané motivy*, *Moje motivy* and *Veřejné motivy ostatních*. Storage is the same choice as for snippets: the folder `themes` next to the templates without a database (on the volume in k3s), or the new table `editor."Themes"` with a database (owner, visibility and revision like `Snippets`; the migration `AddThemes` runs when the editor starts, so take a backup first as usual). With the database an own theme can be made public (every signed-in user sees and applies it, only the owner changes or deletes it) and the dialog Moje motivy deletes own themes. Deleting an account in the ReportServer also deletes the themes of the account (`EditorThemes` in the audit entry; `EditorUserDataCleanupResult` has the new `Themes` count before `Error`). New `IThemeStore`, `FileThemeStore`, `DatabaseThemeStore`, `ThemeCatalog` and `ThemeExtractor`; the MCP server still offers only the bundled themes. - Template editor: "Vytáhnout barvy do palety" can merge near shades. The dialog has the new choice **Sloučit blízké odstíny** (No, or a colour distance CIEDE2000 up to 2 "nearly identical", 5 "close" or 10 "similar"): colours of the same class (text, background, border, shadow) and the same transparency within that distance become one palette entry, named by the colour that is already in the palette, otherwise by the most frequent one. The places of the merged shades get the name of that colour, so their colour changes slightly (the result reports how many); chart colours are never merged. A row of the dialog lists the merged shades ("sloučeno: #1F4E7A (2×)"). Changing the choice proposes the colours again, so edits of the names are lost. Nothing is merged by default and applying a theme with "extract colours first" does not merge. New `ColorHelper.DeltaE` (CIEDE2000, `ToLab`) and `PaletteExtractionService.Propose(template, mergeDeltaE)`. - Template format: the node `Image` has the new optional property `alt`, an alternative text for screen readers (a binding `{{...}}` is allowed; an empty text or a binding that evaluates to empty is not written). HTML writes it as the attribute `alt` of the `img` element, JSON as the property `alt` of the image node. PDF: the text goes to the PDF as the description of a Figure tag (`SemanticImage`), but QuestPDF writes semantic tags only with `DocumentSettings.PDFUA_Conformance`, which the renderer does not set yet (ToDo #83), so a PDF without PDF/UA is unchanged. The data contract of a template includes the fields read by `alt`. `IRenderSink` has the new method `Image(ResolvedStyle, ResolvedImage, string?)` (`ResolvedImage` carries the source, the size and `Alt`); its default implementation drops the text and calls the old `Image(style, src, widthMm, heightMm, elementId)`, so custom sinks work unchanged. - MCP: a personal ReportServer API token (`nrp_…`, created in the ReportServer UI) now also works as the login of the MCP server over HTTP. It is optional and off by default (`NetReporterMcp:Auth:UserTokens:Enabled`, env `MCP_AUTH_USER_TOKENS=true`); the static tokens (`MCP_AUTH_TOKEN`, `MCP_AUTH_READER_TOKEN`) stay in the configuration and work with or without a database. With the switch on, the MCP server checks the token directly in the ReportServer database with the same rules as the API (the token exists, is not revoked or expired, its owner is active and has the role `User` or `Admin`; nothing is cached; `LastUsedUtc` is written at most once in 5 minutes) and gives it the roles `reader` and `writer`. The ReportServer tools then act as the owner of the token: the MCP server calls the API with the caller's own token (their templates, render jobs recorded under that token) and never falls back to the service account, while a caller with a static token still uses the service account. Failed checks are counted per client address (`Auth:UserTokens:FailurePermitLimit` and `FailureWindowSeconds`, default 60 per 60 s) and a client over the limit gets `429` with `Retry-After` before any database query, so invalid tokens are never forwarded to the API (whose own limiter is per address and would be shared by all MCP users). The database is `MCP_AUTH_USER_TOKENS_CONNECTION_STRING`, otherwise `REPORTSERVER_CONNECTION_STRING` (the accounts from `ConnectionStrings:IdentityDb`); `deploy/k8s/sql/mcp-user-tokens-role.sql` creates a database role that can only read tokens and accounts (not the password hashes) and write `LastUsedUtc`. Without static tokens the service account is optional. `/healthz` reports `user-token-db`; the tool descriptions and the server instructions speak of the "calling account". k3s: the optional key `MCP_AUTH_USER_TOKENS_CONNECTION_STRING` in `netreporter-mcp-secrets` and the switch `MCP_AUTH_USER_TOKENS: 'false'` in the configmap (steps in `deploy/k8s/README.md`). Internal: `ApiTokenFailureLimiter` moved to `NetReporter.ReportServer.Core` and the token owner store (`IApiTokenOwnerStore`, `IdentityApiTokenOwnerStore`, `ApiTokenOwner`) to `NetReporter.Identity`. Celý seznam změn: CHANGELOG.md v balíčku a https://dev.azure.com/advsoft/Knihovny/_git/NetReporter?path=/src/NetReporter/CHANGELOG.md

Version Downloads Last updated
3.9.2 4 10/28/2026
3.9.1 1 10/27/2026
3.9.0 8 10/20/2026
3.8.1 3 10/19/2026
3.8.0 3 10/19/2026
3.7.0 3 10/19/2026
3.6.0 4 10/17/2026
3.5.1 4 10/15/2026
3.5.0 4 10/15/2026
3.4.2 4 10/15/2026
3.4.0 4 10/12/2026
3.3.0 4 10/11/2026
3.2.2 4 10/10/2026
3.2.1 4 10/10/2026
3.2.0 4 10/10/2026
3.1.1 4 10/09/2026
3.1.0 4 10/09/2026
3.0.1 4 10/08/2026
3.0.0 4 10/08/2026
2.2.1 5 10/05/2026
2.2.0 4 10/05/2026
2.1.0 4 10/04/2026
2.0.1 4 10/04/2026
2.0.0 4 10/03/2026
1.7.0 4 10/03/2026