Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Components/Server/src/Circuits/CircuitFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace Microsoft.AspNetCore.Components.Server.Circuits
{
internal class CircuitFactory
internal class CircuitFactory : ICircuitFactory
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internal class CircuitFactory : ICircuitFactory
internal sealed class CircuitFactory : ICircuitFactory

{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILoggerFactory _loggerFactory;
Expand Down
46 changes: 46 additions & 0 deletions src/Components/Server/src/Circuits/CircuitHandleRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Lifetime;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

namespace Microsoft.AspNetCore.Components.Server.Circuits
{
internal sealed class CircuitHandleRegistry : ICircuitHandleRegistry
{
public CircuitHandle GetCircuitHandle(IDictionary<object, object?>circuitHandles, object circuitKey)
{
if (circuitHandles.TryGetValue(circuitKey, out var circuitHandle))
{
return (CircuitHandle) circuitHandle;
}

return null;;
}

public CircuitHost GetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey)
{
if (circuitHandles.TryGetValue(circuitKey, out var circuitHandle))
{
return ((CircuitHandle)circuitHandle).CircuitHost;
}

return null;
}

public void SetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey, CircuitHost circuitHost)
{
circuitHandles[circuitKey] = circuitHost?.Handle;
}
}
}
29 changes: 29 additions & 0 deletions src/Components/Server/src/Circuits/ICircuitFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Lifetime;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

namespace Microsoft.AspNetCore.Components.Server.Circuits
{
internal interface ICircuitFactory
{
ValueTask<CircuitHost> CreateCircuitHostAsync(
IReadOnlyList<ComponentDescriptor> components,
CircuitClientProxy client,
string baseUri,
string uri,
ClaimsPrincipal user,
IComponentApplicationStateStore store);
}
}
27 changes: 27 additions & 0 deletions src/Components/Server/src/Circuits/ICircuitHandleRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Lifetime;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

namespace Microsoft.AspNetCore.Components.Server.Circuits
{
internal interface ICircuitHandleRegistry
{
CircuitHandle GetCircuitHandle(IDictionary<object, object?> circuitHandles, object circuitKey);

CircuitHost GetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey);

void SetCircuit(IDictionary<object, object?> circuitHandles, object circuitKey, CircuitHost circuitHost);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Lifetime;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;

namespace Microsoft.AspNetCore.Components.Server
{
internal interface IServerComponentDeserializer
{
bool TryDeserializeComponentDescriptorCollection(
string serializedComponentRecords,
out List<ComponentDescriptor> descriptors);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.Server
// * 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
// component wasn't render as part of the same response.
// * If a marker can't be unprotected we will fail early. We know that the marker was tampered with and can't be trusted.
internal class ServerComponentDeserializer
internal class ServerComponentDeserializer : IServerComponentDeserializer
{
private readonly IDataProtector _dataProtector;
private readonly ILogger<ServerComponentDeserializer> _logger;
Expand Down
31 changes: 12 additions & 19 deletions src/Components/Server/src/ComponentHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,29 @@ namespace Microsoft.AspNetCore.Components.Server
internal sealed class ComponentHub : Hub
{
private static readonly object CircuitKey = new object();
private readonly ServerComponentDeserializer _serverComponentSerializer;
private readonly IServerComponentDeserializer _serverComponentSerializer;
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly CircuitFactory _circuitFactory;
private readonly ICircuitFactory _circuitFactory;
private readonly CircuitIdFactory _circuitIdFactory;
private readonly CircuitRegistry _circuitRegistry;
private readonly ICircuitHandleRegistry _circuitHandleRegistry;
private readonly ILogger _logger;

public ComponentHub(
ServerComponentDeserializer serializer,
IServerComponentDeserializer serializer,
IDataProtectionProvider dataProtectionProvider,
CircuitFactory circuitFactory,
ICircuitFactory circuitFactory,
CircuitIdFactory circuitIdFactory,
CircuitRegistry circuitRegistry,
ICircuitHandleRegistry circuitHandleRegistry,
ILogger<ComponentHub> logger)
{
_serverComponentSerializer = serializer;
_dataProtectionProvider = dataProtectionProvider;
_circuitFactory = circuitFactory;
_circuitIdFactory = circuitIdFactory;
_circuitRegistry = circuitRegistry;
_circuitHandleRegistry = circuitHandleRegistry;
_logger = logger;
}

Expand All @@ -69,7 +72,7 @@ public override Task OnDisconnectedAsync(Exception exception)
{
// If the CircuitHost is gone now this isn't an error. This could happen if the disconnect
// if the result of well behaving client hanging up after an unhandled exception.
var circuitHost = GetCircuit();
var circuitHost = _circuitHandleRegistry.GetCircuit(Context.Items, CircuitKey);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we configure Context.Items instead of introducing the registry?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original intent here was to have an injected type that we could mock in the tests to avoid actually have to created a circuit instance. This originally started out as a singleton service with its own internal dictionary. However, that doesn't work because Context.Items is set up to correctly exist at a connection scope so I set up the registry as a wrapper around Context.Items with the ability to override in the code.

It's not possible for us to configure Context.Items directly in the test since the setting/getting happens entirely within the StartCircuit invocation so it's not possible to set up/tear down ahead of time.

if (circuitHost == null)
{
return Task.CompletedTask;
Expand All @@ -80,7 +83,7 @@ public override Task OnDisconnectedAsync(Exception exception)

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

// Returning the secret here so the client can reconnect.
//
Expand Down Expand Up @@ -176,7 +179,7 @@ public async ValueTask<bool> ConnectCircuit(string circuitIdSecret)
Context.ConnectionAborted);
if (circuitHost != null)
{
SetCircuit(circuitHost);
_circuitHandleRegistry.SetCircuit(Context.Items, CircuitKey, circuitHost);
circuitHost.SetCircuitUser(Context.User);
circuitHost.SendPendingBatches();
return true;
Expand Down Expand Up @@ -251,7 +254,7 @@ public async ValueTask OnLocationChanged(string uri, bool intercepted)
// See comment on error handling on the class definition.
private async ValueTask<CircuitHost> GetActiveCircuitAsync([CallerMemberName] string callSite = "")
{
var handle = (CircuitHandle)Context.Items[CircuitKey];
var handle = _circuitHandleRegistry.GetCircuitHandle(Context.Items, CircuitKey);
var circuitHost = handle?.CircuitHost;
if (handle != null && circuitHost == null)
{
Expand All @@ -275,16 +278,6 @@ private async ValueTask<CircuitHost> GetActiveCircuitAsync([CallerMemberName] st
return circuitHost;
}

private CircuitHost GetCircuit()
{
return ((CircuitHandle)Context.Items[CircuitKey])?.CircuitHost;
}

private void SetCircuit(CircuitHost circuitHost)
{
Context.Items[CircuitKey] = circuitHost?.Handle;
}

private static Task NotifyClientError(IClientProxy client, string error) => client.SendAsync("JS.Error", error);

private static class Log
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti
// user's configuration. So even if the user has multiple independent server-side
// Components entrypoints, this lot is the same and repeated registrations are a no-op.
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<StaticFileOptions>, ConfigureStaticFilesOptions>());
services.TryAddSingleton<CircuitFactory>();
services.TryAddSingleton<ServerComponentDeserializer>();
services.TryAddSingleton<ICircuitFactory, CircuitFactory>();
services.TryAddSingleton<IServerComponentDeserializer, ServerComponentDeserializer>();
services.TryAddSingleton<ICircuitHandleRegistry, CircuitHandleRegistry>();
services.TryAddSingleton<RootComponentTypeCache>();
services.TryAddSingleton<ComponentParameterDeserializer>();
services.TryAddSingleton<ComponentParametersTypeCache>();
Expand Down
Loading