Wednesday, January 28, 2015

Making SignalR Hub’s internal

 

Today I ran into an interesting issue. In my current project, we’re making a web application out of discrete modules. These modules should not expose their internal workings to the outside, to minimize assembly versioning issues. So we’ll ILMerge all non-public dependencies into the assembly.

One example is using SignalR. One of my modules uses signalr to notify clients. This is an internal implementation detail of my module, so it shouldn’t be exposed to the outside world.

The nice thing of this approach is, we can have multiple versions of the same assembly running in the same appdomain. So if I want another module to use a different version of signalr, there’s no issue at all.

But then I ran into a snag. SignalR requires it’s hubs to be public. But, if my hub is public, I’m exposing SignalR and I can’t ILMerge this dependency.

So how to solve this?  After a bit of digging, it’s the IHubDescriptorProvider that finds the hubs. I’ve created a provider that finds ‘only’ my hub, but it’s not difficult to see how to fetch all hubs if you want to.

   1: internal class InternalHubDescriptorProvider : IHubDescriptorProvider
   2: {
   3:    private HubDescriptor _notificationsHubDescriptor;
   4:  
   5:    public InternalHubLocator()
   6:    {
   7:        // create the descriptor that's used to find the notifications hub
   8:        _notificationsHubDescriptor = new HubDescriptor()
   9:        {
  10:            HubType = typeof(NotificationsHub),
  11:            Name = GetHubTypeName(typeof(NotificationsHub))
  12:        };
  13:    }
  14:  
  15:    private static string GetHubTypeName(Type type)
  16:    {
  17:        var lastIndexOfBacktick = type.Name.LastIndexOf('`');
  18:        if (lastIndexOfBacktick == -1)
  19:        {
  20:            return type.Name;
  21:        }
  22:        else
  23:        {
  24:            return type.Name.Substring(0, lastIndexOfBacktick);
  25:        }
  26:    }
  27:    /// <summary>
  28:    /// Retrieve all avaiable hubs.
  29:    /// </summary>
  30:    /// <returns>
  31:    /// Collection of hub descriptors.
  32:    /// </returns>
  33:    public IList<HubDescriptor> GetHubs()
  34:    {
  35:        return new []{_notificationsHubDescriptor};
  36:    }
  37:  
  38:    /// <summary>
  39:    /// Tries to retrieve hub with a given name.
  40:    /// </summary>
  41:    /// <param name="hubName">Name of the hub.</param><param name="descriptor">Retrieved descriptor object.</param>
  42:    /// <returns>
  43:    /// True, if hub has been found
  44:    /// </returns>
  45:    public bool TryGetHub(string hubName, out HubDescriptor descriptor)
  46:    {
  47:        if(string.Equals(hubName, _notificationsHubDescriptor.Name, StringComparison.CurrentCultureIgnoreCase))
  48:        {
  49:            descriptor = _notificationsHubDescriptor;
  50:            return true;
  51:        }
  52:        descriptor = default(HubDescriptor);
  53:        return false;
  54:    }
  55: }

 


One little caveat that had me pulling out my hair. On the hubdescriptor, don’t specify the ‘NameSpecified’ flag, unless you actually have the HubNameAttribute on your hub. Else it will silently eat your outbound messages.

2 comments: