diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index 3c55104ebfe..310adccdaad 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -20,78 +20,78 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BootstrapBlazor.Server/Components/Pages/Chats.razor.cs b/src/BootstrapBlazor.Server/Components/Pages/Chats.razor.cs index 70c2ce53e9e..357ce9f7c5c 100644 --- a/src/BootstrapBlazor.Server/Components/Pages/Chats.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Pages/Chats.razor.cs @@ -99,10 +99,7 @@ private async Task GetCompletionsAsync() if (!string.IsNullOrEmpty(chatMessage.Content)) { msg.Content += chatMessage.Content; - // TODO: NET 11 误报警告 -#pragma warning disable BL0012 // Unnecessary StateHasChanged call - StateHasChanged(); -#pragma warning restore BL0012 // Unnecessary StateHasChanged call + await InvokeAsync(StateHasChanged); } } diff --git a/src/BootstrapBlazor.Server/Components/Samples/Blocks.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Blocks.razor.cs index 59ac3d7297b..418aa798058 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Blocks.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Blocks.razor.cs @@ -10,8 +10,11 @@ namespace BootstrapBlazor.Server.Components.Samples; /// /// /// -public partial class Blocks +public partial class Blocks : IAsyncDisposable { + private bool _isDisposed; + private long _authenticationStateVersion; + private bool IsShow { get; set; } = true; private string GetIcon() => IsShow ? "fa-solid fa-eye-slash" : "fa-solid fa-eye"; @@ -45,6 +48,49 @@ public partial class Blocks private string GetUserText() => IsAuth ? Localizer["Logout"] : Localizer["Login"]; + /// + /// + /// + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged; + await UpdateAuthenticationStateAsync(AuthenticationStateProvider.GetAuthenticationStateAsync()); + } + + private void OnAuthenticationStateChanged(Task authenticationStateTask) + => _ = UpdateAuthenticationStateAsync(authenticationStateTask); + + private async Task UpdateAuthenticationStateAsync(Task authenticationStateTask) + { + var version = Interlocked.Increment(ref _authenticationStateVersion); + try + { + await InvokeAsync(async () => + { + if (_isDisposed || version != Interlocked.Read(ref _authenticationStateVersion)) + { + return; + } + + var state = await authenticationStateTask; + if (_isDisposed || version != Interlocked.Read(ref _authenticationStateVersion)) + { + return; + } + + IsAuth = state.User.Identity?.IsAuthenticated ?? false; + UserName = IsAuth ? state.User.Identity?.Name ?? "" : ""; + StateHasChanged(); + }); + } + catch (Exception ex) + { + await DispatchExceptionAsync(ex); + } + } + private async Task ToggleAuthor() { if (AuthenticationStateProvider is MockAuthenticationStateProvider mock) @@ -52,18 +98,11 @@ private async Task ToggleAuthor() if (!IsAuth) { mock.Login(); - // TODO: NET 11 误报警告 -#pragma warning disable BL0013 // AuthenticationStateProvider.GetAuthenticationStateAsync used without subscribing to AuthenticationStateChanged - var state = await mock.GetAuthenticationStateAsync(); -#pragma warning restore BL0013 // AuthenticationStateProvider.GetAuthenticationStateAsync used without subscribing to AuthenticationStateChanged - UserName = state.User.Identity?.Name; - IsAuth = state.User.Identity?.IsAuthenticated ?? false; + await mock.GetAuthenticationStateAsync(); } else { mock.Logout(); - IsAuth = false; - UserName = ""; } } } @@ -73,4 +112,21 @@ private async Task ToggleAuthor() private Task OnQueryCondition2(string name) => Task.FromResult(IsShow2); private Task OnQueryUser(string name) => Task.FromResult(IsAuth); + + private ValueTask DisposeAsync(bool disposing) + { + if (disposing && !_isDisposed) + { + _isDisposed = true; + AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged; + } + + return ValueTask.CompletedTask; + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + await DisposeAsync(true); + GC.SuppressFinalize(this); + } } diff --git a/src/BootstrapBlazor.Server/Components/Samples/InputCurrencies.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/InputCurrencies.razor.cs index 8bff8a81e44..4bc2237faa6 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/InputCurrencies.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/InputCurrencies.razor.cs @@ -16,5 +16,6 @@ public partial class InputCurrencies private decimal _value2 = 2m; private bool _showIsoCurrencySymbol; + private bool _showClearIcon; } diff --git a/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/MockOnlineContributor.cs b/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/MockOnlineContributor.cs index d9d13c4bc6e..594ae8c2d6f 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/MockOnlineContributor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/MockOnlineContributor.cs @@ -7,7 +7,7 @@ namespace BootstrapBlazor.Server.Components.Samples.Tutorials.OnlineSheet; static class MockOnlineContributor { - public static Task Trigger(IDispatchService dispatchService, IStringLocalizer localizer) + public static Task Trigger(IDispatchService dispatchService, string desc, string cellValue) { return dispatchService.DispatchAsync(new DispatchEntry() { @@ -16,14 +16,14 @@ public static Task Trigger(IDispatchService dispatchService, IStrin { Name = "Argo Zhang", Avatar = "/images/Argo-C.png", - Description = localizer["ContributorDescription", "A8"], + Description = desc, Data = new UniverSheetData() { CommandName = "UpdateRange", Data = new { Range = "A8", - Value = localizer["ContributorCellValue", DateTime.Now, "Argo"].Value + Value = cellValue } } } diff --git a/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/OnlineSheet.razor.cs b/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/OnlineSheet.razor.cs index 5c9e4ef2522..1078945d69f 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/OnlineSheet.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/Tutorials/OnlineSheet/OnlineSheet.razor.cs @@ -61,7 +61,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { await Task.Delay(5000); - await MockOnlineContributor.Trigger(DispatchService, Localizer); + await MockOnlineContributor.Trigger(DispatchService, Localizer["ContributorDescription", "A8"], Localizer["ContributorCellValue", DateTime.Now, "Argo"]); } } diff --git a/test/UnitTest/Utils/JSModuleTest.cs b/test/UnitTest/Utils/JSModuleTest.cs index a2d50572d49..74d88e5c26a 100644 --- a/test/UnitTest/Utils/JSModuleTest.cs +++ b/test/UnitTest/Utils/JSModuleTest.cs @@ -40,12 +40,16 @@ public async Task Dispose_Ok() await module.DisposeAsync(); } - [Fact] - public async Task Dispose_Error() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task Dispose_Error(bool asyncDispose) { - var js = new MockErrorJSObjectReference(); + var js = new MockErrorJSObjectReference(asyncDispose); var module = new JSModule(js); - await module.DisposeAsync(); + var exception = await Record.ExceptionAsync(async () => await module.DisposeAsync()); + Assert.Null(exception); + Assert.Equal(1, js.DisposeCallCount); } [Fact] @@ -126,34 +130,24 @@ public async Task JSModule_ToggleMemorial() await module.ToggleMemorialModeAsync(); } - private class MockErrorJSObjectReference : MockJSObjectReference + private class MockErrorJSObjectReference(bool asyncDispose) : MockJSObjectReference { - protected override ValueTask DisposeAsyncCore(bool disposing) - { - throw new InvalidOperationException(); - } + public int DisposeCallCount { get; private set; } - public override ValueTask InvokeAsync(string identifier, object?[]? args) + public override ValueTask DisposeAsync() { + DisposeCallCount++; + if (asyncDispose) + { + return ValueTask.FromException(new InvalidOperationException()); + } throw new InvalidOperationException(); } } private class MockJSObjectReference : IJSObjectReference { - protected virtual ValueTask DisposeAsyncCore(bool disposing) - { - return ValueTask.CompletedTask; - } - - public async ValueTask DisposeAsync() - { - // TODO: NET 11 误报警告 -#pragma warning disable BL0016 // Unguarded JS interop call - await DisposeAsyncCore(true); -#pragma warning restore BL0016 // Unguarded JS interop call - GC.SuppressFinalize(this); - } + public virtual ValueTask DisposeAsync() => ValueTask.CompletedTask; public virtual ValueTask InvokeAsync(string identifier, object?[]? args) {