[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.
This commit is contained in:
Javier Calvarro Nelson 2019-08-14 13:01:53 +02:00 committed by GitHub
parent e7a1dc620b
commit 74c09470c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 331 additions and 211 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -27,24 +27,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
private bool _disposed;
/// <summary>
/// Sets the current <see cref="Circuits.Circuit"/>.
/// Sets the <see cref="IJSRuntime"/> for the current excution context.
/// </summary>
/// <param name="circuitHost">The <see cref="Circuits.Circuit"/>.</param>
/// <remarks>
/// Calling <see cref="SetCurrentCircuitHost(CircuitHost)"/> will store related values such as the
/// <see cref="IJSRuntime"/> and <see cref="Renderer"/>
/// in the local execution context. Application code should not need to call this method,
/// it is primarily used by the Server-Side Components infrastructure.
/// </remarks>
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>();
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<ILogger, Exception> _intializationStarted;
@ -613,7 +615,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
private static readonly Action<ILogger, string, Exception> _onCircuitClosed;
private static readonly Action<ILogger, Type, string, string, Exception> _circuitHandlerFailed;
private static readonly Action<ILogger, string, Exception> _circuitUnhandledException;
private static readonly Action<ILogger, string, Exception> _circuitUnhandledExceptionFailed;
private static readonly Action<ILogger, string, Exception> _circuitTransmittingClientError;
private static readonly Action<ILogger, string, Exception> _circuitTransmittedClientErrorSuccess;
private static readonly Action<ILogger, string, Exception> _circuitTransmitErrorFailed;
private static readonly Action<ILogger, string, Exception> _unhandledExceptionClientDisconnected;
private static readonly Action<ILogger, string, string, string, Exception> _beginInvokeDotNetStatic;
private static readonly Action<ILogger, string, long, string, Exception> _beginInvokeDotNetInstance;
@ -628,6 +633,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
private static readonly Action<ILogger, string, string, Exception> _locationChangeSucceeded;
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed;
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailedInCircuit;
private static readonly Action<ILogger, long, string, Exception> _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<string>(
LogLevel.Debug,
EventIds.DisposeStarted,
"Disposing circuit {CircuitId} started");
"Disposing circuit '{CircuitId}' started.");
_disposeSucceded = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.DisposeSucceeded,
"Disposing circuit {CircuitId} succeded");
"Disposing circuit '{CircuitId}' succeded.");
_disposeFailed = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.DisposeFailed,
"Disposing circuit {CircuitId} failed");
"Disposing circuit '{CircuitId}' failed.");
_onCircuitOpened = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.OnCircuitOpened,
"Opening circuit with id {CircuitId}.");
"Opening circuit with id '{CircuitId}'.");
_onConnectionUp = LoggerMessage.Define<string, string>(
LogLevel.Debug,
EventIds.OnConnectionUp,
"Circuit id {CircuitId} connected using connection {ConnectionId}.");
"Circuit id '{CircuitId}' connected using connection '{ConnectionId}'.");
_onConnectionDown = LoggerMessage.Define<string, string>(
LogLevel.Debug,
EventIds.OnConnectionDown,
"Circuit id {CircuitId} disconnected from connection {ConnectionId}.");
"Circuit id '{CircuitId}' disconnected from connection '{ConnectionId}'.");
_onCircuitClosed = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.OnCircuitClosed,
"Closing circuit with id {CircuitId}.");
"Closing circuit with id '{CircuitId}'.");
_circuitHandlerFailed = LoggerMessage.Define<Type, string, string>(
LogLevel.Error,
EventIds.CircuitHandlerFailed,
"Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}");
_circuitUnhandledException = LoggerMessage.Define<string>(
LogLevel.Error,
EventIds.CircuitUnhandledException,
"Unhandled exception in circuit {CircuitId}");
_circuitUnhandledException = LoggerMessage.Define<string>(
LogLevel.Error,
EventIds.CircuitUnhandledException,
"Unhandled exception in circuit '{CircuitId}'.");
_circuitUnhandledExceptionFailed = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.CircuitUnhandledExceptionFailed,
"Failed to transmit exception to client in circuit {CircuitId}");
_circuitTransmittingClientError = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.CircuitTransmittingClientError,
"About to notify client of an error in circuit '{CircuitId}'.");
_circuitTransmittedClientErrorSuccess = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.CircuitTransmittedClientErrorSuccess,
"Successfully transmitted error to client in circuit '{CircuitId}'.");
_circuitTransmitErrorFailed = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.CircuitTransmitErrorFailed,
"Failed to transmit exception to client in circuit '{CircuitId}'.");
_unhandledExceptionClientDisconnected = LoggerMessage.Define<string>(
LogLevel.Debug,
EventIds.UnhandledExceptionClientDisconnected,
"An exception ocurred on the circuit host '{CircuitId}' while the client is disconnected.");
_beginInvokeDotNetStatic = LoggerMessage.Define<string, string, string>(
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<string, long, string>(
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<string, string, string>(
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<string, long, string>(
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<string, string>(
LogLevel.Debug,
EventIds.LocationChange,
"Location changing to {URI} in circuit {CircuitId}.");
"Location changing to {URI} in circuit '{CircuitId}'.");
_locationChangeSucceeded = LoggerMessage.Define<string, string>(
LogLevel.Debug,
EventIds.LocationChangeSucceded,
"Location change to {URI} in circuit {CircuitId} succeded.");
"Location change to '{URI}' in circuit '{CircuitId}' succeded.");
_locationChangeFailed = LoggerMessage.Define<string, string>(
LogLevel.Debug,
EventIds.LocationChangeFailed,
"Location change to {URI} in circuit {CircuitId} failed.");
"Location change to '{URI}' in circuit '{CircuitId}' failed.");
_locationChangeFailedInCircuit = LoggerMessage.Define<string, string>(
LogLevel.Error,
EventIds.LocationChangeFailed,
"Location change to {URI} in circuit {CircuitId} failed.");
"Location change to '{URI}' in circuit '{CircuitId}' failed.");
_onRenderCompletedFailed = LoggerMessage.Define<long, string>(
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);
}
}
}

View File

@ -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));
}
}

View File

@ -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
{
/// <summary>
/// Represents an exception related to remote rendering.
/// </summary>
internal class RemoteRendererException : Exception
{
/// <summary>
/// Constructs an instance of <see cref="RemoteRendererException"/>.
/// </summary>
/// <param name="message">The exception message.</param>
public RemoteRendererException(string message) : base(message)
{
}
}
}

View File

@ -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<ILogger, string, Exception> _circuitHostShutdown =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, "CircuitHostShutdown"), "Call to '{CallSite}' received after the circuit was shut down");
private static readonly Action<ILogger, string, Exception> _circuitTerminatedGracefully =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "CircuitTerminatedGracefully"), "Circuit '{CircuitId}' terminated gracefully");
private static readonly Action<ILogger, string, Exception> _invalidInputData =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(8, "InvalidInputData"), "Call to '{CallSite}' received invalid input data");
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(7, "InvalidInputData"), "Call to '{CallSite}' received invalid input data");
private static readonly Action<ILogger, Exception> _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);

View File

@ -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<InvalidOperationException>(() => 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);

View File

@ -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<WebEventDescriptor>(eventDescriptorJson);
}
catch (Exception e)
{
throw new InvalidOperationException("Error parsing the event descriptor", e);
}
return Parse(
Deserialize<WebEventDescriptor>(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<UIClipboardEventArgs>(eventArgsJson);
case "drag":
return Deserialize<UIDragEventArgs>(eventArgsJson);
case "error":
return Deserialize<UIErrorEventArgs>(eventArgsJson);
case "focus":
return Deserialize<UIFocusEventArgs>(eventArgsJson);
case "keyboard":
return Deserialize<UIKeyboardEventArgs>(eventArgsJson);
case "mouse":
return Deserialize<UIMouseEventArgs>(eventArgsJson);
case "pointer":
return Deserialize<UIPointerEventArgs>(eventArgsJson);
case "progress":
return Deserialize<UIProgressEventArgs>(eventArgsJson);
case "touch":
return Deserialize<UITouchEventArgs>(eventArgsJson);
case "unknown":
return EventArgs.Empty;
case "wheel":
return Deserialize<UIWheelEventArgs>(eventArgsJson);
default:
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
return eventArgsType switch
{
"change" => DeserializeChangeEventArgs(eventArgsJson),
"clipboard" => Deserialize<UIClipboardEventArgs>(eventArgsJson),
"drag" => Deserialize<UIDragEventArgs>(eventArgsJson),
"error" => Deserialize<UIErrorEventArgs>(eventArgsJson),
"focus" => Deserialize<UIFocusEventArgs>(eventArgsJson),
"keyboard" => Deserialize<UIKeyboardEventArgs>(eventArgsJson),
"mouse" => Deserialize<UIMouseEventArgs>(eventArgsJson),
"pointer" => Deserialize<UIPointerEventArgs>(eventArgsJson),
"progress" => Deserialize<UIProgressEventArgs>(eventArgsJson),
"touch" => Deserialize<UITouchEventArgs>(eventArgsJson),
"unknown" => EventArgs.Empty,
"wheel" => Deserialize<UIWheelEventArgs>(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<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
}
private static T Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
private static EventFieldInfo InterpretEventFieldInfo(EventFieldInfo fieldInfo)
{

View File

@ -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<AspNetSiteServerFixture>, 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>();
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<Batch> 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>(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;

View File

@ -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<TestSink>();
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 () =>

View File

@ -1,6 +1,6 @@
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<p><button @onclick="@(handleClicks ? (Action)IncrementCount : null)">Click me</button></p>
<p><button id="counter" @onclick="@(handleClicks ? (Action)IncrementCount : null)">Click me</button></p>
<label>
<input type="checkbox" @bind="handleClicks" />

View File

@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ComponentsApp.Server
{
public class Startup