Adventures of integrating SignalR into an existing web application

Signal-R Code

See Previous Adventure: Adventures in learning SignalR

 After making all the reference additions, code changes to integrate SignalR into our existing web application, I ran into some new errors and issues.

 I have an existing component that I’m trying to call so I made a wrapper class that Inherits Hub, created a property from the existing component and then created the wrapper methods to expose the methods from the component’s class.  I also created some messaging events in the existing component that I want to send back to the client and that is when I hit my first roadblock:  

Issue: Using a Hub instance not created by the HubPipeline is unsupported.

Solution:
How to call client methods and manage groups from outside the Hub class

“To call client methods from a different class than your Hub class, get a reference to the SignalR context object for the Hub and use that to call methods on the client or manage groups. “

Private HubContext As IHubContext = Nothing

Public Sub New()
    HubContext = GlobalHost.ConnectionManager.GetHubContext(Of [Name of Hub Class])()
End Sub

 To send the client a message using the hub context the syntax is: HubContext.Clients.All.displayMessage(message)

This opens up a new issue:

When I was working on the sample application I was able to use this syntax: Clients.Caller.displayMessage(message)

to send the message to the caller (and not everyone connected).

I only want to send messages to the current user that is executing a task on the page and not everyone that is on the page.

Solution:

Send messages using the syntax:

HubContext.Clients.Client(CurrentConnectionID).displayMessage(message)

To capture the current connection ID the hub class has the following methods that can be overridden, using the OnConnected method and store the connectionID of the context object.

OnConnected()

OnDisconnected()

OnReconnected()

Called when the connection connects to this hub instance.

Called when a connection disconnects from this hub instance.

Called when the connection reconnects to this hub instance.

To persist the connections many samples used a shared ConcurrentDictionary to store and retrieve the connection id by a user name. 

Shared ClientConnections As New ConcurrentDictionary(Of String, String)

    ''' <summary>
    ''' Called when the connection connects to this hub instance.
    ''' </summary>
    ''' <returns>
    ''' A <see cref="T:System.Threading.Tasks.Task" />.
    ''' </returns>
    Public Overrides Function OnConnected() As Task
        CurrentUserName = Context.User.Identity.Name
        CurrentConnectionID = Context.ConnectionId
        ClientConnections.AddOrUpdate(Context.User.Identity.Name, Context.ConnectionId, Function(key, CurrentUserName) CurrentConnectionID)

        Return MyBase.OnConnected()
    End Function

Now that we have the current connection id stored for a specific user we can send the messages by retrieving the connection ID from the ConcurrentDictionary by the username:

CurrentConnectionID = ClientConnections(CurrentUserName)
HubContext.Clients.Client(CurrentConnectionID).displayMessage(message)

 

Other issues encountered during the integration process:

Issue: There was a lag in receiving messages and eventually messages came through.

Using Fiddler there was a 500 Internal Server Error (text/html) occurring.

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below

 [MissingMethodException: No parameterless constructor defined for this object.]

   System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean&amp; canBeCached, RuntimeMethodHandleInternal&amp; ctor, Boolean&amp; bNeedSecurityCheck) +0

   System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark&amp; stackMark) +113

   System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark&amp; stackMark) +232

   System.Activator.CreateInstance(Type type, Boolean nonPublic) +83

   System.Activator.CreateInstance(Type type) +66

   Microsoft.AspNet.SignalR.Hubs.DefaultHubActivator.Create(HubDescriptor descriptor) +84

   Microsoft.AspNet.SignalR.Hubs.DefaultHubManager.ResolveHub(String hubName) +27

   Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, StateChangeTracker tracker, Boolean throwIfFailedToCreate) +422

   Microsoft.AspNet.SignalR.Hubs.HubDispatcher.OnReceived(IRequest request, String connectionId, String data) +329

   Microsoft.AspNet.SignalR.&lt;&gt;c__DisplayClassc.&lt;ProcessRequest&gt;b__7() +34

   Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(Func`1 func) +28

Solution:

In the HUB class add a parameterless constructor.

Issue: Connection to the hub is slow, constantly reconnecting, and finally disconnects.

Adding these connection events I could see when the connection was slow or reconnecting.   

$.connection.hub.connectionSlow(function () {
            $("#impMsg").html('Slow connection to the hub has been detected.');
        });

        $.connection.hub.reconnecting(function () {
            tryingToReconnect = true;
            $("#impMsg").html('Reconnecting to the hub.');
        });

       $.connection.hub.disconnected(function () {
            if (tryingToReconnect) {
                $("#impMsg").html('Disconnected from the hub.');
            }

            if ($.connection.hub.lastError) { 
                alert("Disconnected. Reason: " + $.connection.hub.lastError.message); 
            }
        });

Solution:

  1. a) Verify that the connection id you are trying to communicate with is valid.   The connectionID can change especially on postbacks.
  2. b) Test for exceptions or NULL objects in the hub class.

Issue: A user can be logged into 2 different machines with the same user name.

Solution:

Possibly use the SessionID as the key or I assigned a GUID to each user logged in and use the user’s assigned GUID as the key in the concurrent dictionary.

The chat samples showed examples on how to send messages to unique users.

 

Conclusion:

Creating the sample application helped me learn SignalR and with that, under my belt, it made it a little easier to tackle the new issues in integrating into our existing application.

Resources:

See Also