Skip to content
Browse files

Added custom SignalR error handling capability

Fixed #515

Created an ErrorExtensions class to add a "GetError" capability to
Exceptions.  GetError returns an error of type SignalRError which
unwraps and pulls unique attributes out of a thrown exception to expose
essential pieces to errors.
  • Loading branch information...
1 parent cfdd04c commit b590bb06b5cbb17f61b8c8ffc56e06e7cbe8d6c8 @NTaylorMullen NTaylorMullen committed
View
6 SignalR.Client.Net35/SignalR.Client.Net35.csproj
@@ -109,6 +109,12 @@
<Compile Include="..\SignalR.Client\Infrastructure\DisposableAction.cs">
<Link>Infrastructure\DisposableAction.cs</Link>
</Compile>
+ <Compile Include="..\SignalR.Client\Infrastructure\ErrorExtensions.cs">
+ <Link>Infrastructure\ErrorExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\SignalR.Client\Infrastructure\SignalRError.cs">
+ <Link>Infrastructure\SignalRError.cs</Link>
+ </Compile>
<Compile Include="..\SignalR.Client\Infrastructure\ThreadSafeInvoker.cs">
<Link>Infrastructure\ThreadSafeInvoker.cs</Link>
</Compile>
View
65 SignalR.Client/Infrastructure/ErrorExtensions.cs
@@ -0,0 +1,65 @@
+using System;
+using System.IO;
+using System.Net;
+
+namespace SignalR.Client
+{
+ public static class ErrorExtensions
+ {
+ /// <summary>
+ /// Simplifies error recognition by unwrapping complex exceptions.
+ /// </summary>
+ /// <param name="ex">The thrown exception.</param>
+ /// <returns>An unwrapped exception in the form of a SignalRError.</returns>
+ public static SignalRError GetError(this Exception ex)
+ {
+ ex = ex.Unwrap();
+ var wex = ex as WebException;
+
+ var error = new SignalRError(ex);
+
+ if (wex != null && wex.Response != null)
+ {
+ var response = wex.Response as HttpWebResponse;
+ if (response != null)
+ {
+ error.StatusCode = response.StatusCode;
+ Stream originStream = response.GetResponseStream();
+
+ if (originStream.CanRead)
+ {
+ // We need to copy the stream over and not consume it all on "ReadToEnd". If we consumed the entire stream GetError
+ // would only be able to be called once per Exception, otherwise you get inconsistent ResponseBody results.
+ Stream stream = Clone(originStream);
+
+ // Consume our copied stream
+ using (var sr = new StreamReader(stream))
+ {
+ error.ResponseBody = sr.ReadToEnd();
+ }
+ }
+ }
+ }
+
+ return error;
+ }
+
+ private static Stream Clone(Stream source)
+ {
+ Stream cloned = new MemoryStream();
+ byte[] buffer = new byte[2048];// Copy up to 2048 bytes at a time
+ int copiedBytes;// Maintains how many bytes were read
+
+ while ((copiedBytes = source.Read(buffer,0,buffer.Length)) > 0)// Read bytes and copy them into a buffer making sure not to trigger the dispose
+ {
+ cloned.Write(buffer, 0, copiedBytes);// Write the copied bytes from the buffer into the cloned stream
+ }
+
+ // Move the stream pointers back to the original start locations
+ source.Seek(0, 0);
+ cloned.Seek(0, 0);
+
+ return cloned;
+ }
+ }
+}
View
36 SignalR.Client/Infrastructure/SignalRError.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Net;
+
+namespace SignalR.Client
+{
+ /// <summary>
+ /// Represents errors that are thrown by the SignalR client
+ /// </summary>
+ public class SignalRError
+ {
+ public SignalRError(Exception exception)
+ {
+ Exception = exception;
+ }
+
+ /// <summary>
+ /// The status code of the error (if it was a WebException)
+ /// </summary>
+ public HttpStatusCode StatusCode { get; set; }
+
+ /// <summary>
+ /// The response body of the error, if it was a WebException and the response is readable
+ /// </summary>
+ public string ResponseBody { get; set; }
+
+ /// <summary>
+ /// The unwrapped underlying exception
+ /// </summary>
+ public Exception Exception { get; private set; }
+
+ public override string ToString()
+ {
+ return Exception.ToString();
+ }
+ }
+}
View
2 SignalR.Client/SignalR.Client.csproj
@@ -66,6 +66,8 @@
<Compile Include="Hubs\IHubProxy.cs" />
<Compile Include="Hubs\Subscription.cs" />
<Compile Include="IConnection.cs" />
+ <Compile Include="Infrastructure\ErrorExtensions.cs" />
+ <Compile Include="Infrastructure\SignalRError.cs" />
<Compile Include="Infrastructure\ExceptionHelper.cs" />
<Compile Include="Infrastructure\ThreadSafeInvoker.cs" />
<Compile Include="Transports\ServerSentEvents\ChunkBuffer.cs" />
View
36 SignalR.Tests/ConnectionFacts.cs
@@ -1,7 +1,9 @@
-using System;
-using System.Threading;
-using Moq;
+using Moq;
using SignalR.Client.Transports;
+using SignalR.Hosting.Memory;
+using System;
+using System.Net;
+using System.Threading.Tasks;
using Xunit;
namespace SignalR.Client.Tests
@@ -62,6 +64,34 @@ public void FailedStartShouldNotBeActive()
Assert.Equal("Something failed.", ex.Message);
Assert.Equal(ConnectionState.Disconnected, connection.State);
}
+
+ [Fact]
+ public void ThrownWebExceptionShouldBeUnwrapped()
+ {
+ var host = new MemoryHost();
+ host.MapConnection<MyBadConnection>("/ErrorsAreFun");
+
+ var connection = new Client.Connection("http://test/ErrorsAreFun");
+
+ var aggEx = Assert.Throws<AggregateException>(() => connection.Start(host).Wait());// Expecting 404
+
+ connection.Stop();
+
+ SignalRError ser = aggEx.GetError();// Unwrap the exception
+ Assert.Equal(ser.StatusCode, HttpStatusCode.NotFound);
+ Assert.NotNull(ser.ResponseBody);
+ Assert.NotNull(ser.Exception);
+ }
+
+ public class MyBadConnection : PersistentConnection
+ {
+ protected override Task OnConnectedAsync(IRequest request, string connectionId)
+ {
+ using (HttpWebRequest.Create("http://localhost/myincorrecturl/hereisa64bitstring_thathopefullywillnevershowupin_yourlocalhost/").GetResponse()) { }// Should throw 404
+
+ return base.OnConnectedAsync(request, connectionId);
+ }
+ }
}
}
}
View
11 samples/SignalR.Client.Samples/Program.cs
@@ -1,11 +1,10 @@
using System;
-using System.Threading;
-using System.Threading.Tasks;
using SignalR.Client.Hubs;
#if !NET35
using SignalR.Hosting.Memory;
#endif
-using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
namespace SignalR.Client.Samples
{
@@ -85,7 +84,7 @@ private static void RunDemoHub(HubConnection hubConnection)
demo.Invoke("multipleCalls").ContinueWith(task =>
{
- Console.WriteLine(task.Exception);
+ Console.WriteLine(task.Exception.GetError());
}, TaskContinuationOptions.OnlyOnFaulted);
@@ -118,7 +117,7 @@ private static void RunStreamingSample()
connection.Error += e =>
{
Console.Error.WriteLine("========ERROR==========");
- Console.Error.WriteLine(e.GetBaseException());
+ Console.Error.WriteLine(e.GetError());
Console.Error.WriteLine("=======================");
};
@@ -157,7 +156,7 @@ private static void RunStreamingSample()
catch(Exception ex)
{
Console.Error.WriteLine("========ERROR==========");
- Console.Error.WriteLine(ex.GetBaseException());
+ Console.Error.WriteLine(ex.GetError());
Console.Error.WriteLine("=======================");
return;
}

0 comments on commit b590bb0

Please sign in to comment.
Something went wrong with that request. Please try again.