From f19251ac1a406779e5620a28aff785021303153f Mon Sep 17 00:00:00 2001 From: OlgunR Date: Mon, 8 Jun 2026 08:38:44 +0200 Subject: [PATCH] Add DevExpress Reporting and Custom Report Storage Updated project dependencies to include DevExpress Reporting and PDF Drawing libraries. Registered DevExpress services and middleware in `Program.cs`. Added `CustomReportStorageWebExtension` to handle read-only invoice reports, including database and file system integration. Enhanced logging for better traceability and error handling. --- .../DXApp.TemplateKitProject.csproj | 4 +- .../CustomReportStorageWebExtension.cs | 119 ++++++++++++++++++ DXApp.TemplateKitProject/Program.cs | 16 ++- 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 DXApp.TemplateKitProject/DevExpressExtensions/CustomReportStorageWebExtension.cs diff --git a/DXApp.TemplateKitProject/DXApp.TemplateKitProject.csproj b/DXApp.TemplateKitProject/DXApp.TemplateKitProject.csproj index 47a1c74..b0e4ad9 100644 --- a/DXApp.TemplateKitProject/DXApp.TemplateKitProject.csproj +++ b/DXApp.TemplateKitProject/DXApp.TemplateKitProject.csproj @@ -16,8 +16,10 @@ + - + + diff --git a/DXApp.TemplateKitProject/DevExpressExtensions/CustomReportStorageWebExtension.cs b/DXApp.TemplateKitProject/DevExpressExtensions/CustomReportStorageWebExtension.cs new file mode 100644 index 0000000..78bd5d1 --- /dev/null +++ b/DXApp.TemplateKitProject/DevExpressExtensions/CustomReportStorageWebExtension.cs @@ -0,0 +1,119 @@ +using DevExpress.XtraReports.UI; +using DevExpress.XtraReports.Web.Extensions; +using DXApp.TemplateKitProject.Data; +using Microsoft.EntityFrameworkCore; + +namespace DXApp.TemplateKitProject.DevExpressExtensions; + +public class CustomReportStorageWebExtension : ReportStorageWebExtension +{ + private readonly AppDbContext _db; + private readonly ILogger _logger; + + public CustomReportStorageWebExtension( + AppDbContext db, + ILogger logger) + { + _db = db; + _logger = logger; + } + + public override bool CanSetData(string url) + { + // Wir erlauben KEIN Speichern von Änderungen über den Viewer + // PDFs sind bei uns Read-Only im Viewer + return false; + } + + public override bool IsValidUrl(string url) + { + // Prüfen ob URL dem Format entspricht: "invoice-{id}" + return url.StartsWith("invoice-", StringComparison.OrdinalIgnoreCase); + } + + public override byte[] GetData(string url) + { + try + { + _logger.LogDebug("GetData aufgerufen für URL: {Url}", url); + + // URL-Format: "invoice-123" → ID extrahieren + if (!url.StartsWith("invoice-", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning("Ungültige URL: {Url}", url); + return Array.Empty(); + } + + var idString = url.Substring("invoice-".Length); + if (!int.TryParse(idString, out var invoiceId)) + { + _logger.LogWarning("Ungültige Invoice-ID in URL: {Url}", url); + return Array.Empty(); + } + + // Rechnung aus Datenbank laden + var invoice = _db.ZugferdInvoices + .AsNoTracking() + .FirstOrDefault(i => i.Id == invoiceId); + + if (invoice == null) + { + _logger.LogWarning("Rechnung mit ID {Id} nicht gefunden.", invoiceId); + return Array.Empty(); + } + + // Prüfen ob ResultFilePath existiert + if (string.IsNullOrEmpty(invoice.ResultFilePath)) + { + _logger.LogWarning( + "Rechnung {Id} hat keine Result-PDF (ResultFilePath ist leer).", + invoiceId); + return Array.Empty(); + } + + // Datei vom Dateisystem laden + if (!File.Exists(invoice.ResultFilePath)) + { + _logger.LogError( + "Result-PDF-Datei nicht gefunden: {Path}", + invoice.ResultFilePath); + return Array.Empty(); + } + + var pdfBytes = File.ReadAllBytes(invoice.ResultFilePath); + + _logger.LogInformation( + "Result-PDF geladen: Invoice {Id}, Größe: {Size} Bytes", + invoiceId, + pdfBytes.Length); + + return pdfBytes; + } + catch (Exception ex) + { + _logger.LogError(ex, "Fehler beim Laden der PDF für URL: {Url}", url); + return Array.Empty(); + } + } + + public override Dictionary GetUrls() + { + // Diese Methode wird verwendet um eine Liste aller verfügbaren Reports zu liefern + // Für unseren Use-Case nicht benötigt (wir laden immer spezifisch eine Invoice) + return new Dictionary(); + } + + public override void SetData(XtraReport report, string url) + { + // Nicht implementiert - wir erlauben kein Speichern über den Viewer + throw new NotSupportedException( + "Das Speichern von Reports über den Document Viewer ist nicht unterstützt."); + } + + public override string SetNewData(XtraReport report, string defaultUrl) + { + // Nicht implementiert - wir erstellen keine neuen Reports über den Viewer + throw new NotSupportedException( + "Das Erstellen neuer Reports über den Document Viewer ist nicht unterstützt."); + } +} \ No newline at end of file diff --git a/DXApp.TemplateKitProject/Program.cs b/DXApp.TemplateKitProject/Program.cs index 3e4a6af..14ce1b5 100644 --- a/DXApp.TemplateKitProject/Program.cs +++ b/DXApp.TemplateKitProject/Program.cs @@ -1,11 +1,19 @@ using DXApp.TemplateKitProject.Data; +using DXApp.TemplateKitProject.DevExpressExtensions; using DXApp.TemplateKitProject.Services; using Microsoft.EntityFrameworkCore; +using DevExpress.AspNetCore; +using DevExpress.AspNetCore.Reporting; +using DevExpress.XtraReports.Web.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); +// DevExpress Controls und Reporting Services +builder.Services.AddDevExpressControls(); +builder.Services.AddScoped(); + // Datenbank var connectionString = builder.Configuration.GetConnectionString("EcmContext") ?? throw new InvalidOperationException("Connection string 'EcmContext' not found."); @@ -30,12 +38,18 @@ if (!app.Environment.IsDevelopment()) app.UseHttpsRedirection(); -// MIME-Types für PDF.js registrieren (einmaliger UseStaticFiles-Aufruf) +// MIME-Types für PDF.js UND DevExpress registrieren var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider(); provider.Mappings[".mjs"] = "text/javascript"; provider.Mappings[".ftl"] = "text/plain"; +provider.Mappings[".woff2"] = "font/woff2"; +provider.Mappings[".woff"] = "font/woff"; + app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider }); +// DevExpress Middleware +app.UseDevExpressControls(); + app.UseRouting(); app.UseAuthorization(); app.MapRazorPages();