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..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 @@ -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; @@ -222,13 +229,13 @@ 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(); - cts.Dispose(); + await cts.CancelAsync(); } + cts.Dispose(); cts = new(); } @@ -244,7 +251,6 @@ protected virtual async ValueTask DisposeAsync(bool disposing) if (disposing) { await PrerenderStateService.DisposeAsync(); - cts.Cancel(); cts.Dispose(); } } 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; 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[])