Skip to content

Commit 436c63a

Browse files
committed
Merge in 'release/8.0' changes
2 parents 3418372 + d4b8ba6 commit 436c63a

File tree

4 files changed

+95
-39
lines changed

4 files changed

+95
-39
lines changed

src/Components/Server/src/Circuits/CircuitHost.cs

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal partial class CircuitHost : IAsyncDisposable
2222
private readonly CircuitOptions _options;
2323
private readonly RemoteNavigationManager _navigationManager;
2424
private readonly ILogger _logger;
25-
private readonly Func<Func<Task>, Task> _dispatchInboundActivity;
25+
private Func<Func<Task>, Task> _dispatchInboundActivity;
2626
private CircuitHandler[] _circuitHandlers;
2727
private bool _initialized;
2828
private bool _isFirstUpdate = true;
@@ -735,11 +735,10 @@ internal Task UpdateRootComponents(
735735

736736
return Renderer.Dispatcher.InvokeAsync(async () =>
737737
{
738-
var webRootComponentManager = Renderer.GetOrCreateWebRootComponentManager();
739738
var shouldClearStore = false;
739+
var shouldWaitForQuiescence = false;
740740
var operations = operationBatch.Operations;
741741
var batchId = operationBatch.BatchId;
742-
Task[]? pendingTasks = null;
743742
try
744743
{
745744
if (Descriptors.Count > 0)
@@ -752,6 +751,7 @@ internal Task UpdateRootComponents(
752751
if (_isFirstUpdate)
753752
{
754753
_isFirstUpdate = false;
754+
shouldWaitForQuiescence = true;
755755
if (store != null)
756756
{
757757
shouldClearStore = true;
@@ -763,6 +763,7 @@ internal Task UpdateRootComponents(
763763

764764
// Retrieve the circuit handlers at this point.
765765
_circuitHandlers = [.. _scope.ServiceProvider.GetServices<CircuitHandler>().OrderBy(h => h.Order)];
766+
_dispatchInboundActivity = BuildInboundActivityDispatcher(_circuitHandlers, Circuit);
766767
await OnCircuitOpenedAsync(cancellation);
767768
await OnConnectionUpAsync(cancellation);
768769

@@ -774,44 +775,9 @@ internal Task UpdateRootComponents(
774775
throw new InvalidOperationException($"The first set of update operations must always be of type {nameof(RootComponentOperationType.Add)}");
775776
}
776777
}
777-
778-
pendingTasks = new Task[operations.Length];
779-
}
780-
781-
for (var i = 0; i < operations.Length; i++)
782-
{
783-
var operation = operations[i];
784-
switch (operation.Type)
785-
{
786-
case RootComponentOperationType.Add:
787-
var task = webRootComponentManager.AddRootComponentAsync(
788-
operation.SsrComponentId,
789-
operation.Descriptor.ComponentType,
790-
operation.Marker.Value.Key,
791-
operation.Descriptor.Parameters);
792-
if (pendingTasks != null)
793-
{
794-
pendingTasks[i] = task;
795-
}
796-
break;
797-
case RootComponentOperationType.Update:
798-
// We don't need to await component updates as any unhandled exception will be reported and terminate the circuit.
799-
_ = webRootComponentManager.UpdateRootComponentAsync(
800-
operation.SsrComponentId,
801-
operation.Descriptor.ComponentType,
802-
operation.Marker.Value.Key,
803-
operation.Descriptor.Parameters);
804-
break;
805-
case RootComponentOperationType.Remove:
806-
webRootComponentManager.RemoveRootComponent(operation.SsrComponentId);
807-
break;
808-
}
809778
}
810779

811-
if (pendingTasks != null)
812-
{
813-
await Task.WhenAll(pendingTasks);
814-
}
780+
await PerformRootComponentOperations(operations, shouldWaitForQuiescence);
815781

816782
await Client.SendAsync("JS.EndUpdateRootComponents", batchId);
817783

@@ -837,6 +803,58 @@ internal Task UpdateRootComponents(
837803
});
838804
}
839805

806+
private async ValueTask PerformRootComponentOperations(
807+
RootComponentOperation[] operations,
808+
bool shouldWaitForQuiescence)
809+
{
810+
var webRootComponentManager = Renderer.GetOrCreateWebRootComponentManager();
811+
var pendingTasks = shouldWaitForQuiescence
812+
? new Task[operations.Length]
813+
: null;
814+
815+
// The inbound activity pipeline needs to be awaited because it populates
816+
// the pending tasks used to wait for quiescence.
817+
await HandleInboundActivityAsync(() =>
818+
{
819+
for (var i = 0; i < operations.Length; i++)
820+
{
821+
var operation = operations[i];
822+
switch (operation.Type)
823+
{
824+
case RootComponentOperationType.Add:
825+
var task = webRootComponentManager.AddRootComponentAsync(
826+
operation.SsrComponentId,
827+
operation.Descriptor.ComponentType,
828+
operation.Marker.Value.Key,
829+
operation.Descriptor.Parameters);
830+
if (pendingTasks != null)
831+
{
832+
pendingTasks[i] = task;
833+
}
834+
break;
835+
case RootComponentOperationType.Update:
836+
// We don't need to await component updates as any unhandled exception will be reported and terminate the circuit.
837+
_ = webRootComponentManager.UpdateRootComponentAsync(
838+
operation.SsrComponentId,
839+
operation.Descriptor.ComponentType,
840+
operation.Marker.Value.Key,
841+
operation.Descriptor.Parameters);
842+
break;
843+
case RootComponentOperationType.Remove:
844+
webRootComponentManager.RemoveRootComponent(operation.SsrComponentId);
845+
break;
846+
}
847+
}
848+
849+
return Task.CompletedTask;
850+
});
851+
852+
if (pendingTasks != null)
853+
{
854+
await Task.WhenAll(pendingTasks);
855+
}
856+
}
857+
840858
private static partial class Log
841859
{
842860
// 100s used for lifecycle stuff

src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,14 @@ public void NavigationManagerCanRefreshSSRPageWhenServerInteractivityEnabled()
11591159
Browser.Equal("GET", () => Browser.Exists(By.Id("method")).Text);
11601160
}
11611161

1162+
[Fact]
1163+
public void InteractiveServerRootComponent_CanAccessCircuitContext()
1164+
{
1165+
Navigate($"{ServerPathBase}/interactivity/circuit-context");
1166+
1167+
Browser.Equal("True", () => Browser.FindElement(By.Id("has-circuit-context")).Text);
1168+
}
1169+
11621170
private void BlockWebAssemblyResourceLoad()
11631171
{
11641172
((IJavaScriptExecutor)Browser).ExecuteScript("sessionStorage.setItem('block-load-boot-resource', 'true')");

src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Components.TestServer.RazorComponents;
99
using Components.TestServer.RazorComponents.Pages.Forms;
1010
using Components.TestServer.Services;
11+
using Microsoft.AspNetCore.Components.Server.Circuits;
1112
using Microsoft.AspNetCore.Mvc;
1213

1314
namespace TestServer;
@@ -35,6 +36,10 @@ public void ConfigureServices(IServiceCollection services)
3536
services.AddHttpContextAccessor();
3637
services.AddSingleton<AsyncOperationService>();
3738
services.AddCascadingAuthenticationState();
39+
40+
var circuitContextAccessor = new TestCircuitContextAccessor();
41+
services.AddSingleton<CircuitHandler>(circuitContextAccessor);
42+
services.AddSingleton(circuitContextAccessor);
3843
}
3944

4045
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@page "/interactivity/circuit-context"
2+
@rendermode RenderMode.InteractiveServer
3+
@inject TestCircuitContextAccessor CircuitContextAccessor
4+
5+
<h1>Circuit context</h1>
6+
7+
<p>
8+
Has circuit context: <span id="has-circuit-context">@_hasCircuitContext</span>
9+
</p>
10+
11+
@code {
12+
private bool _hasCircuitContext;
13+
14+
protected override async Task OnAfterRenderAsync(bool firstRender)
15+
{
16+
if (firstRender)
17+
{
18+
await Task.Yield();
19+
20+
_hasCircuitContext = CircuitContextAccessor.HasCircuitContext;
21+
22+
StateHasChanged();
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)