[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)) 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); Log.HandlingEvent(_logger, eventHandlerId, eventArgs);

View File

@ -2883,7 +2883,7 @@ namespace Microsoft.AspNetCore.Components.Test
{ {
return renderer.DispatchEventAsync(eventHandlerId, new EventArgs()); 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); Assert.Equal(2, numEventsFired);
} }

View File

@ -27,24 +27,9 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
private bool _disposed; private bool _disposed;
/// <summary> /// <summary>
/// Sets the current <see cref="Circuits.Circuit"/>. /// Sets the <see cref="IJSRuntime"/> for the current excution context.
/// </summary> /// </summary>
/// <param name="circuitHost">The <see cref="Circuits.Circuit"/>.</param> public void SetCurrentJSRuntime() => JSInterop.JSRuntime.SetCurrentJSRuntime(JSRuntime);
/// <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);
}
// This event is fired when there's an unrecoverable exception coming from the circuit, and // 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 // 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 try
{ {
SetCurrentCircuitHost(this); SetCurrentJSRuntime();
_initialized = true; // We're ready to accept incoming JSInterop calls from here on _initialized = true; // We're ready to accept incoming JSInterop calls from here on
await OnCircuitOpenedAsync(cancellationToken); 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 // BeginInvokeDotNetFromJS is used in a fire-and-forget context, so it's responsible for its own
// error handling. // error handling.
public async Task BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) 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(() => await Renderer.Dispatcher.InvokeAsync(() =>
{ {
SetCurrentCircuitHost(this); SetCurrentJSRuntime();
Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId); Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId);
DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); 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 // 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. // however, we still want this to get logged if we do.
Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex); Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex);
if (Client.Connected) await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Interop call failed."));
{
await NotifyClientError(Client, "Interop call failed.");
}
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
} }
} }
@ -382,7 +386,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
await Renderer.Dispatcher.InvokeAsync(() => await Renderer.Dispatcher.InvokeAsync(() =>
{ {
SetCurrentCircuitHost(this); SetCurrentJSRuntime();
if (!succeded) if (!succeded)
{ {
// We can log the arguments here because it is simply the JS error with the call stack. // 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 // An error completing JS interop means that the user sent invalid data, a well-behaved
// client won't do this. // client won't do this.
Log.EndInvokeDispatchException(_logger, ex); Log.EndInvokeDispatchException(_logger, ex);
if (Client.Connected) await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Invalid interop arguments."));
{
await NotifyClientError(Client, "Invalid interop arguments.");
}
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
} }
} }
@ -419,17 +420,13 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
WebEventData webEventData; WebEventData webEventData;
try try
{ {
AssertInitialized();
webEventData = WebEventData.Parse(eventDescriptorJson, eventArgsJson); webEventData = WebEventData.Parse(eventDescriptorJson, eventArgsJson);
} }
catch (Exception ex) catch (Exception ex)
{ {
// Invalid event data is fatal. We expect a well-behaved client to send valid JSON. // Invalid event data is fatal. We expect a well-behaved client to send valid JSON.
Log.DispatchEventFailedToParseEventData(_logger, ex); Log.DispatchEventFailedToParseEventData(_logger, ex);
if (Client.Connected) await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Bad input data."));
{
await NotifyClientError(Client, "Invalid event data.");
}
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
return; return;
} }
@ -438,7 +435,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
await Renderer.Dispatcher.InvokeAsync(() => await Renderer.Dispatcher.InvokeAsync(() =>
{ {
SetCurrentCircuitHost(this); SetCurrentJSRuntime();
return Renderer.DispatchEventAsync( return Renderer.DispatchEventAsync(
webEventData.EventHandlerId, webEventData.EventHandlerId,
webEventData.EventFieldInfo, 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 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. // A well-behaved client won't do this.
Log.DispatchEventFailedToDispatchEvent(_logger, webEventData.EventHandlerId.ToString(), ex); Log.DispatchEventFailedToDispatchEvent(_logger, webEventData.EventHandlerId.ToString(), ex);
if (Client.Connected) await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Failed to dispatch event."));
{
await NotifyClientError(Client, "Failed to dispatch event.");
}
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false));
} }
} }
@ -469,7 +463,7 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
{ {
await Renderer.Dispatcher.InvokeAsync(() => await Renderer.Dispatcher.InvokeAsync(() =>
{ {
SetCurrentCircuitHost(this); SetCurrentJSRuntime();
Log.LocationChange(_logger, uri, CircuitId); Log.LocationChange(_logger, uri, CircuitId);
var navigationManager = (RemoteNavigationManager)Services.GetRequiredService<NavigationManager>(); var navigationManager = (RemoteNavigationManager)Services.GetRequiredService<NavigationManager>();
navigationManager.NotifyLocationChanged(uri, intercepted); 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 // LocationChangeException means that it failed in user-code. Treat this like an unhandled
// exception in user-code. // exception in user-code.
Log.LocationChangeFailedInCircuit(_logger, uri, CircuitId, nex); 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)); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(nex, isTerminating: false));
} }
catch (Exception ex) 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 // Any other exception means that it failed validation, or inside the NavigationManager. Treat
// this like bad data. // this like bad data.
Log.LocationChangeFailed(_logger, uri, CircuitId, ex); Log.LocationChangeFailed(_logger, uri, CircuitId, ex);
if (Client.Connected) await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, $"Location change to '{uri}' failed."));
{
await NotifyClientError(Client, $"Location change to {uri} failed.");
}
UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); 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. // 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 // 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()); _ = Renderer.Dispatcher.InvokeAsync(() => Renderer.ProcessBufferedRenderBatches());
} }
@ -566,39 +557,50 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
private async Task ReportUnhandledException(Exception exception) private async Task ReportUnhandledException(Exception exception)
{ {
Log.CircuitUnhandledException(_logger, CircuitId, 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) if (!Client.Connected)
{ {
_logger.LogDebug("Client is disconnected YO."); Log.UnhandledExceptionClientDisconnected(
_logger,
CircuitId,
exception);
return; return;
} }
try try
{ {
if (_options.DetailedErrors) Log.CircuitTransmittingClientError(_logger, CircuitId);
{ await client.SendAsync("JS.Error", error);
await NotifyClientError(Client, exception.ToString()); Log.CircuitTransmittedClientErrorSuccess(_logger, CircuitId);
}
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);
}
} }
catch (Exception ex) 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 class Log
{ {
private static readonly Action<ILogger, Exception> _intializationStarted; 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, string, Exception> _onCircuitClosed;
private static readonly Action<ILogger, Type, string, string, Exception> _circuitHandlerFailed; 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> _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, string, string, Exception> _beginInvokeDotNetStatic;
private static readonly Action<ILogger, string, long, string, Exception> _beginInvokeDotNetInstance; 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> _locationChangeSucceeded;
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed; private static readonly Action<ILogger, string, string, Exception> _locationChangeFailed;
private static readonly Action<ILogger, string, string, Exception> _locationChangeFailedInCircuit; private static readonly Action<ILogger, string, string, Exception> _locationChangeFailedInCircuit;
private static readonly Action<ILogger, long, string, Exception> _onRenderCompletedFailed;
private static class EventIds 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 OnCircuitClosed = new EventId(109, "OnCircuitClosed");
public static readonly EventId CircuitHandlerFailed = new EventId(110, "CircuitHandlerFailed"); public static readonly EventId CircuitHandlerFailed = new EventId(110, "CircuitHandlerFailed");
public static readonly EventId CircuitUnhandledException = new EventId(111, "CircuitUnhandledException"); 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 // 200s used for interactive stuff
public static readonly EventId DispatchEventFailedToParseEventData = new EventId(200, "DispatchEventFailedToParseEventData"); 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 LocationChangeSucceded = new EventId(209, "LocationChangeSucceeded");
public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed"); public static readonly EventId LocationChangeFailed = new EventId(210, "LocationChangeFailed");
public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit"); public static readonly EventId LocationChangeFailedInCircuit = new EventId(211, "LocationChangeFailedInCircuit");
public static readonly EventId OnRenderCompletedFailed = new EventId(212, " OnRenderCompletedFailed");
} }
static Log() static Log()
@ -666,87 +676,102 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
_intializationStarted = LoggerMessage.Define( _intializationStarted = LoggerMessage.Define(
LogLevel.Debug, LogLevel.Debug,
EventIds.InitializationFailed, EventIds.InitializationFailed,
"Circuit initialization started"); "Circuit initialization started.");
_intializationSucceded = LoggerMessage.Define( _intializationSucceded = LoggerMessage.Define(
LogLevel.Debug, LogLevel.Debug,
EventIds.InitializationFailed, EventIds.InitializationFailed,
"Circuit initialization succeeded"); "Circuit initialization succeeded.");
_intializationFailed = LoggerMessage.Define( _intializationFailed = LoggerMessage.Define(
LogLevel.Debug, LogLevel.Debug,
EventIds.InitializationFailed, EventIds.InitializationFailed,
"Circuit initialization failed"); "Circuit initialization failed.");
_disposeStarted = LoggerMessage.Define<string>( _disposeStarted = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.DisposeStarted, EventIds.DisposeStarted,
"Disposing circuit {CircuitId} started"); "Disposing circuit '{CircuitId}' started.");
_disposeSucceded = LoggerMessage.Define<string>( _disposeSucceded = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.DisposeSucceeded, EventIds.DisposeSucceeded,
"Disposing circuit {CircuitId} succeded"); "Disposing circuit '{CircuitId}' succeded.");
_disposeFailed = LoggerMessage.Define<string>( _disposeFailed = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.DisposeFailed, EventIds.DisposeFailed,
"Disposing circuit {CircuitId} failed"); "Disposing circuit '{CircuitId}' failed.");
_onCircuitOpened = LoggerMessage.Define<string>( _onCircuitOpened = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.OnCircuitOpened, EventIds.OnCircuitOpened,
"Opening circuit with id {CircuitId}."); "Opening circuit with id '{CircuitId}'.");
_onConnectionUp = LoggerMessage.Define<string, string>( _onConnectionUp = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.OnConnectionUp, EventIds.OnConnectionUp,
"Circuit id {CircuitId} connected using connection {ConnectionId}."); "Circuit id '{CircuitId}' connected using connection '{ConnectionId}'.");
_onConnectionDown = LoggerMessage.Define<string, string>( _onConnectionDown = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.OnConnectionDown, EventIds.OnConnectionDown,
"Circuit id {CircuitId} disconnected from connection {ConnectionId}."); "Circuit id '{CircuitId}' disconnected from connection '{ConnectionId}'.");
_onCircuitClosed = LoggerMessage.Define<string>( _onCircuitClosed = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.OnCircuitClosed, EventIds.OnCircuitClosed,
"Closing circuit with id {CircuitId}."); "Closing circuit with id '{CircuitId}'.");
_circuitHandlerFailed = LoggerMessage.Define<Type, string, string>( _circuitHandlerFailed = LoggerMessage.Define<Type, string, string>(
LogLevel.Error, LogLevel.Error,
EventIds.CircuitHandlerFailed, EventIds.CircuitHandlerFailed,
"Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}"); "Unhandled error invoking circuit handler type {handlerType}.{handlerMethod}: {Message}");
_circuitUnhandledException = LoggerMessage.Define<string>( _circuitUnhandledException = LoggerMessage.Define<string>(
LogLevel.Error, LogLevel.Error,
EventIds.CircuitUnhandledException, EventIds.CircuitUnhandledException,
"Unhandled exception in circuit {CircuitId}"); "Unhandled exception in circuit '{CircuitId}'.");
_circuitUnhandledExceptionFailed = LoggerMessage.Define<string>( _circuitTransmittingClientError = LoggerMessage.Define<string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.CircuitUnhandledExceptionFailed, EventIds.CircuitTransmittingClientError,
"Failed to transmit exception to client in circuit {CircuitId}"); "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>( _beginInvokeDotNetStatic = LoggerMessage.Define<string, string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.BeginInvokeDotNet, 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>( _beginInvokeDotNetInstance = LoggerMessage.Define<string, long, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.BeginInvokeDotNet, 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>( _beginInvokeDotNetStaticFailed = LoggerMessage.Define<string, string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.BeginInvokeDotNetFailed, 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>( _beginInvokeDotNetInstanceFailed = LoggerMessage.Define<string, long, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.BeginInvokeDotNetFailed, 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( _endInvokeDispatchException = LoggerMessage.Define(
LogLevel.Debug, LogLevel.Debug,
@ -776,25 +801,30 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
_locationChange = LoggerMessage.Define<string, string>( _locationChange = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.LocationChange, EventIds.LocationChange,
"Location changing to {URI} in circuit {CircuitId}."); "Location changing to {URI} in circuit '{CircuitId}'.");
_locationChangeSucceeded = LoggerMessage.Define<string, string>( _locationChangeSucceeded = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.LocationChangeSucceded, EventIds.LocationChangeSucceded,
"Location change to {URI} in circuit {CircuitId} succeded."); "Location change to '{URI}' in circuit '{CircuitId}' succeded.");
_locationChangeFailed = LoggerMessage.Define<string, string>( _locationChangeFailed = LoggerMessage.Define<string, string>(
LogLevel.Debug, LogLevel.Debug,
EventIds.LocationChangeFailed, EventIds.LocationChangeFailed,
"Location change to {URI} in circuit {CircuitId} failed."); "Location change to '{URI}' in circuit '{CircuitId}' failed.");
_locationChangeFailedInCircuit = LoggerMessage.Define<string, string>( _locationChangeFailedInCircuit = LoggerMessage.Define<string, string>(
LogLevel.Error, LogLevel.Error,
EventIds.LocationChangeFailed, 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 InitializationSucceeded(ILogger logger) => _intializationSucceded(logger, null);
public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception); public static void InitializationFailed(ILogger logger, Exception exception) => _intializationFailed(logger, exception);
public static void DisposeStarted(ILogger logger, string circuitId) => _disposeStarted(logger, circuitId, null); 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 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 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 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 CircuitClosed(ILogger logger, string circuitId) => _onCircuitClosed(logger, circuitId, null);
public static void CircuitHandlerFailed(ILogger logger, CircuitHandler handler, string handlerMethod, Exception exception) 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 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 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 EndInvokeJSFailed(ILogger logger, long asyncHandle, string arguments) => _endInvokeJSFailed(logger, asyncHandle, arguments, null);
public static void EndInvokeJSSucceeded(ILogger logger, long asyncCall) => _endInvokeJSSucceeded(logger, asyncCall, 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 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 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 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. // disposed.
} }
public Task OnRenderCompleted(long incomingBatchId, string errorMessageOrNull) public Task OnRenderCompletedAsync(long incomingBatchId, string errorMessageOrNull)
{ {
if (_disposing) if (_disposing)
{ {
@ -273,9 +273,9 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
if (lastBatchId < incomingBatchId) if (lastBatchId < incomingBatchId)
{ {
HandleException( // This exception is due to a bad client input, so we mark it as such to prevent loging it as a warning and
new InvalidOperationException($"Received an acknowledgement for batch with id '{incomingBatchId}' when the last batch produced was '{lastBatchId}'.")); // flooding the logs with warnings.
return Task.CompletedTask; 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 // 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 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 System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -203,7 +201,7 @@ namespace Microsoft.AspNetCore.Components.Server
} }
Log.ReceivedConfirmationForBatch(_logger, renderId); Log.ReceivedConfirmationForBatch(_logger, renderId);
_ = circuitHost.Renderer.OnRenderCompleted(renderId, errorMessageOrNull); _ = circuitHost.OnRenderCompletedAsync(renderId, errorMessageOrNull);
} }
public async ValueTask OnLocationChanged(string uri, bool intercepted) 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 = 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"); 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 = 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 = 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) public static void NoComponentsRegisteredInEndpoint(ILogger logger, string endpointDisplayName) => _noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
{
_noComponentsRegisteredInEndpoint(logger, endpointDisplayName, null);
}
public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) public static void ReceivedConfirmationForBatch(ILogger logger, long batchId) => _receivedConfirmationForBatch(logger, batchId, null);
{
_receivedConfirmationForBatch(logger, batchId, null);
}
public static void UnhandledExceptionInCircuit(ILogger logger, string circuitId, Exception exception)
{
_unhandledExceptionInCircuit(logger, circuitId, exception);
}
public static void CircuitAlreadyInitialized(ILogger logger, string circuitId) => _circuitAlreadyInitialized(logger, circuitId, 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 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 InvalidInputData(ILogger logger, [CallerMemberName] string callSite = "") => _invalidInputData(logger, callSite, null);
public static void CircuitInitializationFailed(ILogger logger, Exception exception) => _circuitInitializationFailed(logger, exception); 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(); component.TriggerRender();
} }
await renderer.OnRenderCompleted(2, null); await renderer.OnRenderCompletedAsync(2, null);
// Assert // Assert
Assert.Equal(9, renderer._unacknowledgedRenderBatches.Count); Assert.Equal(9, renderer._unacknowledgedRenderBatches.Count);
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
} }
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count); Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
await renderer.OnRenderCompleted(2, null); await renderer.OnRenderCompletedAsync(2, null);
// Assert // Assert
Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count); Assert.Equal(10, renderer._unacknowledgedRenderBatches.Count);
@ -153,7 +153,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
var componentId = renderer.AssignRootComponentId(component); var componentId = renderer.AssignRootComponentId(component);
component.TriggerRender(); component.TriggerRender();
_ = renderer.OnRenderCompleted(2, null); _ = renderer.OnRenderCompletedAsync(2, null);
@event.Reset(); @event.Reset();
firstBatchTCS.SetResult(null); firstBatchTCS.SetResult(null);
@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
foreach (var id in renderIds.ToArray()) foreach (var id in renderIds.ToArray())
{ {
_ = renderer.OnRenderCompleted(id, null); _ = renderer.OnRenderCompletedAsync(id, null);
} }
secondBatchTCS.SetResult(null); secondBatchTCS.SetResult(null);
@ -234,14 +234,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
}; };
// Receive the ack for the intial batch // Receive the ack for the intial batch
_ = renderer.OnRenderCompleted(2, null); _ = renderer.OnRenderCompletedAsync(2, null);
// Receive the ack for the second batch // Receive the ack for the second batch
_ = renderer.OnRenderCompleted(3, null); _ = renderer.OnRenderCompletedAsync(3, null);
firstBatchTCS.SetResult(null); firstBatchTCS.SetResult(null);
secondBatchTCS.SetResult(null); secondBatchTCS.SetResult(null);
// Repeat the ack for the third batch // Repeat the ack for the third batch
_ = renderer.OnRenderCompleted(3, null); _ = renderer.OnRenderCompletedAsync(3, null);
// Assert // Assert
Assert.Empty(exceptions); Assert.Empty(exceptions);
@ -297,14 +297,14 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
}; };
// Receive the ack for the intial batch // Receive the ack for the intial batch
_ = renderer.OnRenderCompleted(2, null); _ = renderer.OnRenderCompletedAsync(2, null);
// Receive the ack for the second batch // Receive the ack for the second batch
_ = renderer.OnRenderCompleted(2, null); _ = renderer.OnRenderCompletedAsync(2, null);
firstBatchTCS.SetResult(null); firstBatchTCS.SetResult(null);
secondBatchTCS.SetResult(null); secondBatchTCS.SetResult(null);
// Repeat the ack for the third batch // Repeat the ack for the third batch
_ = renderer.OnRenderCompleted(3, null); _ = renderer.OnRenderCompletedAsync(3, null);
// Assert // Assert
Assert.Empty(exceptions); Assert.Empty(exceptions);
@ -358,7 +358,7 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
}; };
// Pretend that we missed the ack for the initial batch // Pretend that we missed the ack for the initial batch
_ = renderer.OnRenderCompleted(3, null); _ = renderer.OnRenderCompletedAsync(3, null);
firstBatchTCS.SetResult(null); firstBatchTCS.SetResult(null);
secondBatchTCS.SetResult(null); secondBatchTCS.SetResult(null);
@ -414,12 +414,11 @@ namespace Microsoft.AspNetCore.Components.Web.Rendering
exceptions.Add(e); exceptions.Add(e);
}; };
_ = renderer.OnRenderCompleted(4, null); var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.OnRenderCompletedAsync(4, null));
firstBatchTCS.SetResult(null); firstBatchTCS.SetResult(null);
secondBatchTCS.SetResult(null); secondBatchTCS.SetResult(null);
// Assert // Assert
var exception = Assert.Single(exceptions);
Assert.Equal( Assert.Equal(
"Received an acknowledgement for batch with id '4' when the last batch produced was '3'.", "Received an acknowledgement for batch with id '4' when the last batch produced was '3'.",
exception.Message); exception.Message);

View File

@ -11,11 +11,20 @@ namespace Microsoft.AspNetCore.Components.Web
{ {
// This class represents the second half of parsing incoming event data, // This class represents the second half of parsing incoming event data,
// once the type of the eventArgs becomes known. // once the type of the eventArgs becomes known.
public static WebEventData Parse(string eventDescriptorJson, string eventArgsJson) 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( return Parse(
Deserialize<WebEventDescriptor>(eventDescriptorJson), eventDescriptor,
eventArgsJson); eventArgsJson);
} }
@ -25,7 +34,7 @@ namespace Microsoft.AspNetCore.Components.Web
eventDescriptor.BrowserRendererId, eventDescriptor.BrowserRendererId,
eventDescriptor.EventHandlerId, eventDescriptor.EventHandlerId,
InterpretEventFieldInfo(eventDescriptor.EventFieldInfo), InterpretEventFieldInfo(eventDescriptor.EventFieldInfo),
ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson)); ParseEventArgsJson(eventDescriptor.EventHandlerId, eventDescriptor.EventArgsType, eventArgsJson));
} }
private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs) private WebEventData(int browserRendererId, ulong eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs)
@ -44,43 +53,34 @@ namespace Microsoft.AspNetCore.Components.Web
public EventArgs EventArgs { get; } 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 eventArgsType switch
return DeserializeChangeEventArgs(eventArgsJson); {
case "clipboard": "change" => DeserializeChangeEventArgs(eventArgsJson),
return Deserialize<UIClipboardEventArgs>(eventArgsJson); "clipboard" => Deserialize<UIClipboardEventArgs>(eventArgsJson),
case "drag": "drag" => Deserialize<UIDragEventArgs>(eventArgsJson),
return Deserialize<UIDragEventArgs>(eventArgsJson); "error" => Deserialize<UIErrorEventArgs>(eventArgsJson),
case "error": "focus" => Deserialize<UIFocusEventArgs>(eventArgsJson),
return Deserialize<UIErrorEventArgs>(eventArgsJson); "keyboard" => Deserialize<UIKeyboardEventArgs>(eventArgsJson),
case "focus": "mouse" => Deserialize<UIMouseEventArgs>(eventArgsJson),
return Deserialize<UIFocusEventArgs>(eventArgsJson); "pointer" => Deserialize<UIPointerEventArgs>(eventArgsJson),
case "keyboard": "progress" => Deserialize<UIProgressEventArgs>(eventArgsJson),
return Deserialize<UIKeyboardEventArgs>(eventArgsJson); "touch" => Deserialize<UITouchEventArgs>(eventArgsJson),
case "mouse": "unknown" => EventArgs.Empty,
return Deserialize<UIMouseEventArgs>(eventArgsJson); "wheel" => Deserialize<UIWheelEventArgs>(eventArgsJson),
case "pointer": _ => throw new InvalidOperationException($"Unsupported event type '{eventArgsType}'. EventId: '{eventHandlerId}'."),
return Deserialize<UIPointerEventArgs>(eventArgsJson); };
case "progress": }
return Deserialize<UIProgressEventArgs>(eventArgsJson); catch (Exception e)
case "touch": {
return Deserialize<UITouchEventArgs>(eventArgsJson); throw new InvalidOperationException($"There was an error parsing the event arguments. EventId: '{eventHandlerId}'.", e);
case "unknown":
return EventArgs.Empty;
case "wheel":
return Deserialize<UIWheelEventArgs>(eventArgsJson);
default:
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
} }
} }
private static T Deserialize<T>(string json) private static T Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
{
return JsonSerializer.Deserialize<T>(json, JsonSerializerOptionsProvider.Options);
}
private static EventFieldInfo InterpretEventFieldInfo(EventFieldInfo fieldInfo) private static EventFieldInfo InterpretEventFieldInfo(EventFieldInfo fieldInfo)
{ {

View File

@ -6,9 +6,12 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ignitor; using Ignitor;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -20,7 +23,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
{ {
public class ComponentHubReliabilityTest : IClassFixture<AspNetSiteServerFixture>, IDisposable 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; private readonly AspNetSiteServerFixture _serverFixture;
public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output) public ComponentHubReliabilityTest(AspNetSiteServerFixture serverFixture, ITestOutputHelper output)
@ -52,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
return new Exception(error + Environment.NewLine + logs); 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 = _serverFixture.Host.Services.GetRequiredService<TestSink>();
TestSink.MessageLogged += LogMessages; 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")); 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] [Fact]
public async Task CannotInvokeOnRenderCompletedBeforeInitialization() public async Task CannotInvokeOnRenderCompletedBeforeInitialization()
{ {
@ -249,7 +364,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
public async Task OnLocationChanged_ReportsDebugForExceptionInValidation() public async Task OnLocationChanged_ReportsDebugForExceptionInValidation()
{ {
// Arrange // 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 rootUri = _serverFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir"); var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app"); 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.DoesNotContain(Logs, l => l.LogLevel > LogLevel.Information);
Assert.Contains(Logs, l => 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() public async Task OnLocationChanged_ReportsErrorForExceptionInUserCode()
{ {
// Arrange // 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 rootUri = _serverFixture.RootUri;
var baseUri = new Uri(rootUri, "/subdir"); var baseUri = new Uri(rootUri, "/subdir");
Assert.True(await Client.ConnectAsync(baseUri, prerendered: false), "Couldn't connect to the app"); 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( await Client.ExpectCircuitError(() => Client.HubConnection.SendAsync(
"OnLocationChanged", "OnLocationChanged",
new Uri(baseUri, "/test").AbsoluteUri, new Uri(baseUri, "/test").AbsoluteUri,
false)); false));
// Assert // Assert
var actualError = Assert.Single(Errors); var actualError = Assert.Single(Errors);
Assert.Matches(expectedError, actualError); Assert.Equal(expectedError, actualError);
Assert.Contains(Logs, l => 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; 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 private class LogMessage
{ {
public LogMessage(LogLevel logLevel, string message, Exception exception) public LogMessage(LogLevel logLevel, string message, Exception exception)
@ -394,7 +519,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
private class Batch private class Batch
{ {
public Batch(int id, byte [] data) public Batch(int id, byte[] data)
{ {
Id = id; Id = id;
Data = data; Data = data;

View File

@ -9,6 +9,7 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ignitor; using Ignitor;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -500,8 +501,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
// Arrange // Arrange
await GoToTestComponent(Batches); await GoToTestComponent(Batches);
var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>(); var sink = _serverFixture.Host.Services.GetRequiredService<TestSink>();
var logEvents = new List<(LogLevel logLevel, string)>(); var logEvents = new List<(LogLevel logLevel, string eventIdName, Exception exception)>();
sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name)); sink.MessageLogged += (wc) => logEvents.Add((wc.LogLevel, wc.EventId.Name, wc.Exception));
// Act // Act
var browserDescriptor = new WebEventDescriptor() var browserDescriptor = new WebEventDescriptor()
@ -520,8 +521,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
}); });
Assert.Contains( 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. // Taking any other action will fail because the circuit is disposed.
await Client.ExpectCircuitErrorAndDisconnect(async () => await Client.ExpectCircuitErrorAndDisconnect(async () =>
@ -563,7 +565,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
Assert.Contains( Assert.Contains(
logEvents, logEvents,
e => e.eventIdName == "DispatchEventFailedToDispatchEvent" && e.logLevel == LogLevel.Debug && 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. // Taking any other action will fail because the circuit is disposed.
await Client.ExpectCircuitErrorAndDisconnect(async () => await Client.ExpectCircuitErrorAndDisconnect(async () =>

View File

@ -1,6 +1,6 @@
<h1>Counter</h1> <h1>Counter</h1>
<p>Current count: @currentCount</p> <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> <label>
<input type="checkbox" @bind="handleClicks" /> <input type="checkbox" @bind="handleClicks" />

View File

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