From 30ef19c7d9651d0fa113dd8ed08809e545141d74 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 21:00:37 +0000 Subject: [PATCH] [release/8.0] [Blazor] Invoke inbound activity handlers on circuit initialization (#57715) Backport of #57557 to release/8.0 # [Blazor] Invoke inbound activity handlers on circuit initialization Fixes an issue where inbound activity handlers don't get invoked on circuit initialization. > [!NOTE] > This bug only affects Blazor Server apps, _not_ Blazor Web apps utilizing server interactivity ## Description Inbound activity handlers were added in .NET 8 to enable: * Monitoring inbound circuit activity * Enabling server-side Blazor services to be [accessed from a different DI scope](https://learn.microsoft.com/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-8.0#access-server-side-blazor-services-from-a-different-di-scope) However, prior to the fix in this PR, this feature didn't apply to the first interactive render after the initial page load. This means that when utilizing this feature to access Blazor services from a different DI scope, the service might only become accessible after subsequent renders, not the initial render. This PR makes the following changes: * Updated `CircuitHost` to invoke inbound activity handlers on circuit initialization * Added an extra test to verify that inbound activity handlers work on the initial page load * Updated existing Blazor Web tests to reuse test logic from the non-web tests * This helps to ensure that the feature works the same way on Blazor Server and Blazor Web Fixes #57481 ## Customer Impact The [initial issue report](https://github.com/dotnet/aspnetcore/issues/57481) was from a customer who was impacted experiencing this problem in their app. The problem does not inherently cause an app to stop working, but if the application code has made the (rightful) assumption that the service accessor is initialized, then session may crash. The workaround is to upgrade the app to use the "Blazor Web App" pattern, although this can be a fairly large change. ## Regression? - [ ] Yes - [X] No The problem has existed since the introduction of the feature in .NET 8. ## Risk - [ ] High - [ ] Medium - [X] Low The change is straightforward, and new tests have been added to ensure that it addresses the issue. Existing tests verify that a new regression is not introduced. ## Verification - [X] Manual (required) - [X] Automated ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A --- .../Server/src/Circuits/CircuitHost.cs | 4 +-- .../CircuitContextTest.cs | 33 ++++++++++++------- .../ServerRenderingTests/InteractivityTest.cs | 4 +-- .../test/testassets/BasicTestApp/Index.razor | 11 +++++++ .../RazorComponents/App.razor | 1 + .../Interactivity/CircuitContextPage.razor | 24 +------------- 6 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index cd2f861846f0..e21db1a06c9e 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -103,7 +103,7 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, C { Log.InitializationStarted(_logger); - return Renderer.Dispatcher.InvokeAsync(async () => + return HandleInboundActivityAsync(() => Renderer.Dispatcher.InvokeAsync(async () => { if (_initialized) { @@ -164,7 +164,7 @@ public Task InitializeAsync(ProtectedPrerenderComponentApplicationStore store, C UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex), ex); } - }); + })); } // We handle errors in DisposeAsync because there's no real value in letting it propagate. diff --git a/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs index d14759152303..20d4d63500a8 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs @@ -22,28 +22,39 @@ public CircuitContextTest( { } - protected override void InitializeAsyncCore() + [Fact] + public void ComponentMethods_HaveCircuitContext() { Navigate(ServerPathBase, noReload: false); Browser.MountTestComponent(); - Browser.Equal("Circuit Context", () => Browser.Exists(By.TagName("h1")).Text); + TestCircuitContextCore(Browser); } [Fact] - public void ComponentMethods_HaveCircuitContext() + public void ComponentMethods_HaveCircuitContext_OnInitialPageLoad() { - Browser.Click(By.Id("trigger-click-event-button")); + // https://github.com/dotnet/aspnetcore/issues/57481 + Navigate($"{ServerPathBase}?initial-component-type={typeof(CircuitContextComponent).AssemblyQualifiedName}"); + TestCircuitContextCore(Browser); + } + + // Internal for reuse in Blazor Web tests + internal static void TestCircuitContextCore(IWebDriver browser) + { + browser.Equal("Circuit Context", () => browser.Exists(By.TagName("h1")).Text); + + browser.Click(By.Id("trigger-click-event-button")); - Browser.True(() => HasCircuitContext("SetParametersAsync")); - Browser.True(() => HasCircuitContext("OnInitializedAsync")); - Browser.True(() => HasCircuitContext("OnParametersSetAsync")); - Browser.True(() => HasCircuitContext("OnAfterRenderAsync")); - Browser.True(() => HasCircuitContext("InvokeDotNet")); - Browser.True(() => HasCircuitContext("OnClickEvent")); + browser.True(() => HasCircuitContext("SetParametersAsync")); + browser.True(() => HasCircuitContext("OnInitializedAsync")); + browser.True(() => HasCircuitContext("OnParametersSetAsync")); + browser.True(() => HasCircuitContext("OnAfterRenderAsync")); + browser.True(() => HasCircuitContext("InvokeDotNet")); + browser.True(() => HasCircuitContext("OnClickEvent")); bool HasCircuitContext(string eventName) { - var resultText = Browser.FindElement(By.Id($"circuit-context-result-{eventName}")).Text; + var resultText = browser.FindElement(By.Id($"circuit-context-result-{eventName}")).Text; var result = bool.Parse(resultText); return result; } diff --git a/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs index 240876cf53fa..59d6dcd420be 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs @@ -4,6 +4,7 @@ using Components.TestServer.RazorComponents; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests; using Microsoft.AspNetCore.E2ETesting; using Microsoft.AspNetCore.Testing; using OpenQA.Selenium; @@ -1168,8 +1169,7 @@ public void NavigationManagerCanRefreshSSRPageWhenServerInteractivityEnabled() public void InteractiveServerRootComponent_CanAccessCircuitContext() { Navigate($"{ServerPathBase}/interactivity/circuit-context"); - - Browser.Equal("True", () => Browser.FindElement(By.Id("has-circuit-context")).Text); + CircuitContextTest.TestCircuitContextCore(Browser); } [Fact] diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index fabbf037e349..3377745b8aab 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -1,4 +1,6 @@ @using Microsoft.AspNetCore.Components.Rendering +@using System.Web +@inject NavigationManager NavigationManager
Select test: