BackEnd: .NET 6, C#10, Azure Isolated Function project
NuGet: Microsoft.Azure.Functions.Worker.Extensions.SignalRService v1.7.0
How to send a message to a specific user or group?
I found that adding a value to the SignalRConnectionInfoInput.UserId is working but this class is sealed and I couldn't find a way to dynamicly add the UserId value. Here's the working Negotiate function that allow me to broadcast to all users:
[Function(nameof(Negotiate))]
public static HttpResponseData Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
[SignalRConnectionInfoInput(HubName = "MyHubName")] string connectionInfo)
{
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "application/json");
response.WriteString(connectionInfo);
return response;
}
I can send a message to all users with this BroadcastAll function:
[Function(nameof(BroadcastToAll))]
public static SignalRMessageAction BroadcastToAll([HttpTrigger(AuthorizationLevel.Anonymous,
"post", Route = nameof(BroadcastToAll))] HttpRequestData req)
{
using var bodyReader = new StreamReader(req.Body);
var body = bodyReader.ReadToEnd();
return new SignalRMessageAction("MyTargetName")
{
Arguments = new object[] { body },
};
}
But I didn't find a way to send it to specific user correctly other then using the UserId attribute hardcoded like in this Negociate function example which make it works:
[Function(nameof(Negotiate))]
public static HttpResponseData Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
[SignalRConnectionInfoInput(HubName = "MyHubName", **UserId = "12345"**)] string connectionInfo)
{
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "application/json");
response.WriteString(connectionInfo);
return response;
}
Then I can use the UserId property from the SignalRMessageAction class to specify the UserId = "12345" I want to send the message to and in the frontend, only this user will receive this message.
[Function(nameof(SendToUser))]
public static async Task<SignalRMessageAction> SendToUser(
[HttpTrigger(AuthorizationLevel.Anonymous, "post",
Route = nameof(SendToUser))] HttpRequestData req)
{
using var bodyReader = new StreamReader(req.Body);
var requestBody = await bodyReader.ReadToEndAsync();
return new SignalRMessageAction("MyTargetName")
{
Arguments = new object[] { requestBody },
UserId = "12345"
};
}
What I found is that when you set the UserId in the attribute, the accessToken returned by Azure SignalR Service, in the connectionInfo, contains a claim that identify the user: "asrs.s.uid": "12345"
Here's an example of my frontend code: FrontEnd: Angular, TypeScript, MSAL (@azure/msal-angular": "^2.3.0"), SignalR (@microsoft/signalr": "^7.0.5")
public ConnectSignalR(): void {
this.hubConnection = new signalR.HubConnectionBuilder().withUrl(this.basePath).build();
this.hubConnection
.start()
.then(() => console.log('Connection started'))
.catch((err) => console.log('Error while starting connection: ' + err));
this.hubConnection.on('MyTargetName', (mySignalRMessage: string) => {
console.log(mySignalRMessage)
});
}
public DisconnectSignalR(): void {
if (this.hubConnection) {
this.hubConnection.stop();
}
}
Thank you for your help and I hope it can helps other in the future.
I tried to add the UserId = "{headers.x-ms-client-principal-id}" like what the documentation says at section Authenticated tokens but as I'm using MSAL, I don't have this header and I would like to set the value myself in the backend. Doc
Just found a solution Here, hope this helps others: First, you need to add this NuGet: Microsoft.Azure.SignalR.Management
[Function("negotiate")]
public async Task<HttpResponseData> Negotiate([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
var negotiateResponse = await MessageHubContext.NegotiateAsync(new() { UserId = "12345" });
var response = req.CreateResponse();
await response.WriteAsJsonAsync(negotiateResponse, JsonObjectSerializer);
return response;
}