1

I have a service and want to add ability to interact with it via SignalR (on OWIN). There's plenty of examples of how to send a message to clients, but how do I receive messages from the client and forward them to the parent service?

e.g.

public class MyService
{
...
    public void LaunchSR()
    {
        _srWebApp = WebApplication.Start<SrStartup>("http://localhost:" + _signalRPortNumber.ToString());
        _srContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
    }

    public class SrStartup
    {
        public void Configuration(Owin.IAppBuilder app)
        {
            app.MapConnection("/raw", typeof(PersistentConnection), new ConnectionConfiguration { EnableCrossDomain = true });
            app.MapHubs();
        }
    }

    public class MyHub : Hub
    {
        public void SendToServer(string data)
        {
            //!! Don't have a reference to MyService instance,
            //!! so LocalCommand is out of scope
            LocalCommand(data, null);
        }
    }

    public void LocalCommand(data)
    {
        // Do Stuff in the main service context, accessing private objects etc.
    }
}

The code inside SendToServer() has the compile-time error:

"cannot access a non-static member of outer type MyService via nested type MyHub".

I understand why this happens, but don't know how to do this correctly.

Andy Brown
  • 18,961
  • 3
  • 52
  • 62

1 Answers1

2

The Hub instances are transient, so you need to create some kind of inversion. One way is to create something like a communication proxy in a singleton class that the two communicate through.

public class HubToServiceProxy {
  public static readonly HubToServiceProxy Instance = new HubToServiceProxy();
  private HubToServiceProxy() {}
  private MyService m_svc;
  // call this when the service starts up, from the service
  public void RegisterService (MyService svc) {
    // Be very careful of multithreading here, 
    //   the Proxy should probably lock m_svc on 
    //   every read/write to ensure you don't have problems.
    m_svc = svc;
  }
  // call this from the hub instance to send a message to the service
  public void SendCommandToService(string data) {
    m_svc.LocalCommand(data, null);
  }  
}

and then do:

public class MyService
{
  ...
  public MyService() {
    HubToServiceProxy.Instance.RegisterService(this);
  }
  ...
}

public class MyHub : Hub
{
  public void SendToServer(string data)
  {
    HubToServiceProxy.Instance.SendCommandToService(data);
  }
}

A few points to note:

  • RegisterService (MyService svc) could be removed entirely and replaced with an event to listen to: i.e. you could implement the Observer pattern through events (or as close as .NET does it): instead of recording the service reference in the communication proxy.
    • The service would listen for events on the communication proxy (on instantiation it would subscribe to the events on the communication proxy),
    • and the SendCommandToService method would raise the CommandFromClient event instead of calling the service.
  • If you want to stick with coupling the proxy and service:
    • RegisterService (MyService svc) should probably be RegisterService (ILocalCommand svc) where ILocalCommand is an interface that defines the LocalCommand method signature, and is implemented by MyService
    • You could use a static class, not a singleton (your call)
    • If you have multiple service instances you can keep a list of them and send the data to all of them, or some kind of keyed dictionary which allows you to filter where you send the data.
  • You could implement factories instead so that on instantiation of the Hub it calls a factory to get the communication proxy. This would help you perform DI based testing.
  • You probably wouldn't expose the service directly to the Hub through the proxy as this starts to break the rules of OO design such as encapsulation, cohesion and loose coupling.

There are a variety of ways to cut this, you need to choose the best one depending on your exact situation. The main thing to remember is that Hubs are transient, so they have to call some kind of non-transient server-side endpoint which will either: pass the message to the service; or return an object instance the hub can pass a message to for distribution. This initial call cannot, obviously, be an instance of another class, so static or singleton classes are your only option.

In reverse, this is what the GlobalHost.ConnectionManager.GetHubContext<MyHub>() method does to allow instances of your server classes to communicate with clients. It provides a non-transient endpoint for your own instances to call when they want to communicate with clients.

Community
  • 1
  • 1
Andy Brown
  • 18,961
  • 3
  • 52
  • 62
  • Thanks! Accurate code, easy to integrate, and concise yet thorough discussion of caveats and options. – swordfishBob Jun 07 '13 at 23:13
  • Minor edits: one of the summary sentences was ambiguous, and had originally left out an important point about encapsulation and coupling. – Andy Brown Jun 08 '13 at 07:33
  • It appears the cleanest approach is to use events. – swordfishBob Jun 08 '13 at 23:15
  • @swordfishBob. Quite possibly - it depends if your service is single instance, in which case events might be overkill, or you have multiple classes that need to receive calls from clients, in which case events would be more suitable. – Andy Brown Jun 09 '13 at 09:41