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)
{