From bd4fd0d4b5bc989268cf1db47018b57994922be1 Mon Sep 17 00:00:00 2001 From: Syed Mustafa Ahmed Naqvi Date: Sat, 23 May 2026 11:41:22 +0500 Subject: [PATCH] Handle cancelled history reads gracefully Handles cancellation separately in analytics and scan history reads, and centralizes scan history DTO mapping to reduce duplicated code. --- Program.cs | 6 +- .../PublishProfiles/FolderProfile.pubxml | 20 ++++ Services/MovementAnalyticsService.cs | 5 + Services/SqliteScanHistoryStore.cs | 95 +++++++++++-------- dotnet-tools.json | 13 +++ 5 files changed, 98 insertions(+), 41 deletions(-) create mode 100644 Properties/PublishProfiles/FolderProfile.pubxml create mode 100644 dotnet-tools.json diff --git a/Program.cs b/Program.cs index 6fefd55..b989a20 100644 --- a/Program.cs +++ b/Program.cs @@ -27,6 +27,7 @@ builder.Services .AddOptions() .Bind(builder.Configuration.GetSection(DesktopHostOptions.SectionName)); +builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddControllersWithViews(); @@ -76,7 +77,10 @@ if (!app.Environment.IsDevelopment()) app.UseHsts(); } -app.UseHttpsRedirection(); +if (KestrelConfiguration.HasHttpsEndpoint(app.Configuration)) +{ + app.UseHttpsRedirection(); +} app.UseRouting(); app.UseAuthorization(); app.UseStaticFiles(new StaticFileOptions diff --git a/Properties/PublishProfiles/FolderProfile.pubxml b/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..66f9584 --- /dev/null +++ b/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,20 @@ + + + + + false + false + true + Release + Any CPU + FileSystem + \\FileServer\Edata\Deployments-by-DotNet-Team\Deployment-By-Mustafa\AVSSHIPPINGMARK + FileSystem + <_TargetId>Folder + + net10.0 + win-x86 + fcd78a4e-cf57-4247-9698-bb63ba85b062 + true + + \ No newline at end of file diff --git a/Services/MovementAnalyticsService.cs b/Services/MovementAnalyticsService.cs index 9eb0799..f52102d 100644 --- a/Services/MovementAnalyticsService.cs +++ b/Services/MovementAnalyticsService.cs @@ -53,6 +53,11 @@ public sealed class MovementAnalyticsService( .Where(x => x.ProcessedAtUtc >= rangeStartUtc && x.ProcessedAtUtc <= rangeEndUtc) .ToListAsync(cancellationToken); } + catch (OperationCanceledException exception) when (cancellationToken.IsCancellationRequested) + { + _logger.LogDebug(exception, "Movement analytics read was cancelled."); + throw; + } catch (Exception exception) { _logger.LogError(exception, "Failed to load movement analytics."); diff --git a/Services/SqliteScanHistoryStore.cs b/Services/SqliteScanHistoryStore.cs index 450c258..d73afff 100644 --- a/Services/SqliteScanHistoryStore.cs +++ b/Services/SqliteScanHistoryStore.cs @@ -70,16 +70,11 @@ public sealed class SqliteScanHistoryStore( .Take(5) .ToArrayAsync(cancellationToken); - return entries - .Select(x => new ScannedRecordDto - { - ModelNumber = x.ModelNumber, - UniqueNumber = x.UniqueNumber, - ExistsInSystem = x.ExistsInSystem, - ShipmentMarked = x.ShipmentMarked, - ProcessedAtUtc = DateTime.SpecifyKind(x.ProcessedAtUtc, DateTimeKind.Utc) - }) - .ToArray(); + return MapSuccessful(entries); + } + catch (Exception exception) when (TryHandleReadCancellation(exception, cancellationToken, "Successful scan history (last five)")) + { + return Array.Empty(); } catch (Exception exception) { @@ -98,16 +93,11 @@ public sealed class SqliteScanHistoryStore( .Take(5) .ToArrayAsync(cancellationToken); - return entries - .Select(x => new RejectedScanRecordDto - { - RawInputValue = x.RawInputValue, - ModelNumber = x.ModelNumber, - UniqueNumber = x.UniqueNumber, - RejectionReason = x.RejectionReason, - ProcessedAtUtc = DateTime.SpecifyKind(x.ProcessedAtUtc, DateTimeKind.Utc) - }) - .ToArray(); + return MapRejected(entries); + } + catch (Exception exception) when (TryHandleReadCancellation(exception, cancellationToken, "Rejected scan history (last five)")) + { + return Array.Empty(); } catch (Exception exception) { @@ -126,16 +116,11 @@ public sealed class SqliteScanHistoryStore( .OrderByDescending(x => x.ProcessedAtUtc) .ToArrayAsync(cancellationToken); - return entries - .Select(x => new ScannedRecordDto - { - ModelNumber = x.ModelNumber, - UniqueNumber = x.UniqueNumber, - ExistsInSystem = x.ExistsInSystem, - ShipmentMarked = x.ShipmentMarked, - ProcessedAtUtc = DateTime.SpecifyKind(x.ProcessedAtUtc, DateTimeKind.Utc) - }) - .ToArray(); + return MapSuccessful(entries); + } + catch (Exception exception) when (TryHandleReadCancellation(exception, cancellationToken, "Successful scan history (all)")) + { + return Array.Empty(); } catch (Exception exception) { @@ -153,16 +138,11 @@ public sealed class SqliteScanHistoryStore( .OrderByDescending(x => x.ProcessedAtUtc) .ToArrayAsync(cancellationToken); - return entries - .Select(x => new RejectedScanRecordDto - { - RawInputValue = x.RawInputValue, - ModelNumber = x.ModelNumber, - UniqueNumber = x.UniqueNumber, - RejectionReason = x.RejectionReason, - ProcessedAtUtc = DateTime.SpecifyKind(x.ProcessedAtUtc, DateTimeKind.Utc) - }) - .ToArray(); + return MapRejected(entries); + } + catch (Exception exception) when (TryHandleReadCancellation(exception, cancellationToken, "Rejected scan history (all)")) + { + return Array.Empty(); } catch (Exception exception) { @@ -171,6 +151,41 @@ public sealed class SqliteScanHistoryStore( } } + private bool TryHandleReadCancellation(Exception exception, CancellationToken cancellationToken, string operation) + { + if (exception is not OperationCanceledException || !cancellationToken.IsCancellationRequested) + { + return false; + } + + _logger.LogDebug(exception, "{Operation} read was cancelled.", operation); + return true; + } + + private static ScannedRecordDto[] MapSuccessful(IEnumerable entries) => + entries + .Select(x => new ScannedRecordDto + { + ModelNumber = x.ModelNumber, + UniqueNumber = x.UniqueNumber, + ExistsInSystem = x.ExistsInSystem, + ShipmentMarked = x.ShipmentMarked, + ProcessedAtUtc = DateTime.SpecifyKind(x.ProcessedAtUtc, DateTimeKind.Utc) + }) + .ToArray(); + + private static RejectedScanRecordDto[] MapRejected(IEnumerable entries) => + entries + .Select(x => new RejectedScanRecordDto + { + RawInputValue = x.RawInputValue, + ModelNumber = x.ModelNumber, + UniqueNumber = x.UniqueNumber, + RejectionReason = x.RejectionReason, + ProcessedAtUtc = DateTime.SpecifyKind(x.ProcessedAtUtc, DateTimeKind.Utc) + }) + .ToArray(); + private async Task TrimSuccessfulAsync(CancellationToken cancellationToken) { var max = _options.MaxRecordsToKeep; diff --git a/dotnet-tools.json b/dotnet-tools.json new file mode 100644 index 0000000..7dcefc3 --- /dev/null +++ b/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "10.0.8", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file