SignalR Messaging a complete client with a Console Application

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:
SignalRClientReconnected

Logs for server disconnected, reconnected:
SignalRServerReconnected

Logs for client sent messages:
ClientSentMessages

Logs for server sent messages:
ServerSentMessages

Logs for client sent messages with no Server Hub:
clientMessagesNoServerConnected

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

http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/introduction-to-signalr

http://signalr.net

http://www.asp.net/signalr/overview/signalr-20/hubs-api/handling-connection-lifetime-events

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: