Improve unexpected server error message to client (#1532)

* Improve unexpected server error message to client

* Separated expected vs unexpected errors in error message. Fixed broken tests

* Fix ts functional tests
This commit is contained in:
James Newton-King 2018-03-06 22:11:46 +13:00 committed by GitHub
parent 00da533f10
commit 846432c9ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 19 deletions

View File

@ -122,7 +122,7 @@ describe("hubConnection", () => {
}); });
it("rethrows an exception from the server when invoking", (done) => { it("rethrows an exception from the server when invoking", (done) => {
const errorMessage = "An error occurred."; const errorMessage = "An unexpected error occurred invoking 'ThrowException' on the server. InvalidOperationException: An error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace, logger: LogLevel.Trace,
protocol, protocol,
@ -130,7 +130,7 @@ describe("hubConnection", () => {
}); });
hubConnection.start().then(() => { hubConnection.start().then(() => {
hubConnection.invoke("ThrowException", errorMessage).then(() => { hubConnection.invoke("ThrowException", "An error occurred.").then(() => {
// exception expected but none thrown // exception expected but none thrown
fail(); fail();
}).catch((e) => { }).catch((e) => {
@ -195,7 +195,7 @@ describe("hubConnection", () => {
}); });
it("rethrows an exception from the server when streaming", (done) => { it("rethrows an exception from the server when streaming", (done) => {
const errorMessage = "An error occurred."; const errorMessage = "An unexpected error occurred invoking 'StreamThrowException' on the server. InvalidOperationException: An error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace, logger: LogLevel.Trace,
protocol, protocol,
@ -203,13 +203,13 @@ describe("hubConnection", () => {
}); });
hubConnection.start().then(() => { hubConnection.start().then(() => {
hubConnection.stream("StreamThrowException", errorMessage).subscribe({ hubConnection.stream("StreamThrowException", "An error occurred.").subscribe({
complete: function complete() { complete: function complete() {
hubConnection.stop(); hubConnection.stop();
fail(); fail();
}, },
error: function error(err) { error: function error(err) {
expect(err.message).toEqual("An error occurred."); expect(err.message).toEqual(errorMessage);
hubConnection.stop(); hubConnection.stop();
done(); done();
}, },

View File

@ -170,6 +170,13 @@ namespace Microsoft.AspNetCore.SignalR.Internal
return; return;
} }
if (hubMethodInvocationMessage.ArgumentBindingException != null)
{
Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, hubMethodInvocationMessage.ArgumentBindingException);
await SendInvocationError(hubMethodInvocationMessage, connection, $"Failed to invoke '{hubMethodInvocationMessage.Target}'. {hubMethodInvocationMessage.ArgumentBindingException.Message}");
return;
}
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>(); var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
var hub = hubActivator.Create(); var hub = hubActivator.Create();
@ -181,7 +188,15 @@ namespace Microsoft.AspNetCore.SignalR.Internal
if (isStreamedInvocation) if (isStreamedInvocation)
{ {
var enumerator = GetStreamingEnumerator(connection, hubMethodInvocationMessage.InvocationId, methodExecutor, result, methodExecutor.MethodReturnType); if (!TryGetStreamingEnumerator(connection, hubMethodInvocationMessage.InvocationId, methodExecutor, result, methodExecutor.MethodReturnType, out var enumerator))
{
Log.InvalidReturnValueFromStreamingMethod(_logger, methodExecutor.MethodInfo.Name);
await SendInvocationError(hubMethodInvocationMessage, connection,
$"The value returned by the streaming method '{methodExecutor.MethodInfo.Name}' is null, does not implement the IObservable<> interface or is not a ReadableChannel<>.");
return;
}
Log.StreamingResult(_logger, hubMethodInvocationMessage.InvocationId, methodExecutor); Log.StreamingResult(_logger, hubMethodInvocationMessage.InvocationId, methodExecutor);
await StreamResultsAsync(hubMethodInvocationMessage.InvocationId, connection, enumerator); await StreamResultsAsync(hubMethodInvocationMessage.InvocationId, connection, enumerator);
} }
@ -195,12 +210,12 @@ namespace Microsoft.AspNetCore.SignalR.Internal
catch (TargetInvocationException ex) catch (TargetInvocationException ex)
{ {
Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex); Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex);
await SendInvocationError(hubMethodInvocationMessage, connection, ex.InnerException.Message); await SendInvocationError(hubMethodInvocationMessage, connection, BuildUnexpectedErrorMessage(hubMethodInvocationMessage.Target, ex.InnerException));
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex); Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex);
await SendInvocationError(hubMethodInvocationMessage, connection, ex.Message); await SendInvocationError(hubMethodInvocationMessage, connection, BuildUnexpectedErrorMessage(hubMethodInvocationMessage.Target, ex));
} }
finally finally
{ {
@ -209,6 +224,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal
} }
} }
private string BuildUnexpectedErrorMessage(string methodName, Exception exception)
{
return $"An unexpected error occurred invoking '{methodName}' on the server. {exception.GetType().Name}: {exception.Message}";
}
private async Task StreamResultsAsync(string invocationId, HubConnectionContext connection, IAsyncEnumerator<object> enumerator) private async Task StreamResultsAsync(string invocationId, HubConnectionContext connection, IAsyncEnumerator<object> enumerator)
{ {
string error = null; string error = null;
@ -369,7 +389,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
return false; return false;
} }
private IAsyncEnumerator<object> GetStreamingEnumerator(HubConnectionContext connection, string invocationId, ObjectMethodExecutor methodExecutor, object result, Type resultType) private bool TryGetStreamingEnumerator(HubConnectionContext connection, string invocationId, ObjectMethodExecutor methodExecutor, object result, Type resultType, out IAsyncEnumerator<object> enumerator)
{ {
if (result != null) if (result != null)
{ {
@ -378,17 +398,19 @@ namespace Microsoft.AspNetCore.SignalR.Internal
resultType.GetInterfaces().FirstOrDefault(IsIObservable); resultType.GetInterfaces().FirstOrDefault(IsIObservable);
if (observableInterface != null) if (observableInterface != null)
{ {
return AsyncEnumeratorAdapters.FromObservable(result, observableInterface, CreateCancellation()); enumerator = AsyncEnumeratorAdapters.FromObservable(result, observableInterface, CreateCancellation());
return true;
} }
if (IsChannel(resultType, out var payloadType)) if (IsChannel(resultType, out var payloadType))
{ {
return AsyncEnumeratorAdapters.FromChannel(result, payloadType, CreateCancellation()); enumerator = AsyncEnumeratorAdapters.FromChannel(result, payloadType, CreateCancellation());
return true;
} }
} }
Log.InvalidReturnValueFromStreamingMethod(_logger, methodExecutor.MethodInfo.Name); enumerator = null;
throw new InvalidOperationException($"The value returned by the streaming method '{methodExecutor.MethodInfo.Name}' is null, does not implement the IObservable<> interface or is not a ReadableChannel<>."); return false;
CancellationToken CreateCancellation() CancellationToken CreateCancellation()
{ {

View File

@ -397,7 +397,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
var channel = await connection.StreamAsync<int>("StreamException").OrTimeout(); var channel = await connection.StreamAsync<int>("StreamException").OrTimeout();
var ex = await Assert.ThrowsAsync<HubException>(() => channel.ReadAllAsync().OrTimeout()); var ex = await Assert.ThrowsAsync<HubException>(() => channel.ReadAllAsync().OrTimeout());
Assert.Equal("Error occurred while streaming.", ex.Message); Assert.Equal("An unexpected error occurred invoking 'StreamException' on the server. InvalidOperationException: Error occurred while streaming.", ex.Message);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -451,7 +451,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
await connection.StartAsync().OrTimeout(); await connection.StartAsync().OrTimeout();
var ex = await Assert.ThrowsAsync<HubException>(() => connection.InvokeAsync("Echo", "p1", 42)).OrTimeout(); var ex = await Assert.ThrowsAsync<HubException>(() => connection.InvokeAsync("Echo", "p1", 42)).OrTimeout();
Assert.Equal("Invocation provides 2 argument(s) but target expects 1.", ex.Message); Assert.Equal("Failed to invoke 'Echo'. Invocation provides 2 argument(s) but target expects 1.", ex.Message);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -478,7 +478,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
await connection.StartAsync().OrTimeout(); await connection.StartAsync().OrTimeout();
var ex = await Assert.ThrowsAsync<HubException>(() => connection.InvokeAsync("Echo", new int[] { 42 })).OrTimeout(); var ex = await Assert.ThrowsAsync<HubException>(() => connection.InvokeAsync("Echo", new int[] { 42 })).OrTimeout();
Assert.StartsWith("Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex.Message); Assert.StartsWith("Failed to invoke 'Echo'. Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex.Message);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -535,7 +535,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
var channel = await connection.StreamAsync<int>("Stream", 42, 42); var channel = await connection.StreamAsync<int>("Stream", 42, 42);
var ex = await Assert.ThrowsAsync<HubException>(() => channel.ReadAllAsync().OrTimeout()); var ex = await Assert.ThrowsAsync<HubException>(() => channel.ReadAllAsync().OrTimeout());
Assert.Equal("Invocation provides 2 argument(s) but target expects 1.", ex.Message); Assert.Equal("Failed to invoke 'Stream'. Invocation provides 2 argument(s) but target expects 1.", ex.Message);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -563,7 +563,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
var channel = await connection.StreamAsync<int>("Stream", "xyz"); var channel = await connection.StreamAsync<int>("Stream", "xyz");
var ex = await Assert.ThrowsAsync<HubException>(() => channel.ReadAllAsync().OrTimeout()); var ex = await Assert.ThrowsAsync<HubException>(() => channel.ReadAllAsync().OrTimeout());
Assert.StartsWith("Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex.Message); Assert.StartsWith("Failed to invoke 'Stream'. Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.", ex.Message);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -449,7 +449,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var result = (await client.InvokeAsync(methodName).OrTimeout()); var result = (await client.InvokeAsync(methodName).OrTimeout());
Assert.Equal("BOOM!", result.Error); Assert.Equal($"An unexpected error occurred invoking '{methodName}' on the server. InvalidOperationException: BOOM!", result.Error);
// kill the connection // kill the connection
client.Dispose(); client.Dispose();