From 596861783594db550acfe22e242bdd6640000277 Mon Sep 17 00:00:00 2001 From: ysmoradi Date: Wed, 12 Feb 2025 14:43:55 +0100 Subject: [PATCH 1/4] Refactor Boilerplate sales module's product page response cache purge (#9870) --- .../Components/AppComponentBase.cs | 16 ++++++++++------ .../Components/Pages/ProductPage.razor | 4 +--- .../Components/Pages/ProductPage.razor.cs | 8 ++++++-- .../Components/_Imports.razor | 2 +- .../Controllers/Products/ProductController.cs | 11 ++++++++--- .../Boilerplate.Server.Api/Data/AppDbContext.cs | 2 +- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs index 6eb1b0fac8..2c704007b3 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs @@ -56,7 +56,14 @@ public partial class AppComponentBase : ComponentBase, IAsyncDisposable private CancellationTokenSource cts = new(); - protected CancellationToken CurrentCancellationToken => cts.Token; + protected CancellationToken CurrentCancellationToken + { + get + { + cts.Token.ThrowIfCancellationRequested(); + return cts.Token; + } + } protected bool InPrerenderSession => AppPlatform.IsBlazorHybrid is false && JSRuntime.IsInitialized() is false; @@ -224,11 +231,8 @@ public virtual Func WrapHandled(Func func, /// protected void Abort() { - if (cts.IsCancellationRequested is false) - { - cts.Cancel(); - cts.Dispose(); - } + cts.Cancel(); + cts.Dispose(); cts = new(); } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor index 2467adfa77..a115d70ca9 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor @@ -1,6 +1,4 @@ -@attribute [Route(Urls.ProductPage + "/{Id:guid}")] -@attribute [Route(Urls.ProductPage + "/{Id:guid}/{Name}")] -@attribute [Route("{culture?}" + Urls.ProductPage + "/{Id:guid}")] +@attribute [Route(Urls.ProductPage + "/{Id:guid}/{Name}")] @attribute [Route("{culture?}" + Urls.ProductPage + "/{Id:guid}/{Name}")] @attribute [AppResponseCache(SharedMaxAge = 3600 * 24)] @inherits AppPageBase diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor.cs index a606a726dd..482a7994b3 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/ProductPage.razor.cs @@ -58,7 +58,9 @@ private async Task LoadSimilarProducts() try { - similarProducts = await productViewController.GetSimilar(product.Id, CurrentCancellationToken); + similarProducts = await productViewController + .WithQuery(new ODataQuery { Top = 10 }) + .GetSimilar(product.Id, CurrentCancellationToken); } finally { @@ -73,7 +75,9 @@ private async Task LoadSiblingProducts() try { - siblingProducts = await productViewController.WithQuery(new ODataQuery { Filter = $"{nameof(ProductDto.Id)} ne {product.Id}" }).GetSiblings(product.CategoryId.Value, CurrentCancellationToken); + siblingProducts = await productViewController + .WithQuery(new ODataQuery { Top = 10, Filter = $"{nameof(ProductDto.Id)} ne {product.Id}" }) + .GetSiblings(product.CategoryId.Value, CurrentCancellationToken); } finally { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/_Imports.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/_Imports.razor index d37f1a1065..4f76dfcb61 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/_Imports.razor +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/_Imports.razor @@ -1,6 +1,6 @@ @using System.Reflection -@using System.Runtime.Loader @using System.Globalization +@using System.Runtime.Loader @using Microsoft.JSInterop @using Microsoft.Extensions.Logging @using Microsoft.AspNetCore.Components diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs index 0a30e8478d..80c4110c7a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Controllers/Products/ProductController.cs @@ -78,7 +78,7 @@ public async Task Update(ProductDto dto, CancellationToken cancellat await DbContext.SaveChangesAsync(cancellationToken); - await responseCacheService.PurgeCache("/", $"product/{dto.Id}", $"api/ProductView/Get/{dto.Id}" /*You can also use Url.Action to build urls.*/); + await responseCacheService.PurgeCache("/", $"/product/{dto.Id}/{Uri.EscapeDataString(dto.Name!)}", $"/api/ProductView/Get/{dto.Id}" /*You can also use Url.Action to build urls.*/); //#if (signalR == true) await PublishDashboardDataChanged(cancellationToken); @@ -90,11 +90,16 @@ public async Task Update(ProductDto dto, CancellationToken cancellat [HttpDelete("{id}/{concurrencyStamp}")] public async Task Delete(Guid id, string concurrencyStamp, CancellationToken cancellationToken) { - DbContext.Products.Remove(new() { Id = id, ConcurrencyStamp = Convert.FromHexString(concurrencyStamp) }); + var entityToDelete = await DbContext.Products.FindAsync([id], cancellationToken) + ?? throw new ResourceNotFoundException(Localizer[nameof(AppStrings.ProductCouldNotBeFound)]); + + entityToDelete.ConcurrencyStamp = Convert.FromHexString(concurrencyStamp); + + DbContext.Remove(entityToDelete); await DbContext.SaveChangesAsync(cancellationToken); - await responseCacheService.PurgeCache("/", $"product/{id}", $"api/ProductView/Get/{id}" /*You can also use Url.Action to build urls.*/); + await responseCacheService.PurgeCache("/", $"/product/{entityToDelete.Id}/{Uri.EscapeDataString(entityToDelete.Name!)}", $"/api/ProductView/Get/{entityToDelete.Id}" /*You can also use Url.Action to build urls.*/); //#if (signalR == true) await PublishDashboardDataChanged(cancellationToken); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Data/AppDbContext.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Data/AppDbContext.cs index 9ce1ed1f52..b92dcf19e0 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Data/AppDbContext.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Server/Boilerplate.Server.Api/Data/AppDbContext.cs @@ -91,7 +91,7 @@ private void ReplaceOriginalConcurrencyStamp() //#endif ChangeTracker.DetectChanges(); - foreach (var entityEntry in ChangeTracker.Entries().Where(e => e.State is EntityState.Modified)) + foreach (var entityEntry in ChangeTracker.Entries().Where(e => e.State is EntityState.Modified or EntityState.Deleted)) { if (entityEntry.CurrentValues.TryGetValue("ConcurrencyStamp", out var currentConcurrencyStamp) is false || currentConcurrencyStamp is not byte[]) From e1bce56c50e1ee3c4a66665aba5e7e4ed9944f8a Mon Sep 17 00:00:00 2001 From: ysmoradi Date: Wed, 12 Feb 2025 16:09:38 +0100 Subject: [PATCH 2/4] fix --- .../Components/AppComponentBase.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs index 2c704007b3..e6235e21ae 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs @@ -231,7 +231,10 @@ public virtual Func WrapHandled(Func func, /// protected void Abort() { - cts.Cancel(); + if (cts.IsCancellationRequested is false) + { + cts.Cancel(); + } cts.Dispose(); cts = new(); } @@ -248,7 +251,10 @@ protected virtual async ValueTask DisposeAsync(bool disposing) if (disposing) { await PrerenderStateService.DisposeAsync(); - cts.Cancel(); + if (cts.IsCancellationRequested is false) + { + cts.Cancel(); + } cts.Dispose(); } } From 64854414cba9635cb769b536dc1fe244fe2a7762 Mon Sep 17 00:00:00 2001 From: ysmoradi Date: Wed, 12 Feb 2025 16:11:33 +0100 Subject: [PATCH 3/4] fix --- .../Boilerplate.Client.Core/Components/AppComponentBase.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs index e6235e21ae..7d7d176be2 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs @@ -251,10 +251,6 @@ protected virtual async ValueTask DisposeAsync(bool disposing) if (disposing) { await PrerenderStateService.DisposeAsync(); - if (cts.IsCancellationRequested is false) - { - cts.Cancel(); - } cts.Dispose(); } } From 0e9091e5df233b06f05f55c658752b8d25a40254 Mon Sep 17 00:00:00 2001 From: ysmoradi Date: Wed, 12 Feb 2025 16:14:33 +0100 Subject: [PATCH 4/4] fix --- .../Boilerplate.Client.Core/Components/AppComponentBase.cs | 4 ++-- .../Components/ClientAppCoordinator.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs index 7d7d176be2..b2d417e26e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppComponentBase.cs @@ -229,11 +229,11 @@ public virtual Func WrapHandled(Func func, /// /// Cancells running codes inside current component. /// - protected void Abort() + protected async Task Abort() { if (cts.IsCancellationRequested is false) { - cts.Cancel(); + await cts.CancelAsync(); } cts.Dispose(); cts = new(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs index 2f78be08af..13df60b8ba 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/ClientAppCoordinator.cs @@ -104,7 +104,7 @@ public async Task PropagateUserId(bool firstRun, Task task) var userId = isAuthenticated ? user.GetUserId() : (Guid?)null; if (lastPropagatedUserId == userId) return; - Abort(); // Cancels ongoing user id propagation, because the new authentication state is available. + await Abort(); // Cancels ongoing user id propagation, because the new authentication state is available. lastPropagatedUserId = userId; TelemetryContext.UserId = userId; TelemetryContext.UserSessionId = isAuthenticated ? user.GetSessionId() : null;