In the first part of this blog post (which can be found here), I described the steps required to build a custom NServiceBus Transport. In my samples I made use of a proxy to a SignalR Hub. On this Hub I defined a two methods and a “event” that still need implementation. A quick overfew:
_proxy = connection.CreateHubProxy("NServiceBusHub"); _proxy.Invoke("RegisterQueue", address); _proxy.Invoke("Send", message, address); _proxy.On("receive", OnReceive);
Hub
So in short I will need a RegisterQueue method that excepts a parameter of the type Address, a Send method that excepts a TransportMessage and an Address as parameters and I will need to Invoke a receive on the client to which I will supply a TransportMessage.
Let’s start by creating an empty hub.
[HubName("NServiceBusHub")] public class NServiceBusHub : Hub { }
Implementing a SignalR hub is as easy as just creating a class that derives from Hub. Using the HubNameAttribute you can give the hub a name under which it can be found when a client invokes the CreateHubProxy method. In my case the attribute is rather redundant, SignalR will use the classname as hub name if the HubNameAttribute is omitted.
There are a few ways in which the handling of messages can be implemented. For this blog I chose the simplest solution: map every address to a group and send any message you receive directly to that group. This does have the downside that you lose any temporal decoupling, as every service has to be running and be connected at the time a message is sent.
Let’s see what this means for the RegisterQueue method.
public void RegisterQueue(Address address) { Groups.Add(Context.ConnectionId, address.ToString()); }
Groups work pretty much like this: you invoke the Add method with a connection id and a group name (in this case the address) and SignalR will check if there is group with that name and then add the connection id to it. If there is no group with that name, it will create one.
With there registering done, all that is left to do is to Send the messages:
public void Send(TransportMessage message, Address address) { message.Id = Guid.NewGuid().ToString(); Clients.Group(address.ToString()).receive(message); }
Something that is of the utmost importance for NServiceBus is for every message to have a unique id. In it’s default configuration NServiceBus uses MSMQ and uses the ids given out by MSMQ. Since I’m overriding the default configuration by replacing MSMQ for SignalR, I will have to give out Ids myself. In my case the ids do not need to have any additional meaning, so a Guid will suffice.
In order to send the message to the intended recipient, I only have to find the right group based on the address and invoke a receive method (the Group returns a dynamic class, so I can call any method and hope the client listens to it). Note that I always have a single address to which I will dispatch the message, regardless of whether it’s a command or event. All subscription logic still resides in NServiceBus!
Hosting
Hosting this hub is rather straight forward. Just include the class in any webproject and implement the following (SignalR) logic in the Global.asax.
public class Global : System.Web.HttpApplication { protected void Application_Start() { RouteTable.Routes.MapHubs(); } }
Considerations
And there you have it, a SignalR hub matching the previously build NServiceBus transport. There are however a few things to take into consideration.
While you get the benefit of using NServiceBus in your clients there are some mayor downsides to the above implementation:
– There is no durability , something NServiceBus relies heavily on
– There is no transactional support
– There is no guaranteed delivery
Of course you can remedy some of these issues to a certain extend by modifying and expanding the logic of the queue and hub so they persist messages in case the recipient is not available.
—
While this SignalR transport is a viable option in a project like mine, where events are continuously published to every client and it does’t matter if one or two of them never arrive, it might not be as suitable for long running business processes. As such it is important to weigh your options and their consequences before committing to SignalR Transport.