Client – server event aggregation with SignalR

Event aggregation is really a pattern i like, there is something elegant about firing events and subscribers can listen to them without knowing who fired them. This creates decoupled domains both in backends and frontends. At my latest customer we saw an increasing demand for pub / sub and immediate update. We fired events on the backend event bus and in the frontend we had SignalR Hubs that picked up the events and forwarded them to the clients. This caused duplicated and similar code both on the client and on the web server. I decided to create an abstraction layer between SignalR and the event aggregator. The result is a library called SignalR.EventAggregatorProxy

Proxy

I didn’t want to create a dependency to a certain service bus or event aggregator so the first step for a user of my library is to create a proxy. It’s done by implementing SignalR.EventAggregatorProxy.EventAggregation.IEventAggregator. It comes with one method Subscribe. The library calls this method and supplies a handler that you should call when events are fired. Example of a proxy class.

public class EventAggregatorProxy : IEventAggregator, IHandle<Contracts.Events.Event>
{
    private Action<object> handler;

    public EventAggregatorProxy(Caliburn.Micro.IEventAggregator eventAggregator)
    {
        eventAggregator.Subscribe(this);
    }

    public void Subscribe(Action<object> handler)
    {
        this.handler = handler;
    }

    public void Handle(Contracts.Events.Event message)
    {
        if (handler != null) //Events can come in before the subsriber is hooked up
            handler(message);
    }
}

I listen to any event implementing Contracts.Events.Event when one fires I forward it to the library using the handler. Do not forget to register the class with SignalR’s dependency resolver.

Strong typed events from JavaScript

I wanted to avoid magic strings for the event types and use strong types. I did this by creating my own Owin middleware that outputs all the Event types as a JavaScript. This requires for the user to supply my library with information about which events should be proxied. That is done through the standard SignalR / Owin configuration pipeline like

public static void Configuration(IAppBuilder app)
{
    app.MapSignalR();
    app.MapEventProxy<Contracts.Events.Event>();
}

My library will render all classes that inherits from Contracts.Events.Event as JavaScript objects.

Client API

Now that we have client side proxy objects of the server-side events we need a API for the client to subscribe / unsubscribe to events.

signalR.eventAggregator.subscribe(SignalR.EventAggregatorProxy.Demo.Contracts.Events.StandardEvent, this.onEvent, this);

We use the subscribe function, first we tell it which event we want to listen to, note that it’s not supplied as a string but rather the reference to the constructor for the specific event. Next argument is telling which function should handle the event and the last argument tells in which context do you want to be in when the event fires.

You can also use generic arguments like

signalR.eventAggregator.subscribe(SignalR.EventAggregatorProxy.Demo.Contracts.Events.GenericEvent.of("System.String"), this.onEvent, this);

The generic arguments has to be defined with a string sadly. This is because my Owin javascript proxy can’t resolve which types the client code uses as generic arguments.

When a connection dies all events will be unsubscribed, but you can also unsubscribe using client code.

signalR.eventAggregator.unsubscribe(this);

This will cause all subscriptions for a certain context to be unsubscribed. This is especially important when your site is a Single page application since no connections will disconnect by them self.

Security

You get a certain level of security out of the box, the client will only receive events that inherit from a special base class defined by you. Additionally you can have logic in your proxy that only forward certain events to the library. But this is not always enough. More often than not a client should only receive a selection of the events. This is done using the constraint API built into the library. Implement SignalR.EventAggregatorProxy.Constraint.EventConstraintHandler<TEvent>. It has one method called Allow.

bool Allow(TEvent message, string username, dynamic constraint)

Return true or false depending on if the user should receive this particular event. Notice the last argument (dynamic constraint)? It’s an object that the client can specify, like

signalR.eventAggregator.subscribe(MyApp.Events.MyEvent, this.onEvent, this, { id: 5 });

This will create a dynamic object on the server-side with a property id which in this case will have the value 5. I have supplied a generic base class that hides the dynamic object and let you use static declared types. Like

public class MyConstraintHandler : EventConstraintHandler<MyEvent, MyConstraint>
{
    public override bool Allow(MyEvent message, string username, MyConstraint constraint)
    {
        return message.Id == constraint.Id;
    }
}

This is very useful when you want a user to receive updates only for a entity with a certain Id.

Install using nuget

Server package
Install-Package SignalR.EventAggregatorProxy

Constraint part only (Good if you dont want depdecny to SignalR in Backend etc)
Install-Package SignalR.EventAggregatorProxy.Constraint

.NET Client
Install-Package SignalR.EventAggregatorProxy.Client.DotNet

Demo

You can find a MVC4 demo here

Project site here

43 comments

  1. Hi Anders,

    First, let me say that this is one of the coolest, most elegant, and useful libraries that I’ve ever used – congratulations on the fine work. I have a question for you: I have successfully implemented this in a current project (using Castle Windsor for IoC), and in my reference usage against SignalR 2.0.3, everything was working well. However, upon upgrading to SignalR 2.1.0, I am getting this perplexing error:

    System.MissingMethodException: Method not found: ‘Microsoft.AspNet.SignalR.Hubs.IHubConnectionContext Microsoft.AspNet.SignalR.IHubContext.get_Clients()’

    This occurs in the Handle() method of the EventAggregatorProxy class, i.e.

    public void Handle(BaseEvent message)
    {
    // Events can come in before the subscriber is hooked up
    if (_handler != null) { _handler(message); }
    }

    I could drop back to SignalR 2.0.3, but I was curious if you might happen to know why this might be occurring? (I am also able to see this behavior in your demo MVC 4 project). Many thanks, and have a great day!

    Allen

  2. Hi Anders,

    I think I posted my last comment maybe one hour too soon. šŸ™‚ From the SignalR 2.1 “breaking changes” document, it appears that:

    IHubConnectionContext changed to IHubConnectionContext

    It seems to be backwards-compatible when instantiated (as in the EventAggregator code) or usable as IHubConnectionContext (as in the unit tests). All I needed to do was update the SignalR .NET core libraries to version 2.1, rebuild, and re-deploy.

    Once again, thank you for such a great library!

    Regards,

    Allen

    1. Thanks for your kind words, I’m really glad you liked it!
      Yes, I noticed this just a few days ago but haven’t had time to fix it and deploy a new version, will do as soon as possible!

      /Anders

      1. Hi Anders,

        Just downloaded the new version via Nuget, and all is working well, thank you! Regards,

        Allen

  3. Hi Anders, I have been searching for something similar. My requirement is this, I want to be able to set a user as offline when viewing their profile without refreshing. Like this: when the user’s last connectionId is disconnected, I set the user as offline and update their profile without refresh.

    Now the problem is, one or n number of users may be viewing their profile at anytime. Is this possible using SignalR EventAggregatorProxy?

    1. Hi mark. You can use my library for the pub/sub parts, but currently it does not let you listen to Connection states on the server. You can however work around this by creating your own hub. Dont worry it will use the same connection as my library so will not cost you anything extra

          public class ConnectionListenerHub : Hub
          {
              private readonly IEventAggregator eventAggregator;
      
              public ConnectionListenerHub(IEventAggregator eventAggregator)
              {
                  this.eventAggregator = eventAggregator;
              }
      
              public override Task OnConnected()
              {
                  eventAggregator.Publish(new ConnectionStateChangedEvent(Context.ConnectionId, true));
      
                  return base.OnConnected();
              }
      
              public override Task OnDisconnected(bool stopCalled)
              {
                  eventAggregator.Publish(new ConnectionStateChangedEvent(Context.ConnectionId, false));
      
                  return base.OnDisconnected(stopCalled);
              }
          }
      

      From client you need to listen to a dummy event for it to trigger the connect and disconnect events on server

      this.hub = $.connection.connectionListenerHub;
      this.hub.client.dummy = function() {};
      

      See full demo here
      https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/tree/ConnectionStateExample

      1. Awesome! Just browsed the code and looks just like what I need. Just one final question, please suffer me! It looks like the client-side calls uses signalr generated proxy. It my case I do not use generated proxy but I create the hubs using this angularjs script: https://github.com/JustMaier/angular-signalr-hub

        Can I still use it? I am sorry I don’t know AngularJS much. I don’t have issues with the .Net side of things, just the javascript side of the equation (i.e. the client side).

        By the way this is the awesomest signalr extension I seen! Whether I am able to use it in this project or not this one is just super cool! We should push for Damian and co at SignalR to look at this library and find ways to integrate or something. I am doing that right away!

  4. Thank you for your kind words, please spread the word, its always fun if more people know about my library. I dont think it can be part of SignalR core, its too specialized for it, but it would be cool if it could move under their suit of software

    Yes I think you can use it if you create a proxy for it so that my code can run, if you check the javascript client code

    https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/blob/master/SignalR.EventAggregatorProxy.Client.JS/jquery.signalR.eventAggregator.js

    You need to proxy

    this.hub = $.connection.eventAggregatorProxyHub;
    this.hub.client.onEvent = this.onEvent.bind(this);
    
    $.connection.hub.reconnected(this.reconnected.bind(this));
    $.connection.hub.start().done(this.sendSubscribeQueue.bind(this));
    
    this.hub.server.unsubscribe(typeNames).done(this.sendSubscribeQueue.bind(this));
    
    this.hub.server.subscribe(temp, reconnected);
    

    The github page says ‘A handy wrapper for SignalR Hubs.’ that indicates that its just a wrapper around the hubs generated by the Signalr proxy script? If that is true my code should be able to run along side it.

    Just a note, if you use Angular you should create a wrapper around my client side library that marshals the controller scope for you. If you create such a wrapper I would be more than happy to include it.

  5. Hi, I think this library might be what I’m looking for, but I’m not sure. Here is my scenario:-

    I have a SignalR server (self-hosted) which talks to a hardware device. The hardware periodically produces data, which a BLL class publishes via an event aggregator. A SignalR Hub class subscribes to this message and simply forwards it on to clients:-

    Clients.All.SomethingHappened(msg);

    In the client (WPF), I subscribe to these SignalR messages and forward them on to any subscribers (again using an event aggregator):-

    _hubProxy.On(
    “SomethingHappened”,
    msg => { _eventAggregator.Publish(msg); });

    It works, but there are many different message types that I will eventually need to handle, and as you can see above there are several “hops” involved in getting the server-side event to the client-side subscribers.

    (It’s probably irrelevant but I use Prism’s EA on both server and client sides).

    Is your library designed to replace this sort of thing or have I misunderstood its purpose? I’m struggling to understand the documentation and samples, and “which bit goes where”. I’m probably being dumb but I’ve not been able to find an example of server-side code used to publish a message, nor how a .Net client would subscribe to it.

    Thanks in advance

    1. Hi, Andy.
      Yes its exactly this my library does, it listens to server side events through a proxy that you need to define, then client side (JavaScript or .NET) event aggregator will pick these up.

      On the server hook up a listener like this
      https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki/Handle-events-server-side

      Now the events will be forwarded to my library, since your server is self hosted and you do not have a javascript client you do not need to Hook up the event proxy but you need to Init the Bootstrapper calling

      Bootstrapper.Init<TBaseEvent>();

      TBaseEvent is a event that all your messages must inherit. Make sure your messages are defined in a seperate assembly that both server and WPF client can share.

      In your wpf client you hook up listeners like this
      https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki/.NET-Client

      You can load the demo .sln over that github for a complete demo
      https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy

      Please let me know if you have any additional question.

  6. Hi Anders,

    First of all thank you for this great article.
    Do you have eventually a working sample for a self-hosted Owin/Katana server using the SignalR.EventAggregatorProxy ?
    I tried to implement one, by using the SignalR dependency resolver, and the EventAggregator from the MVC4 Demo Server (from the Caliburn.Micro directory). Unfortunately the BuildManagerAssemblyLocator is throwing an exception at the call of the GetReferencedAssemblies method

    public IEnumerable GetAssemblies()
    {
    return BuildManager.GetReferencedAssemblies().Cast();
    }

    This is the exception:

    “This method cannot be called during the application’s pre-start initialization stage”

    Do you have any workaround for this problem ?

    Thank you in advance for your help.

      1. Hi Anders,
        Thank you for the link.
        Is it possible to use the SignalR DependencyResolver and override the AssemblyLocator ?
        The Boostrapper Init() method from SignalR.EventAggregatorProxy has “hardcoded” the usage of the BuildManagerAssemblyLocator.

  7. … one more question regarding the Subscribe() method in the EventProxy class. The username is extracted “hardcoded” from the User property of the HubCallerContext. In my case the client is a .NET console application, and the above User property is null.
    Do you see any workaround for that ?

    1. You can call Boostrapper Init() to setup the defaults, then override the settings you want like custom IAssemblyLocator. As I understand it the user of the hub context should be correct if you have setup your .NET user authentication etc correctly

      1. Ok, thanks. I solved the custom IAssemblyLocator, but I cannot solve the username problem. I am using a server generated token to authenticate the service calls. The server generates the token only after the user credentials from the client have been validated. The client uses this authentication token in all subsequent calls, Thats why in my case the User property of the HubCallerContext is always null.

      2. Cant you wait to subscribe until you know the user is authentication correctly? That way the Identity should be set correctly. If not it seems something is wrong, SignalR uses the built in pipeline for this.

      3. Btw, what is your client? If its the .NET client you need to set authentication on it

        eventAggregator = new EventAggregator()
           .Init("http://localhost", c => c.Credentials = CredentialCache.DefaultCredentials);
        
  8. Yes, I am testing also with .NET Client. Would be possible to send an access token when connecting to the EventAggregator SignalR hub ? On the Server side I would then add to the signalR pipeline in Owin a custom authentication Provider which checks the access token.
    Where does the ConnectionToken returned by the EventAggregator Proxy as result of /signalr/negotiate Client calls actually come from ?
    I would actually like to use the same bearer token for web api and signalr.

    1. We do nothing special here, if setup correctly SingalR should supply you with the Username. It sounds like there is something wrong with your setup. I have only tried forms auth and windows, but any type of authentication should work if you use the standard pipeline for it.

      As you can see here, https://www.asp.net/signalr/overview/security/hub-authorization It should just work

      public class SampleHub : Hub
      {
          public override Task OnConnected()
          {
              return Clients.All.joined(GetAuthInfo());
          }
      
          protected object GetAuthInfo()
          {
              var user = Context.User;
              return new
              {
                  IsAuthenticated = user.Identity.IsAuthenticated,
                  IsAdmin = user.IsInRole("Admin"),
                  UserName = user.Identity.Name
              };
          }
      }
      
  9. On the client side the user would send first its credentials (name, password) over a web api call and, if validated, get the access token as response.
    The same access token would then be sent as query string when connecting to the signalr hub.

  10. Is there any sample with the usage of the “Action<IHubConnection) configureConnection" optional parameter in the EventAggregator Init() method available ?

    1. The Hub does not need to know anything about authentication, if everything is setup correctly User.Identity should be set and everything should be good to go. I would try this in a separate project with a Simple Hub ,get it to work there first.

  11. The idea would be to use for the EventAggregator the HubConnection() constructor , where the query string can be added: public HubConnection(string url, string queryString)

    1. You must make sure that .NET is aware of the logged in user. How its done with your self hosted pipeline I have no idea, but just to prove my point, I did a quick Forms auth test,

  12. Thank you for your reply.
    I took a look into the SignalR EventAggregatorProxy code. Would be possible please to extend the implementation and add as option the queryString as additional parameter in the EventAggregator Init() method (or add a new InitExtended(…) method, to not break compatibilty) ? The queryString can be set only in the HubConnection constructor, the usage of the ā€œAction<IHubConnection) configureConnection" would not help in this case.

  13. It is separated, the extension I am asking for has actually nothing to do with a hack. There has already been foreseen a method to “adapt” the HubConnection through the the usage of the ā€œAction<IHubConnection) configureConnection". I was just asking to foresee also the possibility to set the queryString for the HubConnection, as this can be done only in the constructor.

    1. I’m sorry, I somehow missed that we talked about the client. Please open a issue on github and we can continue talking there so this blog post does not end up with a million comments

  14. Hi Anders

    I have using same concept where I have constraint event which filters on AgentId and from Angular JS I am using different agentIds to subscribe. Somehow when I open two browsers and both with different AgentIds some of messages from AgentId 1033 appearing in 1035 window. Could you please point me to right direction?

    Thanks

  15. Hi Anders

    I am using with Constraint events to allow based on AgentIds. My two browser instance subscribe with AgentIds e.g 1033 and 1035. But some how some messages from 1033 appearing in 1035 window. Could you please point to right direction where I am making mistake.

    Thanks

    1. My Allow method in Constraint handler is like this


      public override bool Allow(DiallerEvent message, ConstraintContext context, DiallerEventConstraint constraint)
      {
      return message.AgentId == constraint.AgentId;
      }

      1. Hi, skahashmi.
        If you on the client specifiy the Constraint correctly it should work, I have used this library in production for years without any problem so I doubt there is a bug. I can look into it though. Thanks for contacting me

  16. Hi Anders!
    Are you planing upgrading your package with latest versions of included packages?
    Regards Jonas

    1. Hi Jonas.
      I have plans on moving to .NET Core for the project. But I dont hava a client currently that needs the project and I dont have time to update it without good reason sadly.

      If you get around to update all the dependencies and everything works, please create a pull request. Keep in mind the project will not work with SignalR for .NET Core

      1. Hi Anders.

        Thanks for your reply! We will use MVC and .NET4.6.2 only. We try with the current package, if we encounter some issues, I’ll look into it, an update the included packages, and create a pull request.
        /Jonas

Leave a reply to Allen Racho Cancel reply