admin管理员组

文章数量:1122832

My goal is to facilitate rpc between clients, where a client can be both the server and the client.

I have set on using the serverless Azure SignalR Service with an Azure Function for trigger binding.

The issue that I face right now is that I can't invoke those methods from the client. The SignalR trigger broadcast is not invoked.

My question is if I approach the problem right, I want to send messages to the SignalR Service, from which the azure function reacts to the connected listeners, but when connecting straight to the service i get 403 no matter what (I do not have managed identity turned on), do I have to mediate it via the function?

I have created a serverless Azure SignalR Service and an Azure Function.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.Functions.Worker.SignalRService;
using Microsoft.Extensions.Logging;

namespace DotnetIsolated_ClassBased
{
    [SignalRConnection("AzureSignalRConnectionString")]
    public class Functions : ServerlessHub<IChatClient>
    {
        private const string HubName = "<hub_name>"
        private readonly ILogger _logger;

        public Functions(IServiceProvider serviceProvider, ILogger<Functions> logger) : base(serviceProvider)
        {
            _logger = logger;
        }

        [Function(nameof(BroadcastToAll))]
        [SignalROutput(HubName = HubName, ConnectionStringSetting = "SignalRConnection")]
        public static SignalRMessageAction BroadcastToAll([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
        {
            using var bodyReader = new StreamReader(req.Body);
            return new SignalRMessageAction("newMessage")
            {
                // broadcast to all the connected clients without specifying any connection, user or group.
                Arguments = new[] { bodyReader.ReadToEnd() },
            };
        }

        [Function("negotiate")]
        public async Task<HttpResponseData> Negotiate([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
        {
            _logger.LogInformation($"Negotiating with user: {req.Headers.GetValues("userId").FirstOrDefault()}");

            var negotiateResponse = await NegotiateAsync(new() { UserId = req.Headers.GetValues("userId").FirstOrDefault() });
            var response = req.CreateResponse();
            await response.WriteBytesAsync(negotiateResponse.ToArray());
            return response;
        }

        [Function("OnConnected")]
        public Task OnConnected([SignalRTrigger(HubName, "connections", "connected")] SignalRInvocationContext invocationContext)
        {
            invocationContext.Headers.TryGetValue("Authorization", out var auth);
            _logger.LogInformation($"{invocationContext.ConnectionId} has connected");
            return Clients.All.newConnection(new NewConnection(invocationContext.ConnectionId, auth));
        }

        [Function("Broadcast")]
        public Task Broadcast(
        [SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
        {
            _logger.LogInformation($"Broadcasting message: {message}");
            return Clients.All.newMessage(new NewMessage(invocationContext, message));
        }

        [Function("SendToGroup")]
        public Task SendToGroup([SignalRTrigger(HubName, "messages", "SendToGroup", "groupName", "message")] SignalRInvocationContext invocationContext, string groupName, string message)
        {
            return Clients.Group(groupName).newMessage(new NewMessage(invocationContext, message));
        }

        [Function("SendToUser")]
        public Task SendToUser([SignalRTrigger(HubName, "messages", "SendToUser", "userName", "message")] SignalRInvocationContext invocationContext, string userName, string message)
        {
            return Clients.User(userName).newMessage(new NewMessage(invocationContext, message));
        }

        [Function("SendToConnection")]
        public Task SendToConnection([SignalRTrigger(HubName, "messages", "SendToConnection", "connectionId", "message")] SignalRInvocationContext invocationContext, string connectionId, string message)
        {
            return Clients.Client(connectionId).newMessage(new NewMessage(invocationContext, message));
        }

        [Function("JoinGroup")]
        public Task JoinGroup([SignalRTrigger(HubName, "messages", "JoinGroup", "connectionId", "groupName")] SignalRInvocationContext invocationContext, string connectionId, string groupName)
        {
            return Groups.AddToGroupAsync(connectionId, groupName);
        }

        [Function("LeaveGroup")]
        public Task LeaveGroup([SignalRTrigger(HubName, "messages", "LeaveGroup", "connectionId", "groupName")] SignalRInvocationContext invocationContext, string connectionId, string groupName)
        {
            return Groups.RemoveFromGroupAsync(connectionId, groupName);
        }

        [Function("JoinUserToGroup")]
        public Task JoinUserToGroup([SignalRTrigger(HubName, "messages", "JoinUserToGroup", "userName", "groupName")] SignalRInvocationContext invocationContext, string userName, string groupName)
        {
            return UserGroups.AddToGroupAsync(userName, groupName);
        }

        [Function("LeaveUserFromGroup")]
        public Task LeaveUserFromGroup([SignalRTrigger(HubName, "messages", "LeaveUserFromGroup", "userName", "groupName")] SignalRInvocationContext invocationContext, string userName, string groupName)
        {
            return UserGroups.RemoveFromGroupAsync(userName, groupName);
        }

        [Function("OnDisconnected")]
        public void OnDisconnected([SignalRTrigger(HubName, "connections", "disconnected")] SignalRInvocationContext invocationContext)
        {
            _logger.LogInformation($"{invocationContext.ConnectionId} has disconnected");
        }
    }
    public class NewConnection
    {
        public string ConnectionId { get; }

        public string Authentication { get; }

        public NewConnection(string connectionId, string auth)
        {
            ConnectionId = connectionId;
            Authentication = auth;
        }
    }

    public class NewMessage
    {
        public string ConnectionId { get; }
        public string Sender { get; }
        public string Text { get; }

        public NewMessage(SignalRInvocationContext invocationContext, string message)
        {
            Sender = string.IsNullOrEmpty(invocationContext.UserId) ? string.Empty : invocationContext.UserId;
            ConnectionId = invocationContext.ConnectionId;
            Text = message;
        }
    }

    public interface IChatClient
    {
        Task newMessage(NewMessage message);
        Task newConnection(NewConnection connection);
    }
// both clients look like this
var connection = new HubConnectionBuilder()
    .WithUrl("<azure_function_endpoint>/api", (options) =>
    {
        options.Headers.Add("UserId", "<some_user_id>");
        options.AccessTokenProvider = async () =>
        {
            return await Task.FromResult("<access_token>"); // ikn about hardcoing tokens, this is just for the poc
        };
    })
    .Build();

connection.On<string>("Broadcast", Console.WriteLine);
await connection.StartAsync();
await connection.SendAsync("Broadcast", "This is a broadcast");

My goal is to facilitate rpc between clients, where a client can be both the server and the client.

I have set on using the serverless Azure SignalR Service with an Azure Function for trigger binding.

The issue that I face right now is that I can't invoke those methods from the client. The SignalR trigger broadcast is not invoked.

My question is if I approach the problem right, I want to send messages to the SignalR Service, from which the azure function reacts to the connected listeners, but when connecting straight to the service i get 403 no matter what (I do not have managed identity turned on), do I have to mediate it via the function?

I have created a serverless Azure SignalR Service and an Azure Function.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.Functions.Worker.SignalRService;
using Microsoft.Extensions.Logging;

namespace DotnetIsolated_ClassBased
{
    [SignalRConnection("AzureSignalRConnectionString")]
    public class Functions : ServerlessHub<IChatClient>
    {
        private const string HubName = "<hub_name>"
        private readonly ILogger _logger;

        public Functions(IServiceProvider serviceProvider, ILogger<Functions> logger) : base(serviceProvider)
        {
            _logger = logger;
        }

        [Function(nameof(BroadcastToAll))]
        [SignalROutput(HubName = HubName, ConnectionStringSetting = "SignalRConnection")]
        public static SignalRMessageAction BroadcastToAll([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
        {
            using var bodyReader = new StreamReader(req.Body);
            return new SignalRMessageAction("newMessage")
            {
                // broadcast to all the connected clients without specifying any connection, user or group.
                Arguments = new[] { bodyReader.ReadToEnd() },
            };
        }

        [Function("negotiate")]
        public async Task<HttpResponseData> Negotiate([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
        {
            _logger.LogInformation($"Negotiating with user: {req.Headers.GetValues("userId").FirstOrDefault()}");

            var negotiateResponse = await NegotiateAsync(new() { UserId = req.Headers.GetValues("userId").FirstOrDefault() });
            var response = req.CreateResponse();
            await response.WriteBytesAsync(negotiateResponse.ToArray());
            return response;
        }

        [Function("OnConnected")]
        public Task OnConnected([SignalRTrigger(HubName, "connections", "connected")] SignalRInvocationContext invocationContext)
        {
            invocationContext.Headers.TryGetValue("Authorization", out var auth);
            _logger.LogInformation($"{invocationContext.ConnectionId} has connected");
            return Clients.All.newConnection(new NewConnection(invocationContext.ConnectionId, auth));
        }

        [Function("Broadcast")]
        public Task Broadcast(
        [SignalRTrigger(HubName, "messages", "broadcast", "message")] SignalRInvocationContext invocationContext, string message)
        {
            _logger.LogInformation($"Broadcasting message: {message}");
            return Clients.All.newMessage(new NewMessage(invocationContext, message));
        }

        [Function("SendToGroup")]
        public Task SendToGroup([SignalRTrigger(HubName, "messages", "SendToGroup", "groupName", "message")] SignalRInvocationContext invocationContext, string groupName, string message)
        {
            return Clients.Group(groupName).newMessage(new NewMessage(invocationContext, message));
        }

        [Function("SendToUser")]
        public Task SendToUser([SignalRTrigger(HubName, "messages", "SendToUser", "userName", "message")] SignalRInvocationContext invocationContext, string userName, string message)
        {
            return Clients.User(userName).newMessage(new NewMessage(invocationContext, message));
        }

        [Function("SendToConnection")]
        public Task SendToConnection([SignalRTrigger(HubName, "messages", "SendToConnection", "connectionId", "message")] SignalRInvocationContext invocationContext, string connectionId, string message)
        {
            return Clients.Client(connectionId).newMessage(new NewMessage(invocationContext, message));
        }

        [Function("JoinGroup")]
        public Task JoinGroup([SignalRTrigger(HubName, "messages", "JoinGroup", "connectionId", "groupName")] SignalRInvocationContext invocationContext, string connectionId, string groupName)
        {
            return Groups.AddToGroupAsync(connectionId, groupName);
        }

        [Function("LeaveGroup")]
        public Task LeaveGroup([SignalRTrigger(HubName, "messages", "LeaveGroup", "connectionId", "groupName")] SignalRInvocationContext invocationContext, string connectionId, string groupName)
        {
            return Groups.RemoveFromGroupAsync(connectionId, groupName);
        }

        [Function("JoinUserToGroup")]
        public Task JoinUserToGroup([SignalRTrigger(HubName, "messages", "JoinUserToGroup", "userName", "groupName")] SignalRInvocationContext invocationContext, string userName, string groupName)
        {
            return UserGroups.AddToGroupAsync(userName, groupName);
        }

        [Function("LeaveUserFromGroup")]
        public Task LeaveUserFromGroup([SignalRTrigger(HubName, "messages", "LeaveUserFromGroup", "userName", "groupName")] SignalRInvocationContext invocationContext, string userName, string groupName)
        {
            return UserGroups.RemoveFromGroupAsync(userName, groupName);
        }

        [Function("OnDisconnected")]
        public void OnDisconnected([SignalRTrigger(HubName, "connections", "disconnected")] SignalRInvocationContext invocationContext)
        {
            _logger.LogInformation($"{invocationContext.ConnectionId} has disconnected");
        }
    }
    public class NewConnection
    {
        public string ConnectionId { get; }

        public string Authentication { get; }

        public NewConnection(string connectionId, string auth)
        {
            ConnectionId = connectionId;
            Authentication = auth;
        }
    }

    public class NewMessage
    {
        public string ConnectionId { get; }
        public string Sender { get; }
        public string Text { get; }

        public NewMessage(SignalRInvocationContext invocationContext, string message)
        {
            Sender = string.IsNullOrEmpty(invocationContext.UserId) ? string.Empty : invocationContext.UserId;
            ConnectionId = invocationContext.ConnectionId;
            Text = message;
        }
    }

    public interface IChatClient
    {
        Task newMessage(NewMessage message);
        Task newConnection(NewConnection connection);
    }
// both clients look like this
var connection = new HubConnectionBuilder()
    .WithUrl("<azure_function_endpoint>/api", (options) =>
    {
        options.Headers.Add("UserId", "<some_user_id>");
        options.AccessTokenProvider = async () =>
        {
            return await Task.FromResult("<access_token>"); // ikn about hardcoing tokens, this is just for the poc
        };
    })
    .Build();

connection.On<string>("Broadcast", Console.WriteLine);
await connection.StartAsync();
await connection.SendAsync("Broadcast", "This is a broadcast");
Share Improve this question asked Nov 21, 2024 at 11:45 Pultea RobertPultea Robert 1 2
  • Are you using this code locally or in function app? – Ikhtesam Afrin Commented Nov 22, 2024 at 3:37
  • In the function app. For the upstream setting I have set my azure function endpoint in the signalr service. – Pultea Robert Commented Nov 22, 2024 at 6:27
Add a comment  | 

2 Answers 2

Reset to default 0

I want to send messages to the SignalR Service, from which the azure function reacts to the connected listeners, but when connecting straight to the service i get 403 no matter what

  • It looks like a permission issue, make sure you have added the SignalR connection string in the Environment Variables of function app.

  • Verify, if you have constructed the Upstream in correct format like {functionAppURL}/runtime/webhooks/signalr?code={signalrExtensionCode}.
  • I am using the same code alike you and also have added below code in program.cs file.
using DotnetIsolated_ClassBased;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();
builder.Services.Configure<KestrelServerOptions>(options =>
{
    options.AllowSynchronousIO = true;
});
builder.Services.AddServerlessHub<Function1>();

builder.Build().Run();
  • I have added the function app Url and access token in client code and below functions have executed successfully.

  • You can also visualize the Live SignalR logs.

just an update, I have managed to make it work by completely creating another resource group and then the function, there seems to be an issue with the resource group as it was made years ago. One other thing that I want to mention is that the client must match the type if you are using strong typed hubs, return Clients.All.newMessage(new NewMessage(invocationContext, message)); on the client side would look like connection.On<NewMessage>("Broadcast", (value) => Console.WriteLine(Serialize(value)));

本文标签: cSending messages to the serverless Azure SignalR ServiceStack Overflow