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