# CertReports.Syncfusion — Documentazione Tecnica > Progetto ASP.NET Core 8 per la generazione di report PDF per certificati finanziari strutturati. > Sostituisce il vecchio progetto WebForms basato su DevExpress (non licenziato) + PdfSharp. > Utilizza **Syncfusion PDF Library** (Community License) e **SkiaSharp** per i grafici. --- ## 1. Panoramica Il sistema riceve un codice ISIN (in chiaro, cifrato o tramite alias), interroga il database SQL Server tramite stored procedures, genera un report PDF composto da 4 sezioni e lo restituisce come stream inline al browser. ### Flusso di generazione ``` Richiesta HTTP (ISIN) │ ▼ ReportController ──→ decodifica ISIN (se cifrato) │ ▼ ReportOrchestrator │ ├──→ CertificateDataService │ ├── rpt_Master_CFT_ISIN → Anagrafica certificato │ ├── rpt_Details_UL_ISIN → Sottostanti │ ├── rpt_Events_CFT_ISIN → Eventi │ └── rpt_AnalisiScenario_ISIN → Analisi scenario │ ├──→ AnagraficaSectionRenderer → PDF Sezione 1 ├──→ EventiSectionRenderer → PDF Sezione 2 ├──→ ScenarioSectionRenderer → PDF Sezione 3 (se dati presenti) │ ├──→ ChartDataService │ ├── FSWeb_Chart_UL → Info sottostanti grafico │ ├── FSWeb_Chart_DailyCTF → Performance certificato │ └── FSWeb_Chart_DailyUL → Performance sottostanti │ ├──→ ChartSectionRenderer → PDF Sezione 4 (SkiaSharp) │ └── SkiaChartRenderer → Genera PNG in memoria │ └──→ PdfMergerService → Unisce i PDF → byte[] │ ▼ Response PDF inline ``` --- ## 2. Struttura del progetto ``` SmartReports/ ← Solution └── CertReports.Syncfusion/ ← Progetto ├── Controllers/ │ ├── ReportController.cs ← API report PDF completo │ └── ChartController.cs ← API chart standalone (PNG/PDF) │ ├── Helpers/ │ ├── CryptoHelper.cs ← Decodifica ISIN (ENCRYPTBYPASSPHRASE) │ ├── GlobalExceptionMiddleware.cs ← Error handling centralizzato │ ├── HealthChecks.cs ← Health check DB + Chart service │ └── PdfTheme.cs ← Colori, font, stili, bordi (tema centralizzato) │ ├── Models/ │ ├── CertificateModels.cs ← Modelli report (anagrafica, eventi, scenario) │ └── ChartModels.cs ← Modelli grafico (serie, punti, barriere) │ ├── Services/ │ ├── Interfaces/ │ │ └── IServices.cs ← Contratti DI per tutti i servizi │ └── Implementations/ │ ├── CertificateDataService.cs ← Accesso DB: anagrafica, eventi, scenario │ ├── ChartDataService.cs ← Accesso DB: dati grafico (3 SP) │ ├── AnagraficaSectionRenderer.cs ← Sezione 1: anagrafica + sottostanti │ ├── EventiSectionRenderer.cs ← Sezione 2: tabella eventi (multi-pagina) │ ├── ScenarioSectionRenderer.cs ← Sezione 3: matrice scenario con gradiente │ ├── ChartSectionRenderer.cs ← Sezione 4: inserisce chart PNG nel PDF │ ├── SkiaChartRenderer.cs ← Genera il grafico con SkiaSharp │ ├── PdfMergerService.cs ← Merge PDF con Syncfusion │ ├── PdfCacheService.cs ← Cache in memoria (IMemoryCache) │ └── ReportOrchestrator.cs ← Coordinatore principale │ ├── Program.cs ← Entry point, DI, middleware ├── GlobalUsings.cs ← Using globali ├── CertReports.Syncfusion.csproj ← Progetto + pacchetti NuGet ├── appsettings.json ← Configurazione produzione ├── appsettings.Development.json ← Override per sviluppo ├── Dockerfile ← Build containerizzata ├── docker-compose.yml ← Compose con SQL Server └── .gitignore ``` --- ## 3. Mapping vecchio → nuovo | Vecchio (DevExpress / WebForms) | Nuovo (Syncfusion / ASP.NET Core 8) | |----------------------------------------------|-------------------------------------------| | `ReportFSSiteCrypt.aspx` (Page_Load) | `ReportController.cs` (API REST) | | `XtraReport` + file `.repx` | Classi `IPdfSectionRenderer` (codice C#) | | `report.ExportToPdf()` per ogni sezione | Ogni renderer restituisce `PdfDocument` | | `PdfSharp.PdfReader.Open` + `CopyPages` | `PdfMergerService` (Syncfusion) | | `CommonClass.DecryptCombined()` | `CryptoHelper.DecryptIsin()` | | `CommonClass.execSP_Scalar()` | `CertificateDataService` (async) | | `ChartFSWeb.aspx` (DevExpress WebChart) | `SkiaChartRenderer` + `ChartController` | | `CallWebApi()` con `WebRequest` | Generazione interna (nessuna chiamata HTTP)| | File temporanei su disco + `File.Delete` | Tutto in memoria (`MemoryStream`) | | `Response.BinaryWrite()` | `return File(bytes, "application/pdf")` | | `System.Data.SqlClient` | `Microsoft.Data.SqlClient` v5 | --- ## 4. Stored procedures utilizzate Tutte le SP sono nel database `FirstSolutionDB`. ### Report (sezioni 1-3) | SP | Parametro | Sezione | Descrizione | |----|-----------|---------|-------------| | `rpt_Master_CFT_ISIN` | `@ISIN varchar(12)` | 1 — Anagrafica | Dati certificato (1 record). Campi pre-formattati con `FORMAT()`. Join su Issuer, Category, BasketType, BarrierType + numerose `OUTER APPLY` per valori calcolati. | | `rpt_Details_UL_ISIN` | `@ISIN varchar(12)` | 1 — Sottostanti | N record sottostanti con strike, last price, barriere, buffer, dividendi. Campi formattati con `FORMAT(...,'N3','it-IT')`. | | `rpt_Events_CFT_ISIN` | `@ISIN varchar(50)` | 2 — Eventi | Lista eventi ordinata per data. Cedole, trigger, autocall, importi pagati. Campi formattati. Colonne Trigger Capitale e Valore Capitale escluse dal report. | | `rpt_AnalisiScenario_ISIN` | `@ISIN varchar(50)` | 3 — Scenario | 7 righe UNION con label in `Descrizione` e 13 colonne di valori (`col1..col13`). Variazioni: -90%, -80%, -70%, -60%, -50%, -40%, -30%, -20%, -10%, 0%, +10%, +20%, +30%. Tutte stringhe pre-formattate. | | `rpt_FindIsinbyAliasID` | `@AliasID` | Risoluzione | Restituisce l'ISIN dato un alias ID. | ### Grafico (sezione 4) | SP | Parametri | Descrizione | |----|-----------|-------------| | `FSWeb_Chart_UL` | `@isin varchar(15)` | Info sottostanti per il grafico: IDCertificates, IDUnderlyings, StartDate, Strike, BarrieraCoupon, BarrieraCapitale, Nome. Restituisce dati solo se ci sono almeno 30 prezzi EOD. | | `FSWeb_Chart_DailyCTF` | `@ISIN varchar(50)` | Performance giornaliera del certificato: `Px_date`, `Performance` (= PX_LAST_EOD / Nominal_amount × 100). | | `FSWeb_Chart_DailyUL` | `@IDCertificates, @IDUnderlyings, @StartDate, @Strike` | Performance giornaliera di ogni sottostante: `Px_date`, `Performance` (= Px_close / Strike × 100). Usa Px_closeadj se AdjustedPrices=1. Filtra per date presenti nel certificato. | ### Nota sui dati formattati Le SP restituiscono la maggior parte dei valori **già formattati come stringhe** (es. `FORMAT(value,'P2','it-IT')`, `FORMAT(value,'F2','it-IT')`). I modelli C# usano `string` per questi campi per evitare parsing e riformattazione non necessari. Solo `NominalValue`, `PrezzoEmissione`, `CpnPagati`, `CpnDaPagare`, `CpnInMemoria` sono `decimal?` perché servono come valori numerici nel rendering. --- ## 5. Endpoint API ### Generazione report PDF completo (inline nel browser) ``` GET /api/report?p={isin_cifrato} GET /api/report?alias={alias_id} GET /api/report/by-isin/{isin} ``` ### Download report come allegato ``` GET /api/report/download?p={isin_cifrato} GET /api/report/download?alias={alias_id} ``` ### Grafico standalone (richiamabile da altri progetti) ``` GET /api/chart/{isin} → PNG inline GET /api/chart/{isin}?format=png → PNG inline GET /api/chart/{isin}?format=pdf → PDF inline GET /api/chart/{isin}?width=1200&height=800 → Dimensioni custom ``` Parametri opzionali: `width` (400-2000, default 1100), `height` (300-1500, default 700), `format` (png/pdf, default png). ### Health check ``` GET /health ``` --- ## 6. Configurazione ### appsettings.json ```json { "ConnectionStrings": { "CertDb": "Data Source=INDIRIZZO_SERVER;Initial Catalog=FirstSolutionDB;Persist Security Info=True;User ID=sa;Password=***;TrustServerCertificate=True;Encrypt=False;" }, "Syncfusion": { "LicenseKey": "CHIAVE_COMMUNITY_SYNCFUSION" }, "CryptoSettings": { "Passphrase": "ddCE3hM9BNJXgwtj" }, "ReportSettings": { "CacheMinutes": 5 } } ``` La sezione `ChartService` non è più necessaria: il grafico viene generato internamente. ### Note sulla connection string - Usare `Data Source` (non `Server`) e `Initial Catalog` (non `Database`) per compatibilità con `Microsoft.Data.SqlClient` v5 - Aggiungere `Encrypt=False;` se il SQL Server non ha SSL configurato - Se la connessione non funziona via TCP, aggiungere il prefisso `tcp:` all'indirizzo (es. `Data Source=tcp:26.69.45.60;`) ### appsettings.Development.json Non deve contenere `ConnectionStrings` — altrimenti sovrascrive i valori di `appsettings.json`. Contiene solo override per logging e cache in sviluppo. --- ## 7. Pacchetti NuGet | Pacchetto | Versione | Scopo | |-----------|----------|-------| | `Syncfusion.Pdf.Net.Core` | 33.x | Generazione e merge PDF | | `Microsoft.Data.SqlClient` | 5.x | Accesso SQL Server | | `SkiaSharp` | 2.x | Generazione grafico server-side | | `Serilog.AspNetCore` | 8.x | Logging strutturato | | `Serilog.Sinks.File` | 5.x | Log su file con rotazione giornaliera | ### Note - `Syncfusion.Drawing.Net.Core` **non esiste più** nella v33: è inglobato in `Syncfusion.Pdf.Net.Core` - Per i colori usare `Color.FromArgb(255, r, g, b)` invece di `new Color(r, g, b)` (cambiamento v33) - `PdfStandardFont` **non è IDisposable** in Syncfusion v33: non usare `using` nella dichiarazione - SkiaSharp v2.x: usare `SKFont` per dimensione/typeface e `canvas.DrawText(text, x, y, SKTextAlign, font, paint)` — le API su `SKPaint` sono obsolete --- ## 8. Architettura e pattern ### Dependency Injection Tutti i servizi sono registrati in `Program.cs`: - `ICertificateDataService` → `CertificateDataService` (Scoped) - `IChartDataService` → `ChartDataService` (Scoped) - `IPdfSectionRenderer` → registrati multipli: `Anagrafica`, `Eventi`, `Scenario` (Scoped) - `IChartSectionRenderer` → `ChartSectionRenderer` (Scoped) - `IPdfMergerService` → `PdfMergerService` (Scoped) - `IReportOrchestrator` → `ReportOrchestrator` (Scoped) - `IPdfCacheService` → `PdfCacheService` (Singleton) - `CryptoHelper` (Singleton) ### Aggiungere una nuova sezione al report 1. Creare una classe che implementa `IPdfSectionRenderer` 2. Assegnare `SectionName` e `Order` (determina la posizione nel PDF) 3. Implementare `Render(CertificateReportData data)` che restituisce un `PdfDocument` 4. Registrarla in `Program.cs`: `builder.Services.AddScoped();` L'orchestratore la includerà automaticamente nel report, ordinata per `Order`. ### Tema e stili `PdfTheme.cs` centralizza: - **Colori**: `HeaderBackground`, `AlternateRow`, `PositiveValue`, `NegativeValue`, ecc. - **Font**: `Regular` (8pt), `Bold`, `Small` (6.5pt), `Title` (14pt), `Header` (7pt bold) - **Layout**: `PageMargin` (40pt), `RowHeight` (18pt), `CellPadding` (4pt) - **Brushes e Pens**: pronti all'uso, derivati dai colori - **Bordi**: `ApplyThinBorders(grid, thickness)` per linee tabella sottili e uniformi Modificare un valore in `PdfTheme` aggiorna tutti i renderer automaticamente. ### Bordi tabelle Tutte le tabelle PdfGrid usano `PdfTheme.ApplyThinBorders(grid)` prima del `Draw()` per ottenere linee sottili e uniformi (0.25pt di default). Il metodo itera su headers e righe applicando `PdfBorders` con `PdfPen` del colore e spessore definiti nel tema. ### Cache I PDF generati vengono memorizzati in `IMemoryCache` per `CacheMinutes` (default 5 minuti, configurabile). Evita rigenerazioni per ricaricamenti di pagina. La cache ha un limite di 200 MB totali. ### Gestione errori `GlobalExceptionMiddleware` cattura tutte le eccezioni non gestite e restituisce risposte JSON coerenti con status code appropriati (400, 404, 408, 500). --- ## 9. Sezione grafico (Sezione 4) Il grafico viene generato **interamente in memoria** con SkiaSharp, eliminando la dipendenza dall'endpoint esterno `ChartFSWeb.aspx`. ### Architettura ``` ChartSectionRenderer │ ├──→ ChartDataService ← Recupera dati da DB (3 SP) │ └──→ SkiaChartRenderer ← Genera PNG con SkiaSharp │ └──→ PNG in memoria → inserito nel PDF come PdfBitmap ``` ### Cosa disegna il grafico - **Serie certificato**: linea nera, spessore 2.5px (performance % nel tempo) - **Serie sottostanti**: N linee colorate, spessore 1.5px (ciascuna con colore diverso dal palette ciclico) - **Barriera Capitale**: linea orizzontale rossa - **Barriera Coupon**: linea orizzontale viola (solo se diversa da Barriera Capitale) - **Strike (100%)**: linea orizzontale verde - **Asse Y**: percentuale con auto-scaling e margine 10% - **Asse X**: date con step automatico (trimestrale/semestrale/annuale) - **Legenda**: box in alto a destra con tutte le serie e le linee costanti - **Griglia**: linee orizzontali e verticali grigio chiaro ### API chart standalone Il `ChartController` espone il grafico come API indipendente, richiamabile da qualsiasi altro progetto senza passare per il report completo. --- ## 10. Sezione scenario (Sezione 3) ### Stile visivo La sezione scenario replica lo stile del vecchio report DevExpress: - **Header con gradiente colore**: da rosso scuro (-90%) attraverso arancio e giallo fino a verde (+30%) - **Testo header bianco** su sfondo colorato - **Titolo "Analisi Scenario"** centrato, grande, colore blu - **Righe alternate** con sfondo grigio chiaro - **Valori negativi** in rosso scuro - **13 colonne di variazione**: -90%, -80%, -70%, -60%, -50%, -40%, -30%, -20%, -10%, 0%, +10%, +20%, +30% ### Logica di inclusione La sezione scenario viene inclusa nel report se `rpt_AnalisiScenario_ISIN` restituisce almeno una riga. La condizione è semplicemente `scenario.Rows.Count > 0` (non controlla i valori, perché la SP restituisce anche `"--"` per dati non calcolabili). --- ## 11. Deploy ### Locale (Visual Studio) ``` F5 → https://localhost:{porta}/api/report/by-isin/{ISIN} F5 → https://localhost:{porta}/api/chart/{ISIN} ``` ### Docker ```bash docker-compose up --build # → http://localhost:5080/api/report/by-isin/{ISIN} # → http://localhost:5080/api/chart/{ISIN} ``` Il `Dockerfile` include l'installazione dei font necessari per il rendering PDF e SkiaSharp (`libfontconfig1`, `fonts-dejavu-core`) e gira come utente non-root per sicurezza. --- ## 12. Logging Serilog scrive su: - **Console**: tutti i log in tempo reale - **File**: `Logs/certreports-{data}.log` con rotazione giornaliera Livelli configurabili in `appsettings.json`. In Development il livello di default è `Debug`, in Production è `Information`. Ogni step del flusso è loggato: ``` [INF] Richiesta report per ISIN CH1277653163 [DBG] Cache MISS per ISIN CH1277653163 [INF] Inizio generazione report per ISIN CH1277653163 [INF] Anagrafica caricata per CH1277653163: Leonteq, 3 sottostanti [INF] Eventi caricati per CH1277653163: 36 eventi [INF] Scenario caricato per CH1277653163: 7 righe [INF] Sezione 'Anagrafica' generata per CH1277653163 [INF] Sezione 'Eventi' generata per CH1277653163 [INF] Sezione 'Scenario' generata per CH1277653163 [INF] Dati grafico caricati per CH1277653163: 4 serie, certificato 650 punti [INF] Grafico generato per CH1277653163: 82450 bytes PNG, 4 serie [INF] Sezione Grafico aggiunta per CH1277653163 [INF] Report generato per CH1277653163: 125000 bytes, 4 sezioni [DBG] Cache SET per CH1277653163 (125000 bytes, TTL 5min) ``` --- ## 13. Problemi noti e soluzioni | Problema | Causa | Soluzione | |----------|-------|-----------| | `Cannot access a closed Stream` nel merge | Gli stream dei `PdfLoadedDocument` venivano chiusi prima del `Save()` finale | `PdfMergerService` tiene tutti gli stream aperti fino al salvataggio, poi fa cleanup nel `finally` | | `Color does not contain a constructor that takes 3 arguments` | Syncfusion v33 ha rimosso `new Color(r,g,b)` | Usare `Color.FromArgb(255, r, g, b)` | | `Syncfusion.Drawing.Net.Core` non trovato | Pacchetto rimosso nella v33, inglobato in `Syncfusion.Pdf.Net.Core` | Rimuovere dal `.csproj` | | `Unexpected character '{'` in `FormatPercent` | Interpolazione annidata non supportata in C# | Usare `value.Value.ToString($"N{decimals}") + " %"` | | Connessione DB via Named Pipes | `Microsoft.Data.SqlClient` v5 tenta Named Pipes di default | Aggiungere `Encrypt=False;` e/o prefisso `tcp:` nella connection string | | Connection string ignorata | `appsettings.Development.json` sovrascriveva `appsettings.json` | Rimuovere `ConnectionStrings` da `appsettings.Development.json` | | Scenario mai generato | SP sbagliata (`rpt_AnalisiRischio_ISIN` con 11 colonne) e check troppo restrittivo | Usare `rpt_AnalisiScenario_ISIN` (13 colonne, label in `Descrizione`), condizione `Rows.Count > 0` | | Namespace conflict `Syncfusion.Pdf` in `ChartController` | `CertReports.Syncfusion.Pdf` confuso con `Syncfusion.Pdf` | Aggiungere `using Syncfusion.Pdf;` esplicito, non usare riferimenti fully-qualified | | `PdfStandardFont` non è IDisposable | Syncfusion v33 non implementa `IDisposable` su questo tipo | Non usare `using` nella dichiarazione | | SkiaSharp `DrawText` obsoleto | `SKPaint.TextSize/Typeface` deprecati in SkiaSharp 2.x | Usare `SKFont` + `canvas.DrawText(text, x, y, SKTextAlign, font, paint)` | | Bordi tabelle troppo spessi | `PdfGrid` usa bordi di default grossi | `PdfTheme.ApplyThinBorders(grid)` con `PdfPen` a 0.25pt prima di `Draw()` | | `grid.Headers` non iterabile con foreach | Syncfusion v33 non espone `IEnumerable` sugli headers | Usare `for (int r = 0; r < grid.Headers.Count; r++)` con accesso indicizzato | | `grid.Draw()` restituisce void | Syncfusion v33 ha rimosso il valore di ritorno | Stimare altezza con `(grid.Rows.Count + 1) * RowHeight` | --- ## 14. Cronologia modifiche | Data | Modifica | |------|----------| | 18/03/2026 | Creazione progetto iniziale con struttura ASP.NET Core 8 + Syncfusion | | 18/03/2026 | Implementazione completa `CryptoHelper.DecryptCombined` (v1 TripleDES + v2 AES) | | 18/03/2026 | Mapping SP reali: `rpt_Master_CFT_ISIN`, `rpt_Details_UL_ISIN`, `rpt_Events_CFT_ISIN` | | 18/03/2026 | Fix compatibilità Syncfusion v33: `Color.FromArgb`, rimozione `Syncfusion.Drawing.Net.Core` | | 18/03/2026 | Fix `PdfMergerService`: stream mantenuti aperti fino al `Save()` finale | | 18/03/2026 | Fix connection string: `Data Source` + `Encrypt=False` per `Microsoft.Data.SqlClient` v5 | | 18/03/2026 | Fix `appsettings.Development.json` che sovrascriveva la connection string | | 18/03/2026 | Prima generazione PDF riuscita con dati reali | | 18/03/2026 | Porting grafico da DevExpress `ChartFSWeb.aspx` a SkiaSharp interno | | 18/03/2026 | Aggiunta `ChartDataService` (SP: `FSWeb_Chart_UL`, `FSWeb_Chart_DailyCTF`, `FSWeb_Chart_DailyUL`) | | 18/03/2026 | Aggiunta `SkiaChartRenderer` con serie, barriere, legenda, auto-scaling | | 18/03/2026 | Aggiunta `ChartController` per API chart standalone (`/api/chart/{isin}`) | | 18/03/2026 | Fix scenario: SP corretta `rpt_AnalisiScenario_ISIN` con 13 colonne e label `Descrizione` | | 18/03/2026 | Scenario: header con gradiente rosso→verde, valori negativi in rosso | | 18/03/2026 | Rimozione colonne Trigger Capitale e Valore Capitale dalla tabella eventi | | 18/03/2026 | Fix bordi tabelle: `PdfTheme.ApplyThinBorders()` con linee 0.25pt | | 18/03/2026 | Fix SkiaSharp: migrazione a API moderna `SKFont` (rimozione warning obsoleti) |