This post continues on from: SignalR Messaging, a more complete server with a Console Application
Now the client will be developed with some error handling and proper logging. This example is a robust client, which can restart or handle any client, server events.
Code: https://github.com/damienbod/SignalRMessagingErrorHandling.git
A base Hub client class is created, so that each Hub client doesn’t need to re-implement the events or the start up logic more than once. This class is an abstract class. This forces the implementation of the start method. The class provides a State property and methods to start and stop the Hub client.
using System;
using Microsoft.AspNet.SignalR.Client;
using SignalRClientConsole.Logging;
namespace SignalRClientConsole.HubClients
{
public abstract class BaseHubClient
{
protected HubConnection _hubConnection;
protected IHubProxy _myHubProxy;
public string HubConnectionUrl { get;set;}
public string HubProxyName { get; set; }
public TraceLevels HubTraceLevel { get; set; }
public System.IO.TextWriter HubTraceWriter { get; set; }
public ConnectionState State
{
get { return _hubConnection.State; }
}
protected void Init()
{
_hubConnection = new HubConnection(HubConnectionUrl)
{
TraceLevel = HubTraceLevel,
TraceWriter = HubTraceWriter
};
_myHubProxy = _hubConnection.CreateHubProxy(HubProxyName);
_hubConnection.Received += _hubConnection_Received;
_hubConnection.Reconnected += _hubConnection_Reconnected;
_hubConnection.Reconnecting += _hubConnection_Reconnecting;
_hubConnection.StateChanged += _hubConnection_StateChanged;
_hubConnection.Error += _hubConnection_Error;
_hubConnection.ConnectionSlow += _hubConnection_ConnectionSlow;
_hubConnection.Closed += _hubConnection_Closed;
}
public void CloseHub()
{
_hubConnection.Stop();
_hubConnection.Dispose();
}
protected void StartHubInternal()
{
try
{
_hubConnection.Start().Wait();
}
catch (Exception ex)
{
HubClientEvents.Log.Warning(ex.Message + " " + ex.StackTrace);
}
}
public abstract void StartHub();
void _hubConnection_Closed()
{
HubClientEvents.Log.ClientEvents( "_hubConnection_Closed New State:" + _hubConnection.State + " " + _hubConnection.ConnectionId );
}
void _hubConnection_ConnectionSlow()
{
HubClientEvents.Log.ClientEvents("_hubConnection_ConnectionSlow New State:" + _hubConnection.State + " " + _hubConnection.ConnectionId);
}
void _hubConnection_Error(Exception obj)
{
HubClientEvents.Log.ClientEvents("_hubConnection_Error New State:" + _hubConnection.State + " " + _hubConnection.ConnectionId);
}
void _hubConnection_StateChanged(StateChange obj)
{
HubClientEvents.Log.ClientEvents("_hubConnection_StateChanged New State:" + _hubConnection.State + " " + _hubConnection.ConnectionId);
}
void _hubConnection_Reconnecting()
{
HubClientEvents.Log.ClientEvents("_hubConnection_Reconnecting New State:" + _hubConnection.State + " " + _hubConnection.ConnectionId);
}
void _hubConnection_Reconnected()
{
HubClientEvents.Log.ClientEvents("_hubConnection_Reconnected New State:" + _hubConnection.State + " " + _hubConnection.ConnectionId);
}
void _hubConnection_Received(string obj)
{
HubClientEvents.Log.ClientEvents("_hubConnection_Received New State:" + _hubConnection.State + " " + _hubConnection.ConnectionId);
}
}
}
Now the Hub client can be implemented. This class implements the abstract base client and also the shared assembly interfaces from the server implementation: ISendHubSync, IRecieveHubSync
using System;
using Damienbod.SignalR.IHubSync.Client;
using Damienbod.SignalR.IHubSync.Client.Dto;
using Microsoft.AspNet.SignalR.Client;
using SignalRClientConsole.Logging;
namespace SignalRClientConsole.HubClients
{
public class MyHubClient : BaseHubClient, ISendHubSync, IRecieveHubSync
{
public MyHubClient()
{
Init();
}
public new void Init()
{
HubConnectionUrl = "http://localhost:8089/";
HubProxyName = "Hubsync";
HubTraceLevel = TraceLevels.None;
HubTraceWriter = Console.Out;
base.Init();
_myHubProxy.On<string, string>("addMessage", Recieve_AddMessage);
_myHubProxy.On("heartbeat", Recieve_Heartbeat);
_myHubProxy.On<HelloModel>("sendHelloObject", Recieve_SendHelloObject);
StartHubInternal();
}
public override void StartHub()
{
_hubConnection.Dispose();
Init();
}
public void Recieve_AddMessage(string name, string message)
{
HubClientEvents.Log.Informational("Recieved addMessage: " + name + ": " + message);
}
public void Recieve_Heartbeat()
{
HubClientEvents.Log.Informational("Recieved heartbeat ");
}
public void Recieve_SendHelloObject(HelloModel hello)
{
HubClientEvents.Log.Informational("Recieved sendHelloObject "+ hello.Molly + ", " + hello.Age);
}
public void AddMessage(string name, string message)
{
_myHubProxy.Invoke("addMessage", "client message", " sent from console client").ContinueWith(task =>
{
if (task.IsFaulted)
{
HubClientEvents.Log.Error("There was an error opening the connection:" + task.Exception.GetBaseException());
}
}).Wait();
HubClientEvents.Log.Informational("Client Sending addMessage to server");
}
public void Heartbeat()
{
_myHubProxy.Invoke("Heartbeat").ContinueWith(task =>
{
if (task.IsFaulted)
{
HubClientEvents.Log.Error("There was an error opening the connection:" + task.Exception.GetBaseException());
}
}).Wait();
HubClientEvents.Log.Informational("Client heartbeat sent to server");
}
public void SendHelloObject(HelloModel hello)
{
_myHubProxy.Invoke<HelloModel>("SendHelloObject", hello).ContinueWith(task =>
{
if (task.IsFaulted)
{
HubClientEvents.Log.Error("There was an error opening the connection:" + task.Exception.GetBaseException());
}
}).Wait();
HubClientEvents.Log.Informational("Client sendHelloObject sent to server");
}
}
}
Using the Hub. A new instance is created and messages can be sent as required. In a productive application, the received message events would probably be passed on. In this demo, log messages are written.
using System;
using Damienbod.SignalR.IHubSync.Client.Dto;
using Microsoft.AspNet.SignalR.Client;
using SignalRClientConsole.HubClients;
using SignalRClientConsole.Logging;
namespace SignalRClientConsole
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting client http://localhost:8089");
var myHubClient = new MyHubClient();
while (true)
{
string key = Console.ReadLine();
if (key.ToUpper() == "W")
{
if (myHubClient.State == ConnectionState.Connected)
{
myHubClient.AddMessage("damien client", "hello all");
}
else
{
HubClientEvents.Log.Warning("Can't send message, connectionState= " + myHubClient.State);
}
}
if (key.ToUpper() == "E")
{
if (myHubClient.State == ConnectionState.Connected)
{
myHubClient.Heartbeat();
}
else
{
HubClientEvents.Log.Warning("Can't send message, connectionState= " + myHubClient.State);
}
}
if (key.ToUpper() == "R")
{
if (myHubClient.State == ConnectionState.Connected)
{
var hello = new HelloModel { Age = 10, Molly = "clientMessage" };
myHubClient.SendHelloObject(hello);
}
else
{
HubClientEvents.Log.Warning("Can't send message, connectionState= " + myHubClient.State);
}
}
if (key.ToUpper() == "T")
{
myHubClient.CloseHub();
HubClientEvents.Log.Informational("Closed Hub");
}
if (key.ToUpper() == "Z")
{
myHubClient.StartHub();
HubClientEvents.Log.Informational("Started the Hub");
}
if (key.ToUpper() == "C")
{
break;
}
}
}
}
}
The demo application uses OUT-OF-PROCCESS Slab logging to show the events. Here’s the client EventSource class.
using System.Diagnostics.Tracing;
namespace SignalRClientConsole.Logging
{
[EventSource(Name = "HubClientEvents")]
public class HubClientEvents : EventSource
{
public static readonly HubClientEvents Log = new HubClientEvents();
[Event(HubType.HubClientCritical, Message = "{0}", Level = EventLevel.Critical)]
public void Critical(string message)
{
if (IsEnabled()) WriteEvent(HubType.HubClientCritical, message);
}
[Event(HubType.HubClientError, Message = "{0}", Level = EventLevel.Error)]
public void Error(string message)
{
if (IsEnabled()) WriteEvent(HubType.HubClientError, message);
}
[Event(HubType.HubClientInformational, Message = "{0}", Level = EventLevel.Informational)]
public void Informational(string message)
{
if (IsEnabled()) WriteEvent(HubType.HubClientInformational, message);
}
[Event(HubType.HubClientLogAlways, Message = "{0}", Level = EventLevel.LogAlways)]
public void LogAlways(string message)
{
if (IsEnabled()) WriteEvent(HubType.HubClientLogAlways, message);
}
[Event(HubType.HubClientVerbose, Message = "{0}", Level = EventLevel.Verbose)]
public void Verbose(string message)
{
if (IsEnabled()) WriteEvent(HubType.HubClientVerbose, message);
}
[Event(HubType.HubClientWarning, Message = "{0}", Level = EventLevel.Warning)]
public void Warning(string message)
{
if (IsEnabled()) WriteEvent(HubType.HubClientWarning, message);
}
[Event(HubType.HubClientEvents, Message = "{0}", Level = EventLevel.Verbose)]
public void ClientEvents(string message)
{
if (IsEnabled()) WriteEvent(HubType.HubClientEvents, message);
}
}
}
Results:
Logs for client disconnected, reconnected:

Logs for server disconnected, reconnected:

Logs for client sent messages:

Logs for server sent messages:

Logs for client sent messages with no Server Hub:

Compared to WCF full duplex communication, it is very easy to implement a robust full-duplex messinging system. SignalR has also less problems with firewalls unlike WCF NET-TCP.
Links:
http://www.asp.net/signalr/overview/signalr-20/hubs-api/handling-connection-lifetime-events
