21 KiB
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
{
"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(nonServer) eInitial Catalog(nonDatabase) per compatibilità conMicrosoft.Data.SqlClientv5 - 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.Corenon esiste più nella v33: è inglobato inSyncfusion.Pdf.Net.Core- Per i colori usare
Color.FromArgb(255, r, g, b)invece dinew Color(r, g, b)(cambiamento v33) PdfStandardFontnon è IDisposable in Syncfusion v33: non usareusingnella dichiarazione- SkiaSharp v2.x: usare
SKFontper dimensione/typeface ecanvas.DrawText(text, x, y, SKTextAlign, font, paint)— le API suSKPaintsono 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
- Creare una classe che implementa
IPdfSectionRenderer - Assegnare
SectionNameeOrder(determina la posizione nel PDF) - Implementare
Render(CertificateReportData data)che restituisce unPdfDocument - Registrarla in
Program.cs:builder.Services.AddScoped<IPdfSectionRenderer, NuovaSezione>();
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
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}.logcon 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) |