public async Task ThrowsIfWeReceiveAnOutOfSequenceClientAcknowledge() { // Arrange var serviceProvider = new ServiceCollection().BuildServiceProvider(); var firstBatchTCS = new TaskCompletionSource <object>(); var secondBatchTCS = new TaskCompletionSource <object>(); var offlineClient = new CircuitClientProxy(new Mock <IClientProxy>(MockBehavior.Strict).Object, "offline-client"); offlineClient.SetDisconnected(); var renderer = GetRemoteRenderer(serviceProvider, offlineClient); RenderFragment initialContent = (builder) => { builder.OpenElement(0, "my element"); builder.AddContent(1, "some text"); builder.CloseElement(); }; var trigger = new Trigger(); var renderIds = new List <long>(); var onlineClient = new Mock <IClientProxy>(); onlineClient.Setup(c => c.SendCoreAsync(It.IsAny <string>(), It.IsAny <object[]>(), It.IsAny <CancellationToken>())) .Callback((string name, object[] value, CancellationToken token) => renderIds.Add((long)value[1])) .Returns <string, object[], CancellationToken>((n, v, t) => (long)v[1] == 2 ? firstBatchTCS.Task : secondBatchTCS.Task); // This produces the initial batch (id = 2) var result = await renderer.RenderComponentAsync <AutoParameterTestComponent>( ParameterCollection.FromDictionary(new Dictionary <string, object> { [nameof(AutoParameterTestComponent.Content)] = initialContent, [nameof(AutoParameterTestComponent.Trigger)] = trigger })); trigger.Component.Content = (builder) => { builder.OpenElement(0, "offline element"); builder.AddContent(1, "offline text"); builder.CloseElement(); }; // This produces an additional batch (id = 3) trigger.TriggerRender(); var originallyQueuedBatches = renderer.PendingRenderBatches.Count; // Act offlineClient.Transfer(onlineClient.Object, "new-connection"); var task = renderer.ProcessBufferedRenderBatches(); var exceptions = new List <Exception>(); renderer.UnhandledException += (sender, e) => { exceptions.Add(e); }; // Pretend that we missed the ack for the initial batch renderer.OnRenderCompleted(3, null); firstBatchTCS.SetResult(null); secondBatchTCS.SetResult(null); // Assert var exception = Assert.Single(exceptions); }
public async Task ProcessBufferedRenderBatches_WritesRenders() { // Arrange var serviceProvider = new ServiceCollection().BuildServiceProvider(); var renderIds = new List <long>(); var firstBatchTCS = new TaskCompletionSource <object>(); var secondBatchTCS = new TaskCompletionSource <object>(); var thirdBatchTCS = new TaskCompletionSource <object>(); var initialClient = new Mock <IClientProxy>(); initialClient.Setup(c => c.SendCoreAsync(It.IsAny <string>(), It.IsAny <object[]>(), It.IsAny <CancellationToken>())) .Callback((string name, object[] value, CancellationToken token) => renderIds.Add((long)value[1])) .Returns(firstBatchTCS.Task); var circuitClient = new CircuitClientProxy(initialClient.Object, "connection0"); var renderer = GetRemoteRenderer(serviceProvider, circuitClient); var component = new TestComponent(builder => { builder.OpenElement(0, "my element"); builder.AddContent(1, "some text"); builder.CloseElement(); }); var client = new Mock <IClientProxy>(); client.Setup(c => c.SendCoreAsync(It.IsAny <string>(), It.IsAny <object[]>(), It.IsAny <CancellationToken>())) .Callback((string name, object[] value, CancellationToken token) => renderIds.Add((long)value[1])) .Returns <string, object[], CancellationToken>((n, v, t) => (long)v[1] == 3 ? secondBatchTCS.Task : thirdBatchTCS.Task); var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); renderer.OnRenderCompleted(2, null); firstBatchTCS.SetResult(null); circuitClient.SetDisconnected(); component.TriggerRender(); component.TriggerRender(); // Act circuitClient.Transfer(client.Object, "new-connection"); var task = renderer.ProcessBufferedRenderBatches(); foreach (var id in renderIds.ToArray()) { renderer.OnRenderCompleted(id, null); } secondBatchTCS.SetResult(null); thirdBatchTCS.SetResult(null); // Assert Assert.Equal(new long[] { 2, 3, 4 }, renderIds); Assert.True(task.Wait(3000), "One or more render batches werent acknowledged"); await task; }
private TestRemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, CircuitClientProxy circuitClient = null) { return(new TestRemoteRenderer( serviceProvider, NullLoggerFactory.Instance, new CircuitOptions(), circuitClient ?? new CircuitClientProxy(), NullLogger.Instance)); }
private RemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, CircuitClientProxy circuitClientProxy) { return(new RemoteRenderer( serviceProvider, new RendererRegistry(), Mock.Of <IJSRuntime>(), circuitClientProxy, Dispatcher, HtmlEncoder.Default, NullLogger.Instance)); }
public async Task ProcessBufferedRenderBatches_WritesRenders() { // Arrange var serviceProvider = new ServiceCollection().BuildServiceProvider(); var renderIds = new List <int>(); var initialClient = new Mock <IClientProxy>(); initialClient.Setup(c => c.SendCoreAsync(It.IsAny <string>(), It.IsAny <object[]>(), It.IsAny <CancellationToken>())) .Callback((string name, object[] value, CancellationToken token) => { renderIds.Add((int)value[1]); }) .Returns(Task.CompletedTask); var circuitClient = new CircuitClientProxy(initialClient.Object, "connection0"); var renderer = GetRemoteRenderer(serviceProvider, circuitClient); var component = new TestComponent(builder => { builder.OpenElement(0, "my element"); builder.AddContent(1, "some text"); builder.CloseElement(); }); var client = new Mock <IClientProxy>(); client.Setup(c => c.SendCoreAsync(It.IsAny <string>(), It.IsAny <object[]>(), It.IsAny <CancellationToken>())) .Callback((string name, object[] value, CancellationToken token) => { renderIds.Add((int)value[1]); }) .Returns(Task.CompletedTask); var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); renderer.OnRenderCompleted(1, null); circuitClient.SetDisconnected(); component.TriggerRender(); component.TriggerRender(); // Act circuitClient.Transfer(client.Object, "new-connection"); var task = renderer.ProcessBufferedRenderBatches(); foreach (var id in renderIds) { renderer.OnRenderCompleted(id, null); } await task; // Assert client.Verify(c => c.SendCoreAsync("JS.RenderBatch", It.IsAny <object[]>(), It.IsAny <CancellationToken>()), Times.Exactly(2)); }
// Implement a `CreateCircuitHostAsync` that mocks the construction // of the CircuitHost. public ValueTask <CircuitHost> CreateCircuitHostAsync( IReadOnlyList <ComponentDescriptor> components, CircuitClientProxy client, string baseUri, string uri, ClaimsPrincipal user, IPersistentComponentStateStore store) { var serviceScope = new Mock <IServiceScope>(); var circuitHost = TestCircuitHost.Create(serviceScope: new AsyncServiceScope(serviceScope.Object)); return(ValueTask.FromResult(circuitHost)); }
/// <summary> /// Creates a new <see cref="RemoteRenderer"/>. /// </summary> public RemoteRenderer( IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, IJSRuntime jsRuntime, CircuitClientProxy client, ILogger logger) : base(serviceProvider, loggerFactory) { _jsRuntime = jsRuntime; _client = client; _options = options; _logger = logger; }
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute) { var serviceCollection = new ServiceCollection(); serviceCollection.AddScoped <IUriHelper>(_ => { var uriHelper = new RemoteUriHelper(NullLogger <RemoteUriHelper> .Instance); uriHelper.InitializeState(uriAbsolute, baseUriAbsolute); return(uriHelper); }); var serviceScope = serviceCollection.BuildServiceProvider().CreateScope(); return(TestCircuitHost.Create(serviceScope)); }
public void Transfer_SetsConnected() { // Arrange var clientProxy = Mock.Of <IClientProxy>( c => c.SendCoreAsync(It.IsAny <string>(), It.IsAny <object[]>(), It.IsAny <CancellationToken>()) == Task.CompletedTask); var circuitClient = new CircuitClientProxy(clientProxy, "connection0"); circuitClient.SetDisconnected(); // Act circuitClient.Transfer(Mock.Of <IClientProxy>(), "connection1"); // Assert Assert.True(circuitClient.Connected); }
public void IfInitialized_IsConnectedValueDeterminedByCircuitProxy() { // Arrange var clientProxy = new FakeClientProxy(); var circuitProxy = new CircuitClientProxy(clientProxy, "test connection"); var remoteComponentContext = new RemoteComponentContext(); // Act/Assert: Can observe connected state remoteComponentContext.Initialize(circuitProxy); Assert.True(remoteComponentContext.IsConnected); // Act/Assert: Can observe disconnected state circuitProxy.SetDisconnected(); Assert.False(remoteComponentContext.IsConnected); }
/// <summary> /// Creates a new <see cref="RemoteRenderer"/>. /// </summary> public RemoteRenderer( IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, CircuitClientProxy client, ILogger logger, RemoteJSRuntime jsRuntime, CircuitJSComponentInterop jsComponentInterop) : base(serviceProvider, loggerFactory, jsRuntime.ReadJsonSerializerOptions(), jsComponentInterop) { _client = client; _options = options; _logger = logger; ElementReferenceContext = jsRuntime.ElementReferenceContext; }
/// <summary> /// Creates a new <see cref="RemoteRenderer"/>. /// </summary> public RemoteRenderer( IServiceProvider serviceProvider, RendererRegistry rendererRegistry, IJSRuntime jsRuntime, CircuitClientProxy client, IDispatcher dispatcher, HtmlEncoder encoder, ILogger logger) : base(serviceProvider, encoder.Encode, dispatcher) { _rendererRegistry = rendererRegistry; _jsRuntime = jsRuntime; _client = client; _id = _rendererRegistry.Add(this); _logger = logger; }
/// <summary> /// Creates a new <see cref="RemoteRenderer"/>. /// </summary> public RemoteRenderer( IServiceProvider serviceProvider, ILoggerFactory loggerFactory, RendererRegistry rendererRegistry, IJSRuntime jsRuntime, CircuitClientProxy client, HtmlEncoder encoder, ILogger logger) : base(serviceProvider, loggerFactory, encoder.Encode) { _rendererRegistry = rendererRegistry; _jsRuntime = jsRuntime; _client = client; Id = _rendererRegistry.Add(this); _logger = logger; }
public CircuitHost( CircuitId circuitId, AsyncServiceScope scope, CircuitOptions options, CircuitClientProxy client, RemoteRenderer renderer, IReadOnlyList <ComponentDescriptor> descriptors, RemoteJSRuntime jsRuntime, RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, ILogger logger) { CircuitId = circuitId; if (CircuitId.Secret is null) { // Prevent the use of a 'default' secret. throw new ArgumentException($"Property '{nameof(CircuitId.Secret)}' cannot be null.", nameof(circuitId)); } _scope = scope; _options = options ?? throw new ArgumentNullException(nameof(options)); Client = client ?? throw new ArgumentNullException(nameof(client)); Renderer = renderer ?? throw new ArgumentNullException(nameof(renderer)); Descriptors = descriptors ?? throw new ArgumentNullException(nameof(descriptors)); JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); _navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager)); _circuitHandlers = circuitHandlers ?? throw new ArgumentNullException(nameof(circuitHandlers)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); Services = scope.ServiceProvider; Circuit = new Circuit(this); Handle = new CircuitHandle() { CircuitHost = this, }; // An unhandled exception from the renderer is always fatal because it came from user code. Renderer.UnhandledException += ReportAndInvoke_UnhandledException; Renderer.UnhandledSynchronizationException += SynchronizationContext_UnhandledException; JSRuntime.UnhandledException += ReportAndInvoke_UnhandledException; _navigationManager.UnhandledException += ReportAndInvoke_UnhandledException; }
public static CircuitHost Create( CircuitId?circuitId = null, AsyncServiceScope?serviceScope = null, RemoteRenderer remoteRenderer = null, IReadOnlyList <ComponentDescriptor> descriptors = null, CircuitHandler[] handlers = null, CircuitClientProxy clientProxy = null) { serviceScope = serviceScope ?? new AsyncServiceScope(Mock.Of <IServiceScope>()); clientProxy = clientProxy ?? new CircuitClientProxy(Mock.Of <IClientProxy>(), Guid.NewGuid().ToString()); var jsRuntime = new RemoteJSRuntime(Options.Create(new CircuitOptions()), Options.Create(new HubOptions <ComponentHub>()), Mock.Of <ILogger <RemoteJSRuntime> >()); var navigationManager = new RemoteNavigationManager(Mock.Of <ILogger <RemoteNavigationManager> >()); var serviceProvider = new Mock <IServiceProvider>(); serviceProvider .Setup(services => services.GetService(typeof(IJSRuntime))) .Returns(jsRuntime); if (remoteRenderer == null) { remoteRenderer = new RemoteRenderer( serviceProvider.Object, NullLoggerFactory.Instance, new CircuitOptions(), clientProxy, NullLogger.Instance, jsRuntime, new CircuitJSComponentInterop(new CircuitOptions())); } handlers ??= Array.Empty <CircuitHandler>(); return(new TestCircuitHost( circuitId is null ? new CircuitId(Guid.NewGuid().ToString(), Guid.NewGuid().ToString()) : circuitId.Value, serviceScope.Value, new CircuitOptions(), clientProxy, remoteRenderer, descriptors ?? new List <ComponentDescriptor>(), jsRuntime, navigationManager, handlers, NullLogger <CircuitHost> .Instance)); }
private RemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, CircuitClientProxy circuitClientProxy) { var jsRuntime = new Mock <IJSRuntime>(); jsRuntime.Setup(r => r.InvokeAsync <object>( "Blazor._internal.attachRootComponentToElement", It.IsAny <int>(), It.IsAny <string>(), It.IsAny <int>())) .ReturnsAsync(Task.FromResult <object>(null)); return(new RemoteRenderer( serviceProvider, new RendererRegistry(), jsRuntime.Object, circuitClientProxy, Dispatcher, HtmlEncoder.Default, NullLogger.Instance)); }
public async Task SendCoreAsync_WithoutTransfer() { // Arrange bool?isCancelled = null; var clientProxy = new Mock <IClientProxy>(); clientProxy.Setup(c => c.SendCoreAsync(It.IsAny <string>(), It.IsAny <object[]>(), It.IsAny <CancellationToken>())) .Callback((string _, object[] __, CancellationToken token) => { isCancelled = token.IsCancellationRequested; }) .Returns(Task.CompletedTask); var circuitClient = new CircuitClientProxy(clientProxy.Object, "connection0"); // Act var sendTask = circuitClient.SendCoreAsync("test", Array.Empty <object>()); await sendTask; // Assert Assert.False(isCancelled); }
public TestRemoteRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, CircuitClientProxy client, ILogger logger) : base(serviceProvider, loggerFactory, options, client, logger, null) { }
private TestCircuitHost(CircuitId circuitId, AsyncServiceScope scope, CircuitOptions options, CircuitClientProxy client, RemoteRenderer renderer, IReadOnlyList <ComponentDescriptor> descriptors, RemoteJSRuntime jsRuntime, RemoteNavigationManager navigationManager, CircuitHandler[] circuitHandlers, ILogger logger) : base(circuitId, scope, options, client, renderer, descriptors, jsRuntime, navigationManager, circuitHandlers, logger) { }
public async ValueTask <CircuitHost> CreateCircuitHostAsync( IReadOnlyList <ComponentDescriptor> components, CircuitClientProxy client, string baseUri, string uri, ClaimsPrincipal user, IPersistentComponentStateStore store) { var scope = _scopeFactory.CreateAsyncScope(); var jsRuntime = (RemoteJSRuntime)scope.ServiceProvider.GetRequiredService <IJSRuntime>(); jsRuntime.Initialize(client); var navigationManager = (RemoteNavigationManager)scope.ServiceProvider.GetRequiredService <NavigationManager>(); var navigationInterception = (RemoteNavigationInterception)scope.ServiceProvider.GetRequiredService <INavigationInterception>(); if (client.Connected) { navigationManager.AttachJsRuntime(jsRuntime); navigationManager.Initialize(baseUri, uri); navigationInterception.AttachJSRuntime(jsRuntime); } else { navigationManager.Initialize(baseUri, uri); } var appLifetime = scope.ServiceProvider.GetRequiredService <ComponentStatePersistenceManager>(); await appLifetime.RestoreStateAsync(store); var jsComponentInterop = new CircuitJSComponentInterop(_options); var renderer = new RemoteRenderer( scope.ServiceProvider, _loggerFactory, _options, client, _loggerFactory.CreateLogger <RemoteRenderer>(), jsRuntime, jsComponentInterop); var circuitHandlers = scope.ServiceProvider.GetServices <CircuitHandler>() .OrderBy(h => h.Order) .ToArray(); var circuitHost = new CircuitHost( _circuitIdFactory.CreateCircuitId(), scope, _options, client, renderer, components, jsRuntime, navigationManager, circuitHandlers, _loggerFactory.CreateLogger <CircuitHost>()); Log.CreatedCircuit(_logger, circuitHost); // Initialize per - circuit data that services need (circuitHost.Services.GetRequiredService <ICircuitAccessor>() as DefaultCircuitAccessor).Circuit = circuitHost.Circuit; circuitHost.SetCircuitUser(user); return(circuitHost); }
public TestRemoteRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, IJSRuntime jsRuntime, CircuitClientProxy client, ILogger logger) : base(serviceProvider, loggerFactory, options, jsRuntime, client, logger) { }
public async ValueTask <string> StartCircuit(string baseUri, string uri) { var circuitHost = GetCircuit(); if (circuitHost != null) { // This is an error condition and an attempt to bind multiple circuits to a single connection. // We can reject this and terminate the connection. Log.CircuitAlreadyInitialized(_logger, circuitHost.CircuitId); await NotifyClientError(Clients.Caller, $"The circuit host '{circuitHost.CircuitId}' has already been initialized."); Context.Abort(); return(null); } if (baseUri == null || uri == null || !Uri.IsWellFormedUriString(baseUri, UriKind.Absolute) || !Uri.IsWellFormedUriString(uri, UriKind.Absolute)) { // We do some really minimal validation here to prevent obviously wrong data from getting in // without duplicating too much logic. // // This is an error condition attempting to initialize the circuit in a way that would fail. // We can reject this and terminate the connection. Log.InvalidInputData(_logger); await NotifyClientError(Clients.Caller, $"The uris provided are invalid."); Context.Abort(); return(null); } // From this point, we can try to actually initialize the circuit. if (DefaultCircuitFactory.ResolveComponentMetadata(Context.GetHttpContext()).Count == 0) { // No components preregistered so return. This is totally normal if the components were prerendered. Log.NoComponentsRegisteredInEndpoint(_logger, Context.GetHttpContext().GetEndpoint()?.DisplayName); return(null); } try { var circuitClient = new CircuitClientProxy(Clients.Caller, Context.ConnectionId); circuitHost = _circuitFactory.CreateCircuitHost( Context.GetHttpContext(), circuitClient, baseUri, uri, Context.User); // Fire-and-forget the initialization process, because we can't block the // SignalR message loop (we'd get a deadlock if any of the initialization // logic relied on receiving a subsequent message from SignalR), and it will // take care of its own errors anyway. _ = circuitHost.InitializeAsync(Context.ConnectionAborted); // 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); return(circuitHost.CircuitId); } catch (Exception ex) { // If the circuit fails to initialize synchronously we can notify the client immediately // and shut down the connection. Log.CircuitInitializationFailed(_logger, ex); await NotifyClientError(Clients.Caller, "The circuit failed to initialize."); Context.Abort(); return(null); } }
public TestRemoteRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, CircuitOptions options, CircuitClientProxy client, ILogger logger) : base(serviceProvider, loggerFactory, options, client, logger, CreateJSRuntime(options), new CircuitJSComponentInterop(options)) { }
public async ValueTask <string> StartCircuit(string baseUri, string uri, string serializedComponentRecords) { var circuitHost = GetCircuit(); if (circuitHost != null) { // This is an error condition and an attempt to bind multiple circuits to a single connection. // We can reject this and terminate the connection. Log.CircuitAlreadyInitialized(_logger, circuitHost.CircuitId); await NotifyClientError(Clients.Caller, $"The circuit host '{circuitHost.CircuitId}' has already been initialized."); Context.Abort(); return(null); } if (baseUri == null || uri == null || !Uri.IsWellFormedUriString(baseUri, UriKind.Absolute) || !Uri.IsWellFormedUriString(uri, UriKind.Absolute)) { // We do some really minimal validation here to prevent obviously wrong data from getting in // without duplicating too much logic. // // This is an error condition attempting to initialize the circuit in a way that would fail. // We can reject this and terminate the connection. Log.InvalidInputData(_logger); await NotifyClientError(Clients.Caller, $"The uris provided are invalid."); Context.Abort(); return(null); } if (!_serverComponentSerializer.TryDeserializeComponentDescriptorCollection(serializedComponentRecords, out var components)) { Log.InvalidInputData(_logger); await NotifyClientError(Clients.Caller, $"The list of component records is not valid."); Context.Abort(); return(null); } try { var circuitClient = new CircuitClientProxy(Clients.Caller, Context.ConnectionId); circuitHost = _circuitFactory.CreateCircuitHost( components, circuitClient, baseUri, uri, Context.User); // Fire-and-forget the initialization process, because we can't block the // SignalR message loop (we'd get a deadlock if any of the initialization // logic relied on receiving a subsequent message from SignalR), and it will // take care of its own errors anyway. _ = circuitHost.InitializeAsync(Context.ConnectionAborted); // 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); // Returning the secret here so the client can reconnect. // // Logging the secret and circuit ID here so we can associate them with just logs (if TRACE level is on). Log.CreatedCircuit(_logger, circuitHost.CircuitId, circuitHost.CircuitId.Secret, Context.ConnectionId); return(circuitHost.CircuitId.Secret); } catch (Exception ex) { // If the circuit fails to initialize synchronously we can notify the client immediately // and shut down the connection. Log.CircuitInitializationFailed(_logger, ex); await NotifyClientError(Clients.Caller, "The circuit failed to initialize."); Context.Abort(); return(null); } }
private TestRemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, CircuitClientProxy circuitClient = null) { var jsRuntime = new Mock <IJSRuntime>(); jsRuntime.Setup(r => r.InvokeAsync <object>( "Blazor._internal.attachRootComponentToElement", It.IsAny <int>(), It.IsAny <string>(), It.IsAny <int>())) .ReturnsAsync(Task.FromResult <object>(null)); return(new TestRemoteRenderer( serviceProvider, NullLoggerFactory.Instance, new CircuitOptions(), jsRuntime.Object, circuitClient ?? new CircuitClientProxy(), NullLogger.Instance)); }
public override CircuitHost CreateCircuitHost(HttpContext httpContext, CircuitClientProxy client, string uriAbsolute, string baseUriAbsolute) { return(TestCircuitHost.Create(Guid.NewGuid().ToString(), MockServiceScope.Object)); }