SignalR Messaging A more complete server with a Console Application

This post continues on from: SignalR Messaging with console server and client, web client, WPF client

Now the server will be developed with some error handling, proper logging, an IoC and an Client assembly with an interface and DTOs.

Code: https://github.com/damienbod/SignalRMessagingErrorHandling.git

To make it easier to use in a .NET client project, we can create an assembly which will be shared with all Hub clients. This assembly should contain all DTOs and interfaces for using the Hub.

We define 2 interfaces, one for receiving messages and one for sending messages.
IRecieveHubSync

using Damienbod.SignalR.IHubSync.Client.Dto;

namespace Damienbod.SignalR.IHubSync.Client
{
    public interface IRecieveHubSync
    {
        void Recieve_AddMessage(string name, string message);
        void Recieve_Heartbeat();
        void Recieve_SendHelloObject(HelloModel hello);
    }
}

ISendHubSync

using Damienbod.SignalR.IHubSync.Client.Dto;

namespace Damienbod.SignalR.IHubSync.Client
{
    public interface ISendHubSync
    {
        void AddMessage(string name, string message);
        void Heartbeat();
        void SendHelloObject(HelloModel hello);
    }
}

These 2 interfaces are not required by the client, but it makes it easy to implement a client for the Hub.

We also use Unity to as an IoC container. It is important that the Hub is created using the transient lifecycle manager from Unity. See signalr docs: Hub object lifetime

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Damienbod.SignalR.Host.Hubs;
using Damienbod.SignalR.IHubSync.Client;
using Damienbod.Slab.Services;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.Practices.Unity;

namespace Damienbod.SignalR.Host.Unity
{
    public class UnityConfiguration
    {
        #region Unity Container
        private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            RegisterTypes(container);
            return container;
        });

        public static IUnityContainer GetConfiguredContainer()
        {
            return container.Value;
        }
        #endregion

        public static IEnumerable<Type> GetTypesWithCustomAttribute<T>(Assembly[] assemblies)
        {
            foreach (var assembly in assemblies)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.GetCustomAttributes(typeof(T), true).Length > 0)
                    {
                        yield return type;
                    }
                }
            }
        }

        public static void RegisterTypes(IUnityContainer container)
        {
            var myAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("Damienbod")).ToArray();

            container.RegisterType<IHubLogger, HubLogger>(new ContainerControlledLifetimeManager());
            container.RegisterType<ISendHubSync, Service.SendHubSync>(new ContainerControlledLifetimeManager());

            // Hub must be transient see signalr docs
            container.RegisterType<HubSync, HubSync>(new TransientLifetimeManager());
            container.RegisterType<Hub, Hub>(new TransientLifetimeManager());
            container.RegisterType<IHubActivator, UnityHubActivator>(new ContainerControlledLifetimeManager());
        }
    }
}

The UnityHubActivator class is used to resolve IHubActivator interfaces. We need to implement this, so Hub instances can be resolved using Unity, or any other IoC…

using System;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.Practices.Unity;

namespace Damienbod.SignalR.Host.Unity
{
    public class UnityHubActivator : IHubActivator
    {
        private readonly IUnityContainer _container;

        public UnityHubActivator(IUnityContainer container)
        {
            _container = container;
        }

        public IHub Create(HubDescriptor descriptor)
        {
            if (descriptor == null)
            {
                throw new ArgumentNullException("descriptor");
            }

            if (descriptor.HubType == null)
            {
                return null;
            }

            object hub = _container.Resolve(descriptor.HubType) ?? Activator.CreateInstance(descriptor.HubType);
            return hub as IHub;
        }
    }
}

We can also add HubPipelineModule(s) to our Hub.
Here’s an Logging example:

using Damienbod.SignalR.Host.Unity;
using Damienbod.Slab;
using Damienbod.Slab.Services;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.Practices.Unity;

namespace Damienbod.SignalR.Host.Service
{
    public class LoggingPipelineModule : HubPipelineModule 
    {
        private readonly IHubLogger _slabLogger;

        public LoggingPipelineModule()
        {
            _slabLogger = UnityConfiguration.GetConfiguredContainer().Resolve<IHubLogger>();
        }

        protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
        {
            _slabLogger.Log(HubType.HubServerVerbose, "=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name);
            return base.OnBeforeIncoming(context);
        }
        protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context)
        {
            _slabLogger.Log(HubType.HubServerVerbose, "<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub);
            return base.OnBeforeOutgoing(context);
        } 
    }
}

And a ErrorHandling example:

using Damienbod.SignalR.Host.Unity;
using Damienbod.Slab;
using Damienbod.Slab.Services;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.Practices.Unity;

namespace Damienbod.SignalR.Host.Service
{
    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        private readonly IHubLogger _slabLogger;

        public ErrorHandlingPipelineModule()
        {
            _slabLogger = UnityConfiguration.GetConfiguredContainer().Resolve<IHubLogger>();
        }

        protected override void OnIncomingError(ExceptionContext ex, IHubIncomingInvokerContext context)
        {
            _slabLogger.Log(HubType.HubServerVerbose, "=> Exception " + ex.Error + " " + ex.Result);
            if (ex.Error.InnerException != null)
            {
                _slabLogger.Log(HubType.HubServerVerbose, "=> Inner Exception " + ex.Error.InnerException.Message);
            }
            base.OnIncomingError(ex, context);
        }
    }
}

On the server, only the ISendHubSync interface needs to be implemented as the Hub itself can be used to receive messages.

using Damienbod.SignalR.Host.Hubs;
using Damienbod.SignalR.IHubSync.Client;
using Damienbod.SignalR.IHubSync.Client.Dto;
using Damienbod.Slab;
using Damienbod.Slab.Services;
using Microsoft.AspNet.SignalR;

namespace Damienbod.SignalR.Host.Service
{
    public class SendHubSync : ISendHubSync
    {
        private readonly IHubLogger _slabLogger;
        private readonly IHubContext _hubContext;

        public SendHubSync(IHubLogger slabLogger)
        {
            _slabLogger = slabLogger;
            _hubContext = GlobalHost.ConnectionManager.GetHubContext<HubSync>(); 
        }
        
        public void AddMessage(string name, string message)
        {
            _hubContext.Clients.All.addMessage("MyHub", "ServerMessage");
            _slabLogger.Log(HubType.HubServerVerbose, "MyHub Sending addMessage");
        }

        public void Heartbeat()
        {
            _hubContext.Clients.All.heartbeat();
            _slabLogger.Log(HubType.HubServerVerbose, "MyHub Sending heartbeat");
        }

        public void SendHelloObject(HelloModel hello)
        {
            _hubContext.Clients.All.sendHelloObject(hello);
            _slabLogger.Log(HubType.HubServerVerbose, "MyHub Sending sendHelloObject");
        }
    }
}

Example of a Hub

using System.Threading.Tasks;
using Damienbod.SignalR.IHubSync.Client.Dto;
using Damienbod.Slab;
using Damienbod.Slab.Services;
using Microsoft.AspNet.SignalR;

namespace Damienbod.SignalR.Host.Hubs
{
    public class HubSync : Hub
    {
        private readonly IHubLogger _slabLogger;

        public HubSync(IHubLogger slabLogger)
        {
            _slabLogger = slabLogger;
        }

        public void AddMessage(string name, string message)
        {
            _slabLogger.Log(HubType.HubServerVerbose, "HubSync Sending AddMessage " + name + " " + message);
            Clients.All.addMessage(name, message);
        }

        public void Heartbeat()
        {
            _slabLogger.Log(HubType.HubServerVerbose, "HubSync Sending Heartbeat");
            Clients.All.heartbeat();
        }

        public void SendHelloObject(HelloModel hello)
        {
            _slabLogger.Log(HubType.HubServerVerbose, "HubSync Sending SendHelloObject " + hello.Molly + " " + hello.Age);
            Clients.All.sendHelloObject(hello);
        }

        public override Task OnConnected()
        {
            _slabLogger.Log(HubType.HubServerVerbose, "HubSync OnConnected" + Context.ConnectionId);
            return (base.OnConnected());
        }

        public override Task OnDisconnected()
        {
            _slabLogger.Log(HubType.HubServerVerbose, "HubSync OnDisconnected" + Context.ConnectionId);
            return (base.OnDisconnected());
        }

        public override Task OnReconnected()
        {
            _slabLogger.Log(HubType.HubServerVerbose, "HubSync OnReconnected" + Context.ConnectionId);
            return (base.OnReconnected());
        }
    }
}

Here’s the startup logic.

using System;
using Damienbod.SignalR.Host.Service;
using Damienbod.SignalR.Host.Unity;
using Damienbod.SignalR.IHubSync.Client;
using Damienbod.SignalR.IHubSync.Client.Dto;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Hosting;
using Microsoft.Owin;
using Microsoft.Practices.Unity;
using Owin;

[assembly: OwinStartup(typeof(Damienbod.SignalR.Host.Startup))]
namespace Damienbod.SignalR.Host
{
    public class Startup
    {
        private static ISendHubSync _myHub;

        public static void Start()
        {
            GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => new UnityHubActivator(UnityConfiguration.GetConfiguredContainer())); 
            GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
            GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule());
            
            var url = MyConfiguration.GetInstance().MyHubServiceUrl();
            _myHub = UnityConfiguration.GetConfiguredContainer().Resolve<ISendHubSync>();

            using (WebApp.Start(url))
            {

                Console.WriteLine("Server running on {0}", url);
                while (true)
                {
                    var key = Console.ReadLine();
                    if (key.ToUpper() == "W")
                    {
                        _myHub.AddMessage("Server", "addmessage sent from server");
                    }
                    if (key.ToUpper() == "E")
                    {
                        _myHub.Heartbeat();
                    }
                    if (key.ToUpper() == "R")
                    {
                        _myHub.SendHelloObject(new HelloModel { Age = 37, Molly = "pushed direct from Server " });
                    }
                    if (key.ToUpper() == "C")
                    {
                        break;
                    }
                }

                Console.ReadLine();
            }
        }

        public void Configuration(IAppBuilder app)
        {
            // Branch the pipeline here for requests that start with "/signalr"
            app.Map("/signalr", map =>
            {
                

                // Setup the CORS middleware to run before SignalR.
                // By default this will allow all origins. You can 
                // configure the set of origins and/or http verbs by
                // providing a cors options with a different policy.
                map.UseCors(CorsOptions.AllowAll);
                var hubConfiguration = new HubConfiguration
                {
                    // You can enable JSONP by uncommenting line below.
                    // JSONP requests are insecure but some older browsers (and some
                    // versions of IE) require JSONP to work cross domain
                    // EnableJSONP = true
                };
                // Run the SignalR pipeline. We're not using MapSignalR
                // since this branch already runs under the "/signalr"
                // path.

                hubConfiguration.EnableDetailedErrors = true;
                map.RunSignalR(hubConfiguration);
            });
        }
    }
}

In this server, SLAB OUT-OF-Process is used to log everything.
SigRHub1

With very little effort, a stable signalR Host can be created in a console or Windows service application (or IIS).

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

5 comments

  1. What’s up, just wanted to mention, I enjoyed this blog
    post. It was practical. Keep on posting!

  2. nainaigu · · Reply

    >In this server, SLAB OUT-OF-Process is used to log everything.
    i run the solution but i dose not see this logs.why? EventSource.IsEnabled() always return false.

  3. Hi nainaigu

    I’ll have a look at this in the holidays, the SLAB OUT-OF-PROCESS logging should work, I run it locally to see what is required.

    Greetings Damien

  4. “>In this server, SLAB OUT-OF-Process is used to log everything.
    i run the solution but i dose not see this logs.why? EventSource.IsEnabled() always return false.”
    I have also the same problem. Any hint to resolve it?

  5. Peter · · Reply

    For those not seeing the logs, you need to have an active event listener (such as PerfView) for IsEnabled to return true.
    Take a look at https://channel9.msdn.com/posts/introducing-semantic-logging.

Leave a comment

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