Skip to content

Commit 4eaaaad

Browse files
authored
Move ComponentHubReliability tests to unit and E2E tests (dotnet#32834)
* Move ComponentHubReliability tests to unit and E2E tests * Clean up source formatting in ComponentHubTest * Remove out parameter definition from ComponentHub * Fix NavigationFailure component test * Move component mount to test cases * Look at log with correct level * Simplify log assertion check * Wait for exception UI to appear before asserting log * Factor out mocked dependencies to interfaces * Fix up service injection and CircuitHandleRegistry * Clean up interfaces and fix up Context.Items usage * Reseal ComponentHub class
1 parent b0f3998 commit 4eaaaad

File tree

12 files changed

+460
-468
lines changed

12 files changed

+460
-468
lines changed

src/Components/Server/src/Circuits/CircuitFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
namespace Microsoft.AspNetCore.Components.Server.Circuits
1818
{
19-
internal class CircuitFactory
19+
internal class CircuitFactory : ICircuitFactory
2020
{
2121
private readonly IServiceScopeFactory _scopeFactory;
2222
private readonly ILoggerFactory _loggerFactory;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Security.Claims;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Components.Lifetime;
10+
using Microsoft.AspNetCore.Components.Routing;
11+
using Microsoft.AspNetCore.DataProtection;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
15+
using Microsoft.JSInterop;
16+
17+
namespace Microsoft.AspNetCore.Components.Server.Circuits
18+
{
19+
internal sealed class CircuitHandleRegistry : ICircuitHandleRegistry
20+
{
21+
public CircuitHandle GetCircuitHandle(IDictionary<object, object?>circuitHandles, object circuitKey)
22+
{
23+
if (circuitHandles.TryGetValue(circuitKey, out var circuitHandle))
24+
{
25+
return (CircuitHandle) circuitHandle;
26+
}
27+
28+
return null;;
29+
}
30+
31+
public CircuitHost GetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey)
32+
{
33+
if (circuitHandles.TryGetValue(circuitKey, out var circuitHandle))
34+
{
35+
return ((CircuitHandle)circuitHandle).CircuitHost;
36+
}
37+
38+
return null;
39+
}
40+
41+
public void SetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey, CircuitHost circuitHost)
42+
{
43+
circuitHandles[circuitKey] = circuitHost?.Handle;
44+
}
45+
}
46+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Security.Claims;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Components.Lifetime;
10+
using Microsoft.AspNetCore.Components.Routing;
11+
using Microsoft.AspNetCore.DataProtection;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
15+
using Microsoft.JSInterop;
16+
17+
namespace Microsoft.AspNetCore.Components.Server.Circuits
18+
{
19+
internal interface ICircuitFactory
20+
{
21+
ValueTask<CircuitHost> CreateCircuitHostAsync(
22+
IReadOnlyList<ComponentDescriptor> components,
23+
CircuitClientProxy client,
24+
string baseUri,
25+
string uri,
26+
ClaimsPrincipal user,
27+
IComponentApplicationStateStore store);
28+
}
29+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Security.Claims;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Components.Lifetime;
10+
using Microsoft.AspNetCore.Components.Routing;
11+
using Microsoft.AspNetCore.DataProtection;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
15+
using Microsoft.JSInterop;
16+
17+
namespace Microsoft.AspNetCore.Components.Server.Circuits
18+
{
19+
internal interface ICircuitHandleRegistry
20+
{
21+
CircuitHandle GetCircuitHandle(IDictionary<object, object?> circuitHandles, object circuitKey);
22+
23+
CircuitHost GetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey);
24+
25+
void SetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey, CircuitHost circuitHost);
26+
}
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Security.Claims;
8+
using System.Threading.Tasks;
9+
using Microsoft.AspNetCore.Components.Lifetime;
10+
using Microsoft.AspNetCore.Components.Routing;
11+
using Microsoft.AspNetCore.DataProtection;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Logging;
14+
using Microsoft.Extensions.Options;
15+
using Microsoft.JSInterop;
16+
17+
namespace Microsoft.AspNetCore.Components.Server
18+
{
19+
internal interface IServerComponentDeserializer
20+
{
21+
bool TryDeserializeComponentDescriptorCollection(
22+
string serializedComponentRecords,
23+
out List<ComponentDescriptor> descriptors);
24+
}
25+
}

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.Server
5555
// * If a marker has the right sequence but the invocation ID is different we will fail at that point. We know for sure that the
5656
// component wasn't render as part of the same response.
5757
// * If a marker can't be unprotected we will fail early. We know that the marker was tampered with and can't be trusted.
58-
internal class ServerComponentDeserializer
58+
internal class ServerComponentDeserializer : IServerComponentDeserializer
5959
{
6060
private readonly IDataProtector _dataProtector;
6161
private readonly ILogger<ServerComponentDeserializer> _logger;

src/Components/Server/src/ComponentHub.cs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,29 @@ namespace Microsoft.AspNetCore.Components.Server
3737
internal sealed class ComponentHub : Hub
3838
{
3939
private static readonly object CircuitKey = new object();
40-
private readonly ServerComponentDeserializer _serverComponentSerializer;
40+
private readonly IServerComponentDeserializer _serverComponentSerializer;
4141
private readonly IDataProtectionProvider _dataProtectionProvider;
42-
private readonly CircuitFactory _circuitFactory;
42+
private readonly ICircuitFactory _circuitFactory;
4343
private readonly CircuitIdFactory _circuitIdFactory;
4444
private readonly CircuitRegistry _circuitRegistry;
45+
private readonly ICircuitHandleRegistry _circuitHandleRegistry;
4546
private readonly ILogger _logger;
4647

4748
public ComponentHub(
48-
ServerComponentDeserializer serializer,
49+
IServerComponentDeserializer serializer,
4950
IDataProtectionProvider dataProtectionProvider,
50-
CircuitFactory circuitFactory,
51+
ICircuitFactory circuitFactory,
5152
CircuitIdFactory circuitIdFactory,
5253
CircuitRegistry circuitRegistry,
54+
ICircuitHandleRegistry circuitHandleRegistry,
5355
ILogger<ComponentHub> logger)
5456
{
5557
_serverComponentSerializer = serializer;
5658
_dataProtectionProvider = dataProtectionProvider;
5759
_circuitFactory = circuitFactory;
5860
_circuitIdFactory = circuitIdFactory;
5961
_circuitRegistry = circuitRegistry;
62+
_circuitHandleRegistry = circuitHandleRegistry;
6063
_logger = logger;
6164
}
6265

@@ -69,7 +72,7 @@ public override Task OnDisconnectedAsync(Exception exception)
6972
{
7073
// If the CircuitHost is gone now this isn't an error. This could happen if the disconnect
7174
// if the result of well behaving client hanging up after an unhandled exception.
72-
var circuitHost = GetCircuit();
75+
var circuitHost = _circuitHandleRegistry.GetCircuit(Context.Items, CircuitKey);
7376
if (circuitHost == null)
7477
{
7578
return Task.CompletedTask;
@@ -80,7 +83,7 @@ public override Task OnDisconnectedAsync(Exception exception)
8083

8184
public async ValueTask<string> StartCircuit(string baseUri, string uri, string serializedComponentRecords, string applicationState)
8285
{
83-
var circuitHost = GetCircuit();
86+
var circuitHost = _circuitHandleRegistry.GetCircuit(Context.Items, CircuitKey);
8487
if (circuitHost != null)
8588
{
8689
// This is an error condition and an attempt to bind multiple circuits to a single connection.
@@ -139,7 +142,7 @@ public async ValueTask<string> StartCircuit(string baseUri, string uri, string s
139142
// It's safe to *publish* the circuit now because nothing will be able
140143
// to run inside it until after InitializeAsync completes.
141144
_circuitRegistry.Register(circuitHost);
142-
SetCircuit(circuitHost);
145+
_circuitHandleRegistry.SetCircuit(Context.Items, CircuitKey, circuitHost);
143146

144147
// Returning the secret here so the client can reconnect.
145148
//
@@ -176,7 +179,7 @@ public async ValueTask<bool> ConnectCircuit(string circuitIdSecret)
176179
Context.ConnectionAborted);
177180
if (circuitHost != null)
178181
{
179-
SetCircuit(circuitHost);
182+
_circuitHandleRegistry.SetCircuit(Context.Items, CircuitKey, circuitHost);
180183
circuitHost.SetCircuitUser(Context.User);
181184
circuitHost.SendPendingBatches();
182185
return true;
@@ -262,7 +265,7 @@ public async ValueTask OnLocationChanged(string uri, bool intercepted)
262265
// See comment on error handling on the class definition.
263266
private async ValueTask<CircuitHost> GetActiveCircuitAsync([CallerMemberName] string callSite = "")
264267
{
265-
var handle = (CircuitHandle)Context.Items[CircuitKey];
268+
var handle = _circuitHandleRegistry.GetCircuitHandle(Context.Items, CircuitKey);
266269
var circuitHost = handle?.CircuitHost;
267270
if (handle != null && circuitHost == null)
268271
{
@@ -286,16 +289,6 @@ private async ValueTask<CircuitHost> GetActiveCircuitAsync([CallerMemberName] st
286289
return circuitHost;
287290
}
288291

289-
private CircuitHost GetCircuit()
290-
{
291-
return ((CircuitHandle)Context.Items[CircuitKey])?.CircuitHost;
292-
}
293-
294-
private void SetCircuit(CircuitHost circuitHost)
295-
{
296-
Context.Items[CircuitKey] = circuitHost?.Handle;
297-
}
298-
299292
private static Task NotifyClientError(IClientProxy client, string error) => client.SendAsync("JS.Error", error);
300293

301294
private static class Log

src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti
6060
// user's configuration. So even if the user has multiple independent server-side
6161
// Components entrypoints, this lot is the same and repeated registrations are a no-op.
6262
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<StaticFileOptions>, ConfigureStaticFilesOptions>());
63-
services.TryAddSingleton<CircuitFactory>();
64-
services.TryAddSingleton<ServerComponentDeserializer>();
63+
services.TryAddSingleton<ICircuitFactory, CircuitFactory>();
64+
services.TryAddSingleton<IServerComponentDeserializer, ServerComponentDeserializer>();
65+
services.TryAddSingleton<ICircuitHandleRegistry, CircuitHandleRegistry>();
6566
services.TryAddSingleton<RootComponentTypeCache>();
6667
services.TryAddSingleton<ComponentParameterDeserializer>();
6768
services.TryAddSingleton<ComponentParametersTypeCache>();

0 commit comments

Comments
 (0)