aspnet / AzureSignalR-samples

Code samples for Azure SignalR
MIT License
330 stars 389 forks source link

Sending messages to specific group is not working in Azure function based SignalR service #145

Open ravisankarchalamalasetty opened 3 years ago

ravisankarchalamalasetty commented 3 years ago

Azure SignalR service + Azure function based hubs

I am just wondering if sending message to specific group within connected clients is working at all for anyone? Broadcast is working well though! Likewise, sending message to a specific user within connected clients is also not working just like sending to a specific group of users. Not sure if I am missing something in this context. Any leads would be appreciated!

    [FunctionName("SendMessageToSpecificGroup")]
    public Task SendMessageToSpecificGroup(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
        [SignalR(HubName = "maintenancehub")] IAsyncCollector<SignalRMessage> signalRMessages)
    {
        var inputParameters = JsonConvert.DeserializeObject<GroupDTO>(message.ToString());

        return signalRMessages.AddAsync(
            new SignalRMessage
            {
                GroupName = inputParameters.GroupName,
                Target = "GroupSpecificMessageTargetCaller",
                Arguments = new[] { message }
            });
    }

    [FunctionName("BroadcastMessage")]
    public Task BroadcastMessage(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
        [SignalR(HubName = "maintenancehub")] IAsyncCollector<SignalRMessage> signalRMessages,
        ILogger logger)
    {

        logger.LogInformation($"message to be sent is:" + message);
        return signalRMessages.AddAsync(
            new SignalRMessage
            {
                Target = "BroadcastMessageTargetCaller",
                Arguments = new[] { message }
            });
    }

The target method i.e. 'GroupSpecificMessageTargetCaller' is never triggering in the client side javascript. On the contrary broadcast message to all connected clients is working well though. Target method 'BroadcastMessageTargetCaller' in the html client is getting triggered in broadcast case.

Client side code

connection.on('BroadcastMessageTargetCaller', BroadcastMessageTargetCaller);
connection.on('GroupSpecificMessageTargetCaller', GroupSpecificMessageTargetCaller);

let counter = 0;
function BroadcastMessageTargetCaller(message) {
  message.id = counter++; // vue transitions need an id
  data.messages.unshift(message);
}

let groupCounter = 0;
function GroupSpecificMessageTargetCaller(message) {
  message.id = groupCounter++; 
  data.groupSpecificMessages.unshift(message);
}

Attaching complete source code for any further investigation. Thanks in advance!

AzureFunctonBasedSignalRService.zip

Server code-->

namespace MyFirstAzureFunction { public class MaintenanceHub : ServerlessHub {

    Dictionary<string, string> groupsDictionary = new Dictionary<string, string>();

    #region "SignalR Triggers"

    [FunctionName("negotiate")]
    public  SignalRConnectionInfo Negotiate(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
        ClaimsPrincipal claimsPrincipal,
        [SignalRConnectionInfo(HubName = "maintenancehub")] SignalRConnectionInfo connectionInfo, ILogger log)
    {
        return connectionInfo;
    }

    [FunctionName(nameof(OnConnected))]
    public async Task OnConnected([SignalRTrigger] InvocationContext invocationContext, ILogger logger)
    {
        await Clients.All.SendAsync("ClientMethod", invocationContext.ConnectionId);
        logger.LogInformation("Connection Id of the client is:" + invocationContext.ConnectionId);
    }

    [FunctionName(nameof(OnDisconnected))]
    public void OnDisconnected([SignalRTrigger] InvocationContext invocationContext, ILogger logger)
    {
        logger.LogInformation($"{invocationContext.UserId} has disconnected");
    }

    #endregion

    #region "HTTP Triggers"

    [FunctionName("AddUserToGroup")]
    public Task AddUserToGroup(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object userDetails,
        ClaimsPrincipal claimsPrincipal,
        [SignalR(HubName = "maintenancehub")] IAsyncCollector<SignalRGroupAction> signalRGroupActions,
        ILogger logger)
    {

        var loggedInUserDetails = JsonConvert.DeserializeObject<AddUserToGroupDTO>(userDetails.ToString());

        string[] userIdentifierData = loggedInUserDetails.UserName.Split(".", StringSplitOptions.None);

        if (!groupsDictionary.ContainsKey(userIdentifierData[0]))
        {
            groupsDictionary.Add(userIdentifierData[0], userIdentifierData[0]);
            logger.LogInformation($"New group is created with the name: " + userIdentifierData[0]);
        }

        logger.LogInformation($"User added under " + loggedInUserDetails.UserName + " group");
        //var userIdClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier);

        return signalRGroupActions.AddAsync(
            new SignalRGroupAction
            {
                UserId = loggedInUserDetails.UserName,//loggedInUserDetails.UserName,
                GroupName = userIdentifierData[0],
                Action = GroupAction.Add
            });
    }

    [FunctionName("BroadcastMessage")]
    public Task BroadcastMessage(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
        [SignalR(HubName = "maintenancehub")] IAsyncCollector<SignalRMessage> signalRMessages,
        ILogger logger)
    {

        logger.LogInformation($"message to be sent is:" + message);
        return signalRMessages.AddAsync(
            new SignalRMessage
            {
                Target = "BroadcastMessageTargetCaller",
                Arguments = new[] { message }
            });
    }

        //await Clients.All.SendAsync("BroadcastMessageTargetCaller", new[] { message });

    [FunctionName("SendMessageToSpecificGroup")]
    public Task SendMessageToSpecificGroup(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
        [SignalR(HubName = "maintenancehub")] IAsyncCollector<SignalRMessage> signalRMessages)
    {
        var inputParameters = JsonConvert.DeserializeObject<GroupDTO>(message.ToString());

        return signalRMessages.AddAsync(
            new SignalRMessage
            {
                GroupName = inputParameters.GroupName,
                Target = "GroupSpecificMessageTargetCaller",
                Arguments = new[] { message }
            });
    }

    //await Clients.Group(inputParameters.GroupName).SendAsync("GroupSpecificMessageTargetCaller", new[] { message});

    [FunctionName("SendMessageToSpecificUser")]
    public Task SendMessageToSpecificUser(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
        [SignalR(HubName = "maintenancehub")] IAsyncCollector<SignalRMessage> signalRMessages,
        ILogger logger
        )
    {
        var inputParameters = JsonConvert.DeserializeObject<UserDTO>(message.ToString());
        return signalRMessages.AddAsync(
            new SignalRMessage
            {
                UserId = inputParameters.UserId,
                Target = "UserSpecificMessageTargetCaller",
                Arguments = new[] { message }
            });
    }

    #endregion

}

#region "Helper classes"

public class AddUserToGroupDTO
{

    public string UserName { get; set; }

}

public class GroupDTO
{

    public string Sender { get; set; }

    public string Text { get; set; }

    public string GroupName { get; set; }

}

public class UserDTO
{

    public string Sender { get; set; }

    public string Text { get; set; }

    public string UserId { get; set; }

}

public class messageDTO
{
    public string Sender { get; set; }
    public string Text { get; set; }
}

#endregion

}

zackliu commented 3 years ago

Found some bugs in your codes, you're adding user to group with the user name and group name you typed in but sending message to group and user with a specific name SIL01D

if(data.username)
{
  addUserToGroup(data.username);
}
...  
function addUserToGroup(sender) {
      return axios.post(`${apiBaseUrl}/api/addusertogroup`, {
        UserName: sender
      }).then(
        resp => { resp.data;
                  console.log('response received from /api/addusertogroup POST call -->' + resp.data);
                }
        ).catch((e)=> console.log(e));
    }

You passed in data.username for adding user to group but use a specific name for sending messages to group

const tempGroupName = "SIL01D";
...
function SendMessageToSpecificGroup(sender, messageText) {
      return axios.post(`${apiBaseUrl}/api/sendmessagetospecificgroup`, {
        sender: sender,
        text: messageText,
        groupName: tempGroupName,
      }).then(
        resp => { resp.data;
                  console.log('response received from /api/sendmessagetospecificgroup POST Call -->' + resp.data);
                }
        ).catch((e)=> console.log(e));
    }

So, first you need to change them to the same one. I suggest using tempGroupName and tempUserId to make it easier.

Then, a client connection's userId is defined in negotiation, that means you needs to specify userId in Negotiate function like this. Or, when you try to send to user or add user to group, the userId can't match to your connection.

[FunctionName("negotiate")]
        public  SignalRConnectionInfo Negotiate(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
            ClaimsPrincipal claimsPrincipal,
            [SignalRConnectionInfo(HubName = "maintenancehub", UserId = "SIL01D.Ravi")] SignalRConnectionInfo connectionInfo, ILogger log)
        {
            return connectionInfo;
        }

Besides, you have a typo in SaaSApplication.html. The estateSpecificMessages should be groupSpecificMessages

  let groupCounter = 0;
  function GroupSpecificMessageTargetCaller(message) {
    message.id = groupCounter++; 
    data.estateSpecificMessages.unshift(message);
  }

After these fixes, I finished to add user to group and send messages to user image

ACalderwood93 commented 3 years ago

@zackliu

Replying long after you did but thanks for your help. I've been looking through docs for serverless signalR for a bit now and your post was the first time i've seen mention of setting the UserID in the negotiation. ( I probably just missed it before) But thanks, this has really helped me out.