All posts by mlapaglia

SignalR with Load Balanced IIS and SQL Messaging Backplane

A requirement of a recent project was to have asynchronous communication between a set of load balanced servers and multiple web clients. SignalR has the ability to work across multiple servers using a SQL server backplane through the NuGet package. I found a good guide on asp.net‘s website to get the server up and running, but all of the examples involved sending messages to all users at the same time.

Clients.All.addNewMessageToPage(name, message);

In my scenario I needed to signal each user individually.

Clients can be sent to different servers behind the load balancer as they browse the site. Consider this scenario:

1. Client A makes a request to be added to the notification list.
2. The load balancer sends Client A to Server 1.
3. Server 1 registers Client A with itโ€™s local instance of SignalR.
4. Client B requests to notify Client A that some event has occurred.
5. The load balancer sends Client B to Server 2.
6. Server 2 is not aware of the connection between Server 1 and Client A, and the request fails.

With the SQL backplane all of the servers share the same connection list, allowing a message to be sent to a Client A from a Server 2, even though Client A registered with Server 1.

In order to send a message to a single client the server needs to know the specific connection ID to send the message to. SignalR doesn’t offer a way to store a connection ID “key value pair”, so I created one on my own.

In my Hub.cs file I added a method that runs during the client registration:

public void registerClient(long SpecificIdentifier)
{
   HubMethods.RegisterClient(SpecificIdentifier, Guid.Parse(Context.ConnectionId));
}

In the RegisterClient method I store the SignalR connection ID along with a identifier specific to my application. A table stores the ID, the SignalR connection ID, and a timestamp. A procedure was created to clear out the table nightly.

public static void RegisterClient(long SpecificIdentifier, Guid ConnectionID)
{
   using (dbEntities db = new dbEntities())
   {
      Notification notification = db.Notifications.Where(x => x.ID == SpecificIdentifier).FirstOrDefault();

      if (notification == null)
      {
         notification = new Notification()
         {
            ID = SpecificIdentifier,
            ConnectionID = ConnectionID,
            LastModified = DateTime.UtcNow,
         };

         db.Notifications.Add(notification);
      }
      else
      {
         notification.ID = SpecificIdentifier;
         notification.ConnectionID = ConnectionID;
         notification.LastModified = DateTime.UtcNow;
      }

      db.SaveChanges();
   }
}

With the SQL backplane all of the servers share the same connection list, allowing a message to be sent to a Client A from a Server 2, even though Client A registered with Server 1.

When it comes time to send a message to a client, the notification method simply looks up the SignalR connection ID based on the ID specific to our application. The SQL backplane gathers the required information automatically based on the connection ID and the message is sent.

public static void NotifyClient(long SpecificIdentifier, Hub.Statuses Status)
{
   using (dbEntities = new dbEntities ())
   {
      Notification notification = db.Notifications.Where(x => x.ID == SpecificIdentifier).FirstOrDefault();

      if (notification != null)
      {
         String message = String.Empty;

         if (Status== Hub.Statuses.Confirmed)
         {
            message = "Here's the message for confirmation";
         }
         else if (Status == Hub.Statuses.Failed)
         {
            message = "Here's the message for failure";
         }

         try
         {
            IHubContext notificationHub = GlobalHost.ConnectionManager.GetHubContext<Hub>();
            notificationHub.Clients.Client(notification.ConnectionID.ToString()).addMessage(message);
         }
         catch (Exception ex)
         {
            ex.LogToElmah();
         }
      }
      else
      {
         throw new Exception("Unable to find connection");
      }
   }
}

Reddit User in Need

I user from Reddit had some shipping issues with his 3D printer he bought off of an acquaintance. The printer was shipped completely assembled, which doesn’t bode well for any shipping carrier. A few of the pieces came broken when they arrived, leaving theaxeman21 stranded with no way to fix his issue.

I volunteered my Rostock Max’s services to get him his remaining parts. He came to the SeeMeCNC hangout while his pieces were being printed. I am happy to say his pieces are in the mail theaxeman21! Hope your printer can get up and running soon ๐Ÿ™‚

z3FIDzv