From 74c09470c61e09896e56a43c0bfcb051200f22e0 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 14 Aug 2019 13:01:53 +0200 Subject: [PATCH] [Blazor][Fixes #12056] Avoid producing warnings for bad inputs. * Does not produce warnings when receiving invalid event arguments. * Does not produce warnings when receiving an invalid event handler id. * Does not produce warnings when receiving an ack for an unknown batch. --- .../Components/src/Rendering/Renderer.cs | 2 +- .../Components/test/RendererTest.cs | 2 +- .../Server/src/Circuits/CircuitHost.cs | 226 ++++++++++-------- .../Server/src/Circuits/RemoteRenderer.cs | 10 +- .../src/Circuits/RemoteRendererException.cs | 21 -- src/Components/Server/src/ComponentHub.cs | 28 +-- .../test/Circuits/RemoteRendererTest.cs | 25 +- src/Components/Shared/src/WebEventData.cs | 70 +++--- .../ComponentHubReliabilityTest.cs | 143 ++++++++++- .../InteropReliabilityTests.cs | 12 +- .../BasicTestApp/CounterComponent.razor | 2 +- .../ComponentsApp.Server/Startup.cs | 1 - 12 files changed, 331 insertions(+), 211 deletions(-) delete mode 100644 src/Components/Server/src/Circuits/RemoteRendererException.cs diff --git a/src/Components/Components/src/Rendering/Renderer.cs b/src/Components/Components/src/Rendering/Renderer.cs index fba917bf05..208630f141 100644 --- a/src/Components/Components/src/Rendering/Renderer.cs +++ b/src/Components/Components/src/Rendering/Renderer.cs @@ -213,7 +213,7 @@ namespace Microsoft.AspNetCore.Components.Rendering if (!_eventBindings.TryGetValue(eventHandlerId, out var callback)) { - throw new ArgumentException($"There is no event handler with ID {eventHandlerId}"); + throw new ArgumentException($"There is no event handler associated with this event. EventId: '{eventHandlerId}'.", nameof(eventHandlerId)); } Log.HandlingEvent(_logger, eventHandlerId, eventArgs); diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index ccca6b8006..71ebf86351 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -2883,7 +2883,7 @@ namespace Microsoft.AspNetCore.Components.Test { return renderer.DispatchEventAsync(eventHandlerId, new EventArgs()); }); - Assert.Equal($"There is no event handler with ID {eventHandlerId}", ex.Message); + Assert.Contains($"There is no event handler associated with this event. EventId: '{eventHandlerId}'.", ex.Message); Assert.Equal(2, numEventsFired); } diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index dc3a866dd0..97a2e3f3c7 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -27,24 +27,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private bool _disposed; /// - /// Sets the current . + /// Sets the for the current excution context. /// - /// The . - /// - /// Calling will store related values such as the - /// and - /// in the local execution context. Application code should not need to call this method, - /// it is primarily used by the Server-Side Components infrastructure. - /// - public static void SetCurrentCircuitHost(CircuitHost circuitHost) - { - if (circuitHost is null) - { - throw new ArgumentNullException(nameof(circuitHost)); - } - - JSInterop.JSRuntime.SetCurrentJSRuntime(circuitHost.JSRuntime); - } + public void SetCurrentJSRuntime() => JSInterop.JSRuntime.SetCurrentJSRuntime(JSRuntime); // This event is fired when there's an unrecoverable exception coming from the circuit, and // it need so be torn down. The registry listens to this even so that the circuit can @@ -116,7 +101,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits try { - SetCurrentCircuitHost(this); + SetCurrentJSRuntime(); _initialized = true; // We're ready to accept incoming JSInterop calls from here on await OnCircuitOpenedAsync(cancellationToken); @@ -342,6 +327,28 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits } } + // Called by the client when it completes rendering a batch. + // OnRenderCompletedAsync is used in a fire-and-forget context, so it's responsible for its own + // error handling. + public async Task OnRenderCompletedAsync(long renderId, string errorMessageOrNull) + { + AssertInitialized(); + AssertNotDisposed(); + + try + { + _ = Renderer.OnRenderCompletedAsync(renderId, errorMessageOrNull); + } + catch (Exception e) + { + // Captures sync exceptions when invoking OnRenderCompletedAsync. + // An exception might be throw synchronously when we receive an ack for a batch we never produced. + Log.OnRenderCompletedFailed(_logger, renderId, CircuitId, e); + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(e, $"Failed to complete render batch '{renderId}'.")); + UnhandledException(this, new UnhandledExceptionEventArgs(e, isTerminating: false)); + } + } + // BeginInvokeDotNetFromJS is used in a fire-and-forget context, so it's responsible for its own // error handling. public async Task BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) @@ -353,7 +360,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { await Renderer.Dispatcher.InvokeAsync(() => { - SetCurrentCircuitHost(this); + SetCurrentJSRuntime(); Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId); DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); }); @@ -363,10 +370,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // We don't expect any of this code to actually throw, because DotNetDispatcher.BeginInvoke doesn't throw // however, we still want this to get logged if we do. Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex); - if (Client.Connected) - { - await NotifyClientError(Client, "Interop call failed."); - } + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Interop call failed.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } } @@ -382,7 +386,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { await Renderer.Dispatcher.InvokeAsync(() => { - SetCurrentCircuitHost(this); + SetCurrentJSRuntime(); if (!succeded) { // We can log the arguments here because it is simply the JS error with the call stack. @@ -401,10 +405,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // An error completing JS interop means that the user sent invalid data, a well-behaved // client won't do this. Log.EndInvokeDispatchException(_logger, ex); - if (Client.Connected) - { - await NotifyClientError(Client, "Invalid interop arguments."); - } + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Invalid interop arguments.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } } @@ -419,17 +420,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits WebEventData webEventData; try { - AssertInitialized(); webEventData = WebEventData.Parse(eventDescriptorJson, eventArgsJson); } catch (Exception ex) { // Invalid event data is fatal. We expect a well-behaved client to send valid JSON. Log.DispatchEventFailedToParseEventData(_logger, ex); - if (Client.Connected) - { - await NotifyClientError(Client, "Invalid event data."); - } + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Bad input data.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); return; } @@ -438,7 +435,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { await Renderer.Dispatcher.InvokeAsync(() => { - SetCurrentCircuitHost(this); + SetCurrentJSRuntime(); return Renderer.DispatchEventAsync( webEventData.EventHandlerId, webEventData.EventFieldInfo, @@ -450,10 +447,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // A failure in dispatching an event means that it was an attempt to use an invalid event id. // A well-behaved client won't do this. Log.DispatchEventFailedToDispatchEvent(_logger, webEventData.EventHandlerId.ToString(), ex); - if (Client.Connected) - { - await NotifyClientError(Client, "Failed to dispatch event."); - } + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Failed to dispatch event.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } } @@ -469,7 +463,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits { await Renderer.Dispatcher.InvokeAsync(() => { - SetCurrentCircuitHost(this); + SetCurrentJSRuntime(); Log.LocationChange(_logger, uri, CircuitId); var navigationManager = (RemoteNavigationManager)Services.GetRequiredService(); navigationManager.NotifyLocationChanged(uri, intercepted); @@ -491,7 +485,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // LocationChangeException means that it failed in user-code. Treat this like an unhandled // exception in user-code. Log.LocationChangeFailedInCircuit(_logger, uri, CircuitId, nex); - await ReportUnhandledException(nex); + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(nex, "Location change failed.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(nex, isTerminating: false)); } catch (Exception ex) @@ -499,10 +493,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // Any other exception means that it failed validation, or inside the NavigationManager. Treat // this like bad data. Log.LocationChangeFailed(_logger, uri, CircuitId, ex); - if (Client.Connected) - { - await NotifyClientError(Client, $"Location change to {uri} failed."); - } + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, $"Location change to '{uri}' failed.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } } @@ -527,7 +518,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits // Dispatch any buffered renders we accumulated during a disconnect. // Note that while the rendering is async, we cannot await it here. The Task returned by ProcessBufferedRenderBatches relies on - // OnRenderCompleted to be invoked to complete, and SignalR does not allow concurrent hub method invocations. + // OnRenderCompletedAsync to be invoked to complete, and SignalR does not allow concurrent hub method invocations. _ = Renderer.Dispatcher.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches()); } @@ -566,39 +557,50 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private async Task ReportUnhandledException(Exception exception) { Log.CircuitUnhandledException(_logger, CircuitId, exception); + + await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(exception), exception); + } + + private string GetClientErrorMessage(Exception exception, string additionalInformation = null) + { + if (_options.DetailedErrors) + { + return exception.ToString(); + } + else + { + return + $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + + $"detailed exceptions in '{typeof(CircuitOptions).Name}.{nameof(CircuitOptions.DetailedErrors)}'. {additionalInformation}"; + } + } + + // exception is only populated when either the renderer or the synchronization context signal exceptions. + // In other cases it is null and should never be sent to the client. + // error contains the information to send to the client. + private async Task TryNotifyClientErrorAsync(IClientProxy client, string error, Exception exception = null) + { if (!Client.Connected) { - _logger.LogDebug("Client is disconnected YO."); + Log.UnhandledExceptionClientDisconnected( + _logger, + CircuitId, + exception); return; } try { - if (_options.DetailedErrors) - { - await NotifyClientError(Client, exception.ToString()); - } - else - { - var message = - $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + - $"detailed exceptions in '{typeof(CircuitOptions).Name}.{nameof(CircuitOptions.DetailedErrors)}'"; - await NotifyClientError(Client, message); - } + Log.CircuitTransmittingClientError(_logger, CircuitId); + await client.SendAsync("JS.Error", error); + Log.CircuitTransmittedClientErrorSuccess(_logger, CircuitId); } catch (Exception ex) { - Log.CircuitUnhandledExceptionFailed(_logger, CircuitId, ex); + Log.CircuitTransmitErrorFailed(_logger, CircuitId, ex); } } - private async Task NotifyClientError(IClientProxy client, string error) - { - _logger.LogDebug("About to notify of an error"); - await client.SendAsync("JS.Error", error); - _logger.LogDebug("Completed notify of an error"); - } - private static class Log { private static readonly Action _intializationStarted; @@ -613,7 +615,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private static readonly Action _onCircuitClosed; private static readonly Action _circuitHandlerFailed; private static readonly Action _circuitUnhandledException; - private static readonly Action _circuitUnhandledExceptionFailed; + private static readonly Action _circuitTransmittingClientError; + private static readonly Action _circuitTransmittedClientErrorSuccess; + private static readonly Action _circuitTransmitErrorFailed; + private static readonly Action _unhandledExceptionClientDisconnected; private static readonly Action _beginInvokeDotNetStatic; private static readonly Action _beginInvokeDotNetInstance; @@ -628,6 +633,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits private static readonly Action _locationChangeSucceeded; private static readonly Action _locationChangeFailed; private static readonly Action _locationChangeFailedInCircuit; + private static readonly Action _onRenderCompletedFailed; private static class EventIds { @@ -644,7 +650,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public static readonly EventId OnCircuitClosed = new EventId(109, "OnCircuitClosed"); public static readonly EventId CircuitHandlerFailed = new EventId(110, "CircuitHandlerFailed"); public static readonly EventId CircuitUnhandledException = new EventId(111, "CircuitUnhandledException"); - public static readonly EventId CircuitUnhandledExceptionFailed = new EventId(112, "CircuitUnhandledExceptionFailed"); + public static readonly EventId CircuitTransmittingClientError = new EventId(112, "CircuitTransmittingClientError"); + public static readonly EventId CircuitTransmittedClientErrorSuccess = new EventId(113, "CircuitTransmittedClientErrorSuccess"); + public static readonly EventId CircuitTransmitErrorFailed = new EventId(114, "CircuitTransmitErrorFailed"); + public static readonly EventId UnhandledExceptionClientDisconnected = new EventId(115, "UnhandledExceptionClientDisconnected"); // 200s used for interactive stuff public static readonly EventId DispatchEventFailedToParseEventData = new EventId(200, "DispatchEventFailedToParseEventData"); @@ -659,6 +668,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public static readonly EventId LocationChangeSucceded = new EventId(209, "LocationChangeSucceeded"); public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed"); public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit"); + public static readonly EventId OnRenderCompletedFailed = new EventId(212, " OnRenderCompletedFailed"); } static Log() @@ -666,87 +676,102 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _intializationStarted = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationFailed, - "Circuit initialization started"); + "Circuit initialization started."); _intializationSucceded = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationFailed, - "Circuit initialization succeeded"); + "Circuit initialization succeeded."); _intializationFailed = LoggerMessage.Define( LogLevel.Debug, EventIds.InitializationFailed, - "Circuit initialization failed"); + "Circuit initialization failed."); _disposeStarted = LoggerMessage.Define( LogLevel.Debug, EventIds.DisposeStarted, - "Disposing circuit {CircuitId} started"); + "Disposing circuit '{CircuitId}' started."); _disposeSucceded = LoggerMessage.Define( LogLevel.Debug, EventIds.DisposeSucceeded, - "Disposing circuit {CircuitId} succeded"); + "Disposing circuit '{CircuitId}' succeded."); _disposeFailed = LoggerMessage.Define( LogLevel.Debug, EventIds.DisposeFailed, - "Disposing circuit {CircuitId} failed"); + "Disposing circuit '{CircuitId}' failed."); _onCircuitOpened = LoggerMessage.Define( LogLevel.Debug, EventIds.OnCircuitOpened, - "Opening circuit with id {CircuitId}."); + "Opening circuit with id '{CircuitId}'."); _onConnectionUp = LoggerMessage.Define( LogLevel.Debug, EventIds.OnConnectionUp, - "Circuit id {CircuitId} connected using connection {ConnectionId}."); + "Circuit id '{CircuitId}' connected using connection '{ConnectionId}'."); _onConnectionDown = LoggerMessage.Define( LogLevel.Debug, EventIds.OnConnectionDown, - "Circuit id {CircuitId} disconnected from connection {ConnectionId}."); + "Circuit id '{CircuitId}' disconnected from connection '{ConnectionId}'."); _onCircuitClosed = LoggerMessage.Define( LogLevel.Debug, EventIds.OnCircuitClosed, - "Closing circuit with id {CircuitId}."); + "Closing circuit with id '{CircuitId}'."); _circuitHandlerFailed = LoggerMessage.Define( LogLevel.Error, EventIds.CircuitHandlerFailed, "Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}"); - _circuitUnhandledException = LoggerMessage.Define( - LogLevel.Error, - EventIds.CircuitUnhandledException, - "Unhandled exception in circuit {CircuitId}"); + _circuitUnhandledException = LoggerMessage.Define( + LogLevel.Error, + EventIds.CircuitUnhandledException, + "Unhandled exception in circuit '{CircuitId}'."); - _circuitUnhandledExceptionFailed = LoggerMessage.Define( - LogLevel.Debug, - EventIds.CircuitUnhandledExceptionFailed, - "Failed to transmit exception to client in circuit {CircuitId}"); + _circuitTransmittingClientError = LoggerMessage.Define( + LogLevel.Debug, + EventIds.CircuitTransmittingClientError, + "About to notify client of an error in circuit '{CircuitId}'."); + + _circuitTransmittedClientErrorSuccess = LoggerMessage.Define( + LogLevel.Debug, + EventIds.CircuitTransmittedClientErrorSuccess, + "Successfully transmitted error to client in circuit '{CircuitId}'."); + + _circuitTransmitErrorFailed = LoggerMessage.Define( + LogLevel.Debug, + EventIds.CircuitTransmitErrorFailed, + "Failed to transmit exception to client in circuit '{CircuitId}'."); + + _unhandledExceptionClientDisconnected = LoggerMessage.Define( + LogLevel.Debug, + EventIds.UnhandledExceptionClientDisconnected, + "An exception ocurred on the circuit host '{CircuitId}' while the client is disconnected."); _beginInvokeDotNetStatic = LoggerMessage.Define( LogLevel.Debug, EventIds.BeginInvokeDotNet, - "Invoking static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'"); + "Invoking static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'."); _beginInvokeDotNetInstance = LoggerMessage.Define( LogLevel.Debug, EventIds.BeginInvokeDotNet, - "Invoking instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'"); + "Invoking instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'."); _beginInvokeDotNetStaticFailed = LoggerMessage.Define( LogLevel.Debug, EventIds.BeginInvokeDotNetFailed, - "Failed to invoke static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'"); + "Failed to invoke static method with identifier '{MethodIdentifier}' on assembly '{Assembly}' with callback id '{CallId}'."); _beginInvokeDotNetInstanceFailed = LoggerMessage.Define( LogLevel.Debug, EventIds.BeginInvokeDotNetFailed, - "Failed to invoke instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'"); + "Failed to invoke instance method '{MethodIdentifier}' on instance '{DotNetObjectId}' with callback id '{CallId}'."); _endInvokeDispatchException = LoggerMessage.Define( LogLevel.Debug, @@ -776,25 +801,30 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits _locationChange = LoggerMessage.Define( LogLevel.Debug, EventIds.LocationChange, - "Location changing to {URI} in circuit {CircuitId}."); + "Location changing to {URI} in circuit '{CircuitId}'."); _locationChangeSucceeded = LoggerMessage.Define( LogLevel.Debug, EventIds.LocationChangeSucceded, - "Location change to {URI} in circuit {CircuitId} succeded."); + "Location change to '{URI}' in circuit '{CircuitId}' succeded."); _locationChangeFailed = LoggerMessage.Define( LogLevel.Debug, EventIds.LocationChangeFailed, - "Location change to {URI} in circuit {CircuitId} failed."); + "Location change to '{URI}' in circuit '{CircuitId}' failed."); _locationChangeFailedInCircuit = LoggerMessage.Define( LogLevel.Error, EventIds.LocationChangeFailed, - "Location change to {URI} in circuit {CircuitId} failed."); + "Location change to '{URI}' in circuit '{CircuitId}' failed."); + + _onRenderCompletedFailed = LoggerMessage.Define( + LogLevel.Debug, + EventIds.OnRenderCompletedFailed, + "Failed to complete render batch '{RenderId}' in circuit host '{CircuitId}'."); } - public static void InitializationStarted(ILogger logger) =>_intializationStarted(logger, null); + public static void InitializationStarted(ILogger logger) => _intializationStarted(logger, null); public static void InitializationSucceeded(ILogger logger) => _intializationSucceded(logger, null); public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception); public static void DisposeStarted(ILogger logger, string circuitId) => _disposeStarted(logger, circuitId, null); @@ -802,7 +832,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public static void DisposeFailed(ILogger logger, string circuitId, Exception exception) => _disposeFailed(logger, circuitId, exception); public static void CircuitOpened(ILogger logger, string circuitId) => _onCircuitOpened(logger, circuitId, null); public static void ConnectionUp(ILogger logger, string circuitId, string connectionId) => _onConnectionUp(logger, circuitId, connectionId, null); - public static void ConnectionDown(ILogger logger, string circuitId, string connectionId) => _onConnectionDown(logger, circuitId, connectionId, null); + public static void ConnectionDown(ILogger logger, string circuitId, string connectionId) => _onConnectionDown(logger, circuitId, connectionId, null); public static void CircuitClosed(ILogger logger, string circuitId) => _onCircuitClosed(logger, circuitId, null); public static void CircuitHandlerFailed(ILogger logger, CircuitHandler handler, string handlerMethod, Exception exception) @@ -816,7 +846,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits } public static void CircuitUnhandledException(ILogger logger, string circuitId, Exception exception) => _circuitUnhandledException(logger, circuitId, exception); - public static void CircuitUnhandledExceptionFailed(ILogger logger, string circuitId, Exception exception) => _circuitUnhandledExceptionFailed(logger, circuitId, exception); + public static void CircuitTransmitErrorFailed(ILogger logger, string circuitId, Exception exception) => _circuitTransmitErrorFailed(logger, circuitId, exception); public static void EndInvokeDispatchException(ILogger logger, Exception ex) => _endInvokeDispatchException(logger, ex); public static void EndInvokeJSFailed(ILogger logger, long asyncHandle, string arguments) => _endInvokeJSFailed(logger, asyncHandle, arguments, null); public static void EndInvokeJSSucceeded(ILogger logger, long asyncCall) => _endInvokeJSSucceeded(logger, asyncCall, null); @@ -851,6 +881,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits public static void LocationChangeSucceeded(ILogger logger, string uri, string circuitId) => _locationChangeSucceeded(logger, uri, circuitId, null); public static void LocationChangeFailed(ILogger logger, string uri, string circuitId, Exception exception) => _locationChangeFailed(logger, uri, circuitId, exception); public static void LocationChangeFailedInCircuit(ILogger logger, string uri, string circuitId, Exception exception) => _locationChangeFailedInCircuit(logger, uri, circuitId, exception); + public static void UnhandledExceptionClientDisconnected(ILogger logger, string circuitId, Exception exception) => _unhandledExceptionClientDisconnected(logger, circuitId, exception); + public static void CircuitTransmittingClientError(ILogger logger, string circuitId) => _circuitTransmittingClientError(logger, circuitId, null); + public static void CircuitTransmittedClientErrorSuccess(ILogger logger, string circuitId) => _circuitTransmittedClientErrorSuccess(logger, circuitId, null); + public static void OnRenderCompletedFailed(ILogger logger, long renderId, string circuitId, Exception e) => _onRenderCompletedFailed(logger, renderId, circuitId, e); } } } diff --git a/src/Components/Server/src/Circuits/RemoteRenderer.cs b/src/Components/Server/src/Circuits/RemoteRenderer.cs index 2a1ded9244..a53ee90de3 100644 --- a/src/Components/Server/src/Circuits/RemoteRenderer.cs +++ b/src/Components/Server/src/Circuits/RemoteRenderer.cs @@ -222,7 +222,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering // disposed. } - public Task OnRenderCompleted(long incomingBatchId, string errorMessageOrNull) + public Task OnRenderCompletedAsync(long incomingBatchId, string errorMessageOrNull) { if (_disposing) { @@ -273,9 +273,9 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering if (lastBatchId < incomingBatchId) { - HandleException( - new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'.")); - return Task.CompletedTask; + // This exception is due to a bad client input, so we mark it as such to prevent loging it as a warning and + // flooding the logs with warnings. + throw new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'."); } // Normally we will not have pending renders, but it might happen that we reached the limit of @@ -320,7 +320,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering } else { - pendingRenderInfo.TrySetException(new RemoteRendererException(errorMessageOrNull)); + pendingRenderInfo.TrySetException(new InvalidOperationException(errorMessageOrNull)); } } diff --git a/src/Components/Server/src/Circuits/RemoteRendererException.cs b/src/Components/Server/src/Circuits/RemoteRendererException.cs deleted file mode 100644 index 75edc46ad7..0000000000 --- a/src/Components/Server/src/Circuits/RemoteRendererException.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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; - -namespace Microsoft.AspNetCore.Components.Web.Rendering -{ - /// - /// Represents an exception related to remote rendering. - /// - internal class RemoteRendererException : Exception - { - /// - /// Constructs an instance of . - /// - /// The exception message. - public RemoteRendererException(string message) : base(message) - { - } - } -} diff --git a/src/Components/Server/src/ComponentHub.cs b/src/Components/Server/src/ComponentHub.cs index a2ddc909ca..7ba95300de 100644 --- a/src/Components/Server/src/ComponentHub.cs +++ b/src/Components/Server/src/ComponentHub.cs @@ -6,8 +6,6 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -203,7 +201,7 @@ namespace Microsoft.AspNetCore.Components.Server } Log.ReceivedConfirmationForBatch(_logger, renderId); - _ = circuitHost.Renderer.OnRenderCompleted(renderId, errorMessageOrNull); + _ = circuitHost.OnRenderCompletedAsync(renderId, errorMessageOrNull); } public async ValueTask OnLocationChanged(string uri, bool intercepted) @@ -281,29 +279,15 @@ namespace Microsoft.AspNetCore.Components.Server private static readonly Action _circuitHostShutdown = LoggerMessage.Define(LogLevel.Debug, new EventId(6, "CircuitHostShutdown"), "Call to '{CallSite}' received after the circuit was shut down"); - private static readonly Action _circuitTerminatedGracefully = - LoggerMessage.Define(LogLevel.Debug, new EventId(7, "CircuitTerminatedGracefully"), "Circuit '{CircuitId}' terminated gracefully"); - private static readonly Action _invalidInputData = - LoggerMessage.Define(LogLevel.Debug, new EventId(8, "InvalidInputData"), "Call to '{CallSite}' received invalid input data"); + LoggerMessage.Define(LogLevel.Debug, new EventId(7, "InvalidInputData"), "Call to '{CallSite}' received invalid input data"); private static readonly Action _circuitInitializationFailed = - LoggerMessage.Define(LogLevel.Debug, new EventId(9, "CircuitInitializationFailed"), "Circuit initialization failed"); + LoggerMessage.Define(LogLevel.Debug, new EventId(8, "CircuitInitializationFailed"), "Circuit initialization failed"); - public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName) - { - _noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null); - } + public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName) => _noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null); - public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) - { - _receivedConfirmationForBatch(logger, batchId, null); - } - - public static void UnhandledExceptionInCircuit(ILogger logger, string circuitId, Exception exception) - { - _unhandledExceptionInCircuit(logger, circuitId, exception); - } + public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) => _receivedConfirmationForBatch(logger, batchId, null); public static void CircuitAlreadyInitialized(ILogger logger, string circuitId) => _circuitAlreadyInitialized(logger, circuitId, null); @@ -311,8 +295,6 @@ namespace Microsoft.AspNetCore.Components.Server public static void CircuitHostShutdown(ILogger logger, [CallerMemberName] string callSite = "") => _circuitHostShutdown(logger, callSite, null); - public static void CircuitTerminatedGracefully(ILogger logger, string circuitId) => _circuitTerminatedGracefully(logger, circuitId, null); - public static void InvalidInputData(ILogger logger, [CallerMemberName] string callSite = "") => _invalidInputData(logger, callSite, null); public static void CircuitInitializationFailed(ILogger logger, Exception exception) => _circuitInitializationFailed(logger, exception); diff --git a/src/Components/Server/test/Circuits/RemoteRendererTest.cs b/src/Components/Server/test/Circuits/RemoteRendererTest.cs index 386ea99f28..11f772e3ca 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering component.TriggerRender(); } - await renderer.OnRenderCompleted(2, null); + await renderer.OnRenderCompletedAsync(2, null); // Assert Assert.Equal(9, renderer._unacknowledgedRenderBatches.Count); @@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering } Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count); - await renderer.OnRenderCompleted(2, null); + await renderer.OnRenderCompletedAsync(2, null); // Assert Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count); @@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); - _ = renderer.OnRenderCompleted(2, null); + _ = renderer.OnRenderCompletedAsync(2, null); @event.Reset(); firstBatchTCS.SetResult(null); @@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering foreach (var id in renderIds.ToArray()) { - _ = renderer.OnRenderCompleted(id, null); + _ = renderer.OnRenderCompletedAsync(id, null); } secondBatchTCS.SetResult(null); @@ -234,14 +234,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering }; // Receive the ack for the intial batch - _ = renderer.OnRenderCompleted(2, null); + _ = renderer.OnRenderCompletedAsync(2, null); // Receive the ack for the second batch - _ = renderer.OnRenderCompleted(3, null); + _ = renderer.OnRenderCompletedAsync(3, null); firstBatchTCS.SetResult(null); secondBatchTCS.SetResult(null); // Repeat the ack for the third batch - _ = renderer.OnRenderCompleted(3, null); + _ = renderer.OnRenderCompletedAsync(3, null); // Assert Assert.Empty(exceptions); @@ -297,14 +297,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering }; // Receive the ack for the intial batch - _ = renderer.OnRenderCompleted(2, null); + _ = renderer.OnRenderCompletedAsync(2, null); // Receive the ack for the second batch - _ = renderer.OnRenderCompleted(2, null); + _ = renderer.OnRenderCompletedAsync(2, null); firstBatchTCS.SetResult(null); secondBatchTCS.SetResult(null); // Repeat the ack for the third batch - _ = renderer.OnRenderCompleted(3, null); + _ = renderer.OnRenderCompletedAsync(3, null); // Assert Assert.Empty(exceptions); @@ -358,7 +358,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering }; // Pretend that we missed the ack for the initial batch - _ = renderer.OnRenderCompleted(3, null); + _ = renderer.OnRenderCompletedAsync(3, null); firstBatchTCS.SetResult(null); secondBatchTCS.SetResult(null); @@ -414,12 +414,11 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering exceptions.Add(e); }; - _ = renderer.OnRenderCompleted(4, null); + var exception = await Assert.ThrowsAsync(() => renderer.OnRenderCompletedAsync(4, null)); firstBatchTCS.SetResult(null); secondBatchTCS.SetResult(null); // Assert - var exception = Assert.Single(exceptions); Assert.Equal( "Received an acknowledgement for batch with id '4' when the last batch produced was '3'.", exception.Message); diff --git a/src/Components/Shared/src/WebEventData.cs b/src/Components/Shared/src/WebEventData.cs index dbcf8c5aa9..07aafbddf2 100644 --- a/src/Components/Shared/src/WebEventData.cs +++ b/src/Components/Shared/src/WebEventData.cs @@ -11,11 +11,20 @@ namespace Microsoft.AspNetCore.Components.Web { // This class represents the second half of parsing incoming event data, // once the type of the eventArgs becomes known. - public static WebEventData Parse(string eventDescriptorJson, string eventArgsJson) { + WebEventDescriptor eventDescriptor; + try + { + eventDescriptor = Deserialize(eventDescriptorJson); + } + catch (Exception e) + { + throw new InvalidOperationException("Error parsing the event descriptor", e); + } + return Parse( - Deserialize(eventDescriptorJson), + eventDescriptor, eventArgsJson); } @@ -25,7 +34,7 @@ namespace Microsoft.AspNetCore.Components.Web eventDescriptor.BrowserRendererId, eventDescriptor.EventHandlerId, InterpretEventFieldInfo(eventDescriptor.EventFieldInfo), - ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson)); + ParseEventArgsJson(eventDescriptor.EventHandlerId, eventDescriptor.EventArgsType, eventArgsJson)); } private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs) @@ -44,43 +53,34 @@ namespace Microsoft.AspNetCore.Components.Web public EventArgs EventArgs { get; } - private static EventArgs ParseEventArgsJson(string eventArgsType, string eventArgsJson) + private static EventArgs ParseEventArgsJson(ulong eventHandlerId, string eventArgsType, string eventArgsJson) { - switch (eventArgsType) + try { - case "change": - return DeserializeChangeEventArgs(eventArgsJson); - case "clipboard": - return Deserialize(eventArgsJson); - case "drag": - return Deserialize(eventArgsJson); - case "error": - return Deserialize(eventArgsJson); - case "focus": - return Deserialize(eventArgsJson); - case "keyboard": - return Deserialize(eventArgsJson); - case "mouse": - return Deserialize(eventArgsJson); - case "pointer": - return Deserialize(eventArgsJson); - case "progress": - return Deserialize(eventArgsJson); - case "touch": - return Deserialize(eventArgsJson); - case "unknown": - return EventArgs.Empty; - case "wheel": - return Deserialize(eventArgsJson); - default: - throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType)); + return eventArgsType switch + { + "change" => DeserializeChangeEventArgs(eventArgsJson), + "clipboard" => Deserialize(eventArgsJson), + "drag" => Deserialize(eventArgsJson), + "error" => Deserialize(eventArgsJson), + "focus" => Deserialize(eventArgsJson), + "keyboard" => Deserialize(eventArgsJson), + "mouse" => Deserialize(eventArgsJson), + "pointer" => Deserialize(eventArgsJson), + "progress" => Deserialize(eventArgsJson), + "touch" => Deserialize(eventArgsJson), + "unknown" => EventArgs.Empty, + "wheel" => Deserialize(eventArgsJson), + _ => throw new InvalidOperationException($"Unsupported event type '{eventArgsType}'. EventId: '{eventHandlerId}'."), + }; + } + catch (Exception e) + { + throw new InvalidOperationException($"There was an error parsing the event arguments. EventId: '{eventHandlerId}'.", e); } } - private static T Deserialize(string json) - { - return JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options); - } + private static T Deserialize(string json) => JsonSerializer.Deserialize(json, JsonSerializerOptionsProvider.Options); private static EventFieldInfo InterpretEventFieldInfo(EventFieldInfo fieldInfo) { diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs index 2c579568c0..afb29e3a05 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ComponentHubReliabilityTest.cs @@ -6,9 +6,12 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Text.Json; using System.Threading.Tasks; using Ignitor; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -20,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { public class ComponentHubReliabilityTest : IClassFixture, IDisposable { - private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(10); + private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromSeconds(Debugger.IsAttached ? 60 : 10); private readonly AspNetSiteServerFixture _serverFixture; public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output) @@ -52,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests return new Exception(error + Environment.NewLine + logs); }; - _ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI. + _ = _serverFixture.RootUri; // this is needed for the side-effects of getting the URI. TestSink = _serverFixture.Host.Services.GetRequiredService(); TestSink.MessageLogged += LogMessages; } @@ -199,6 +202,118 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests Assert.Contains(Logs, l => (l.LogLevel, l.Message) == (LogLevel.Debug, "Call to 'DispatchBrowserEvent' received before the circuit host initialization")); } + private async Task GoToTestComponent(IList batches) + { + var rootUri = _serverFixture.RootUri; + Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir"), prerendered: false), "Couldn't connect to the app"); + Assert.Single(batches); + + await Client.SelectAsync("test-selector-select", "BasicTestApp.CounterComponent"); + Assert.Equal(2, batches.Count); + } + + [Fact] + public async Task DispatchingAnInvalidEventArgument_DoesNotProduceWarnings() + { + // Arrange + var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + + $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Bad input data."; + + var eventDescriptor = Serialize(new WebEventDescriptor() + { + BrowserRendererId = 0, + EventHandlerId = 3, + EventArgsType = "mouse", + }); + + await GoToTestComponent(Batches); + Assert.Equal(2, Batches.Count); + + // Act + await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync( + "DispatchBrowserEvent", + eventDescriptor, + "{sadfadsf]")); + + // Assert + var actualError = Assert.Single(Errors); + Assert.Equal(expectedError, actualError); + Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information); + Assert.Contains(Logs, l => (l.LogLevel, l.Exception?.Message) == (LogLevel.Debug, "There was an error parsing the event arguments. EventId: '3'.")); + } + + [Fact] + public async Task DispatchingAnInvalidEvent_DoesNotTriggerWarnings() + { + // Arrange + var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + + $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to dispatch event."; + + var eventDescriptor = Serialize(new WebEventDescriptor() + { + BrowserRendererId = 0, + EventHandlerId = 1990, + EventArgsType = "mouse", + }); + + var eventArgs = new UIMouseEventArgs + { + Type = "click", + Detail = 1, + ScreenX = 47, + ScreenY = 258, + ClientX = 47, + ClientY = 155, + }; + + await GoToTestComponent(Batches); + Assert.Equal(2, Batches.Count); + + // Act + await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync( + "DispatchBrowserEvent", + eventDescriptor, + Serialize(eventArgs))); + + // Assert + var actualError = Assert.Single(Errors); + Assert.Equal(expectedError, actualError); + Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information); + Assert.Contains(Logs, l => (l.LogLevel, l.Message, l.Exception?.Message) == + (LogLevel.Debug, + "There was an error dispatching the event '1990' to the application.", + "There is no event handler associated with this event. EventId: '1990'. (Parameter 'eventHandlerId')")); + } + + [Fact] + public async Task DispatchingAnInvalidRenderAcknowledgement_DoesNotTriggerWarnings() + { + // Arrange + var expectedError = $"There was an unhandled exception on the current circuit, so this circuit will be terminated. For more details turn on " + + $"detailed exceptions in 'CircuitOptions.DetailedErrors'. Failed to complete render batch '1846'."; + + await GoToTestComponent(Batches); + Assert.Equal(2, Batches.Count); + + Client.ConfirmRenderBatch = false; + await Client.ClickAsync("counter"); + + // Act + await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync( + "OnRenderCompleted", + 1846, + null)); + + // Assert + var actualError = Assert.Single(Errors); + Assert.Equal(expectedError, actualError); + Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information); + Assert.Contains(Logs, l => (l.LogLevel, l.Message, l.Exception?.Message) == + (LogLevel.Debug, + $"Failed to complete render batch '1846' in circuit host '{Client.CircuitId}'.", + "Received an acknowledgement for batch with id '1846' when the last batch produced was '4'.")); + } + [Fact] public async Task CannotInvokeOnRenderCompletedBeforeInitialization() { @@ -249,7 +364,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests public async Task OnLocationChanged_ReportsDebugForExceptionInValidation() { // Arrange - var expectedError = "Location change to http://example.com failed."; + var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " + + "For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " + + "Location change to 'http://example.com' failed."; + var rootUri = _serverFixture.RootUri; var baseUri = new Uri(rootUri, "/subdir"); Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app"); @@ -267,7 +385,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests Assert.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information); Assert.Contains(Logs, l => { - return l.LogLevel == LogLevel.Debug && Regex.IsMatch(l.Message, "Location change to http://example.com in circuit .* failed."); + return (l.LogLevel, l.Message) == (LogLevel.Debug, $"Location change to 'http://example.com' in circuit '{Client.CircuitId}' failed."); }); } @@ -275,7 +393,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests public async Task OnLocationChanged_ReportsErrorForExceptionInUserCode() { // Arrange - var expectedError = "There was an unhandled exception .?"; + var expectedError = "There was an unhandled exception on the current circuit, so this circuit will be terminated. " + + "For more details turn on detailed exceptions in 'CircuitOptions.DetailedErrors'. " + + "Location change failed."; + var rootUri = _serverFixture.RootUri; var baseUri = new Uri(rootUri, "/subdir"); Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app"); @@ -287,14 +408,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync( "OnLocationChanged", new Uri(baseUri, "/test").AbsoluteUri, - false)); + false)); // Assert var actualError = Assert.Single(Errors); - Assert.Matches(expectedError, actualError); + Assert.Equal(expectedError, actualError); Assert.Contains(Logs, l => { - return l.LogLevel == LogLevel.Error && Regex.IsMatch(l.Message, "Unhandled exception in circuit .*"); + return (l.LogLevel, l.Message) == (LogLevel.Error, $"Location change to '{new Uri(_serverFixture.RootUri,"/test")}' in circuit '{Client.CircuitId}' failed."); }); } @@ -373,6 +494,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests TestSink.MessageLogged -= LogMessages; } + private string Serialize(T browserEventDescriptor) => + JsonSerializer.Serialize(browserEventDescriptor, TestJsonSerializerOptionsProvider.Options); + + [DebuggerDisplay("{LogLevel.ToString(),nq} - {Message ?? \"null\",nq} - {Exception?.Message,nq}")] private class LogMessage { public LogMessage(LogLevel logLevel, string message, Exception exception) @@ -394,7 +519,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests private class Batch { - public Batch(int id, byte [] data) + public Batch(int id, byte[] data) { Id = id; Data = data; diff --git a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs index 3d589988b8..4b165a8099 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs @@ -9,6 +9,7 @@ using System.Text.Json; using System.Threading.Tasks; using Ignitor; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; @@ -500,8 +501,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests // Arrange await GoToTestComponent(Batches); var sink = _serverFixture.Host.Services.GetRequiredService(); - var logEvents = new List<(LogLevel logLevel, string)>(); - sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name)); + var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>(); + sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception)); // Act var browserDescriptor = new WebEventDescriptor() @@ -520,8 +521,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests }); Assert.Contains( - (LogLevel.Debug, "DispatchEventFailedToParseEventData"), - logEvents); + logEvents, + e => e.eventIdName == "DispatchEventFailedToParseEventData" && e.logLevel == LogLevel.Debug && + e.exception.Message == "There was an error parsing the event arguments. EventId: '6'."); // Taking any other action will fail because the circuit is disposed. await Client.ExpectCircuitErrorAndDisconnect(async () => @@ -563,7 +565,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests Assert.Contains( logEvents, e => e.eventIdName == "DispatchEventFailedToDispatchEvent" && e.logLevel == LogLevel.Debug && - e.exception is ArgumentException ae && ae.Message.Contains("There is no event handler with ID 1")); + e.exception is ArgumentException ae && ae.Message.Contains("There is no event handler associated with this event. EventId: '1'.")); // Taking any other action will fail because the circuit is disposed. await Client.ExpectCircuitErrorAndDisconnect(async () => diff --git a/src/Components/test/testassets/BasicTestApp/CounterComponent.razor b/src/Components/test/testassets/BasicTestApp/CounterComponent.razor index a2df8f3ea6..9de91502e1 100644 --- a/src/Components/test/testassets/BasicTestApp/CounterComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/CounterComponent.razor @@ -1,6 +1,6 @@

Counter

Current count: @currentCount

-

+