0

We are evaluating how to send messages to connected clients via SignalR. Our application is published in Azure, and has multiple instances. We are able to successfully pass messages to clients connected to the same instance, but not other instances.

We initially were looking at ServiceBus, but we (perhaps mistakenly) found out that AzureSignalR should basically be a service bus that handles all of the backend stuff for us.

We set up signalR in Startup.cs such as:

public void ConfigureServices(IServiceCollection services)
{
    var signalRConnString = Configuration.GetConnectionString("AxiomSignalRPrimaryEndPoint");
    services.AddSignalR()
    .AddAzureSignalR(signalRConnString)
    .AddJsonProtocol(options =>
   {
       options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
   });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
 app.UseAzureSignalR(routes =>
 {
    routes.MapHub<CallRegistrationHub>("/callRegistrationHub");
    routes.MapHub<CaseHeaderHub>("/caseHeaderHub");
    routes.MapHub<EmployeesHub>("/employeesHub");
 });
}

Issue
We have to store some objects that should probably be on the service bus, and not stored in an individual instance; However, I am unsure of how to tell the hub that the objects should be on the bus and not internal to that specific instance of the hub, as below:

public class EmployeesHub : Hub
{
    private static volatile List<Tuple<string, string, string,string, int>> UpdateList = new List<Tuple<string, string, string,string,int>>();
    private static volatile List<Tuple<string, int>> ConnectedClients = new List<Tuple<string, int>>();
}

We have functions that need to send messages to all connected clients that are looking at the current record regardless of in what instance they reside:

public async void LockField(string fieldName, string value, string userName, int IdRec)
{
    var clients = ConnectedClients.Where(x => x.Item1 != Context.ConnectionId && x.Item2 == IdRec).Select(x => x.Item1).Distinct().ToList();
    clients.ForEach(async x =>
    {
        await Clients.Client(x).SendAsync("LockField", fieldName, value, userName, true);
    });
    if (!UpdateList.Any(x=> x.Item1 == Context.ConnectionId && x.Item3 == fieldName && x.Item5 == IdRec))
    {               
        UpdateList.Add(new Tuple<string, string, string,string,int>(Context.ConnectionId,userName, fieldName, value, IdRec));
    }
}

This is not working for different instances (which makes sense, because each instance will have its own objects.. However, we were hoping that by using AzureSignalR instead of SignalR (AzureSignalR conn string has an endpoint to the Azure service) that it would handle the service bus functionality for us. We are not sure what steps to take to get this functioning correctly.

Thanks.

juunas
  • 54,244
  • 13
  • 113
  • 149
Aaron
  • 1,313
  • 16
  • 26

2 Answers2

0

The reason for this issue is that I was preemptively attempting to limit message traffic. I was attempting to only send messages to clients that were looking at the same record. However, because my objects were instance-specific, it would only grab the connection IDs from the current instance's object.

Further testing (using ARR affinity) proves that on a Clients.All() call, all clients, including those in different instances, receive the message.

So, our AzureSignalR setup appears to be correct.

Current POC Solution - currently testing
-When a client registers, we will broadcast to all connected clients "What field do you have locked for this Id?"
-If client is on a different Id, it will ignore the message.
-If client does not have any fields locked, it will ignore the message.
-If client has a field locked, it will respond to the message with required info.
-AzureSignalR will then rebroadcast the data required to perform a lock.

This increases message count, but not significantly. But it will resolve the multiple instances holding different connected ClientIds issue.

Aaron
  • 1,313
  • 16
  • 26
0

Just a thought, but have you tried using SignalR Groups? https://learn.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.2#groups-in-signalr

You could try creating a group for each combination of IdRec and fieldName and then just broadcast messages to the group. This is the gist of how I think your LockField function might look:

public async void LockField(string fieldName, string value, string userName, int IdRec)
{
    string groupName = GetGroupName(IdRec, fieldName);
    await Clients.Group(groupName).SendAsync("LockField", fieldName, value, userName, true);
    await this.Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}

You could implement the GetGroupName method however you please, so long as it produces unique strings. A simple solution might be something like

public string GetGroupName(int IdRec, string fieldName)
{
    return $"{IdRec} - {fieldName}";
}
supwar
  • 222
  • 2
  • 7
  • We wound up going with AzureSignalR. It's basically a servicebus designed for it. It works great. – Aaron Jan 31 '19 at 01:37