From c3730d109b2033ab6a9dd2940496ce4d13c4be22 Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 25 May 2026 14:04:25 +0200 Subject: [PATCH] Add in-memory report storage and async report handling Introduced `InMemoryReportStorageWebExtension` to manage reports in memory, enabling dynamic report storage and retrieval. Updated `ReportViewer.razor` to conditionally render the `DxReportViewer` component and initialize reports asynchronously. Configured dependency injection in `Program.cs` to register `InMemoryReportStorageWebExtension` as a singleton and integrated it with `CustomReportProvider`. Registered `EnvelopeGenerator.ReceiverUI.PredefinedReports.Report` as a trusted class for deserialization. Enhanced `CustomReportProvider` to fetch reports from memory or generate them dynamically using `ReportsFactory`. Added necessary `using` directives and ensured proper report lifecycle management. --- .../Pages/ReportViewer.razor | 16 +++- EnvelopeGenerator.ReceiverUI/Program.cs | 5 ++ .../Services/CustomReportProvider.cs | 9 ++ .../InMemoryReportStorageWebExtension.cs | 83 +++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 EnvelopeGenerator.ReceiverUI/Services/InMemoryReportStorageWebExtension.cs diff --git a/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor index 37456125..dd2f9d6d 100644 --- a/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor +++ b/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor @@ -1,12 +1,24 @@ @page "/reportviewer/" @using DevExpress.XtraReports.UI; +@using EnvelopeGenerator.ReceiverUI.Services; +@inject InMemoryReportStorageWebExtension ReportStorage - +@if(Report is not null) { + +} @code { DxReportViewer reportViewer; - XtraReport Report = new PredefinedReports.Report(); + XtraReport? Report; + + protected override async Task OnInitializedAsync() { + Report = ReportStorage.TryGetReport("LargeDatasetReport", out var savedReport) + ? savedReport + : PredefinedReports.ReportsFactory.GetReport("LargeDatasetReport"); + + await Task.CompletedTask; + } } \ No newline at end of file diff --git a/EnvelopeGenerator.ReceiverUI/Program.cs b/EnvelopeGenerator.ReceiverUI/Program.cs index 6d512004..778cb6ba 100644 --- a/EnvelopeGenerator.ReceiverUI/Program.cs +++ b/EnvelopeGenerator.ReceiverUI/Program.cs @@ -5,6 +5,7 @@ using DevExpress.DataAccess.Web; using EnvelopeGenerator.ReceiverUI.Services; using DevExpress.XtraReports.Services; using DevExpress.Blazor.Reporting; +using DevExpress.XtraReports.Web.Extensions; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -20,6 +21,10 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); DevExpress.Utils.DeserializationSettings.RegisterTrustedClass(typeof(EnvelopeGenerator.ReceiverUI.Data.DataItemList)); +DevExpress.Utils.DeserializationSettings.RegisterTrustedClass(typeof(EnvelopeGenerator.ReceiverUI.PredefinedReports.Report)); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddScoped(); +ReportStorageWebExtension.RegisterExtensionGlobal(new InMemoryReportStorageWebExtension()); await builder.Build().RunAsync(); \ No newline at end of file diff --git a/EnvelopeGenerator.ReceiverUI/Services/CustomReportProvider.cs b/EnvelopeGenerator.ReceiverUI/Services/CustomReportProvider.cs index fbe7a8f6..535d7ff1 100644 --- a/EnvelopeGenerator.ReceiverUI/Services/CustomReportProvider.cs +++ b/EnvelopeGenerator.ReceiverUI/Services/CustomReportProvider.cs @@ -5,7 +5,16 @@ using EnvelopeGenerator.ReceiverUI.PredefinedReports; namespace EnvelopeGenerator.ReceiverUI.Services { public class CustomReportProvider : IReportProviderAsync { + private readonly InMemoryReportStorageWebExtension reportStorage; + + public CustomReportProvider(InMemoryReportStorageWebExtension reportStorage) { + this.reportStorage = reportStorage; + } + public Task GetReportAsync(string id, ReportProviderContext context) { + if(reportStorage.TryGetReport(id, out var savedReport)) + return Task.FromResult(savedReport); + return Task.FromResult(ReportsFactory.GetReport(id)); } } diff --git a/EnvelopeGenerator.ReceiverUI/Services/InMemoryReportStorageWebExtension.cs b/EnvelopeGenerator.ReceiverUI/Services/InMemoryReportStorageWebExtension.cs new file mode 100644 index 00000000..96b40bb8 --- /dev/null +++ b/EnvelopeGenerator.ReceiverUI/Services/InMemoryReportStorageWebExtension.cs @@ -0,0 +1,83 @@ +using DevExpress.XtraReports.UI; +using DevExpress.XtraReports.Web.Extensions; +using EnvelopeGenerator.ReceiverUI.PredefinedReports; + +namespace EnvelopeGenerator.ReceiverUI.Services; + +public class InMemoryReportStorageWebExtension : ReportStorageWebExtension +{ + private const string DefaultReportName = "LargeDatasetReport"; + private static readonly Dictionary Reports = new(StringComparer.OrdinalIgnoreCase); + + public override bool CanSetData(string url) => IsValidUrl(url); + + public override byte[] GetData(string url) + { + url = NormalizeUrl(url); + + if (Reports.TryGetValue(url, out var reportLayout)) + return reportLayout; + + if (ReportsFactory.Reports.TryGetValue(url, out var reportFactory)) + return SaveReport(reportFactory()); + + throw new DevExpress.XtraReports.Web.ClientControls.FaultException($"Report '{url}' was not found."); + } + + public override Dictionary GetUrls() + { + var urls = ReportsFactory.Reports.Keys + .Concat(Reports.Keys) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToDictionary(name => name, name => name, StringComparer.OrdinalIgnoreCase); + + return urls; + } + + public override bool IsValidUrl(string url) + { + return !string.IsNullOrWhiteSpace(url) + && url.IndexOfAny(Path.GetInvalidFileNameChars()) < 0; + } + + public override void SetData(XtraReport report, string url) + { + url = NormalizeUrl(url); + Reports[url] = SaveReport(report); + } + + public override string SetNewData(XtraReport report, string defaultUrl) + { + var url = NormalizeUrl(defaultUrl); + Reports[url] = SaveReport(report); + return url; + } + + public bool TryGetReport(string url, out XtraReport report) + { + url = NormalizeUrl(url); + + if (!Reports.ContainsKey(url)) + { + report = null!; + return false; + } + + using var stream = new MemoryStream(Reports[url]); + report = XtraReport.FromXmlStream(stream, true); + report.Name = url; + return true; + } + + private static string NormalizeUrl(string url) + { + return string.IsNullOrWhiteSpace(url) ? DefaultReportName : url; + } + + private static byte[] SaveReport(XtraReport report) + { + using var stream = new MemoryStream(); + report.SaveLayoutToXml(stream); + return stream.ToArray(); + } +}