Friday, August 14, 2020

Azure Functions SignalR Service Output Binding and Console Client

In this post, let's see how easy it is to publish messages using Azure SignalR Service from an Azure Function. 

I have created an Azure SignalR Service, I don't think any explanation is necessary on creating an Azure SignalR Service, it's just filling in basic details as in creating every other Azure service.

Then I have created a Function App using Visual Studio and have installed Microsoft.Azure.WebJobs.Extensions.SignalRService NuGet package. And then I have the following HTTP Trigger function.

public static class ReceiveNotificationFunction
{
    [FunctionName("ReceiveNotification")]
    public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
        [SignalR(HubName = "%SignalRHubName%", ConnectionStringSetting = "SignalRConnection")] IAsyncCollector<SignalRMessage> signalRMessageCollector,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        Notification notification = JsonConvert.DeserializeObject<Notification>(requestBody);

        await signalRMessageCollector.AddAsync(new SignalRMessage
        {
            Target = "ReceiveNotification",
            Arguments = new[] { notification }
        });

        var responseMessage = "This HTTP triggered function executed successfully.";
        return new OkObjectResult(responseMessage);
    }
}

So here I have specified an output binding for Azure SignalR Service, I am picking up the HubName and ConnectionString from local settings. The HubName can be anything you like. You can get the connection string of Azure SignalR Service from your SignalR Service Details page in Azure.

Then I am converting the request body to Notification type (some DTO), creating a SignalRMessage and adding it to the IAsyncCollector. In the SignalRMessageTarget is the client method to be invoked on each client. And Arguments is an array of data to be passed in. SignalRMessage also contains properties like UserId, GroupName which you can use to target the audience. Here I am just broadcasting a notification to all connected clients.

Now this is all good, to test whether it actually sends the messages/notifications, let's create Console client that listens to these messages.

I have created a Console Application and I have the following code.

class Program
{
    private static readonly JwtSecurityTokenHandler _jwtTokenHandler = new JwtSecurityTokenHandler();

    static async Task Main(string[] args)
    {
        var serviceUrl = "https://xxx.service.signalr.net";
        var accessKey = "SaXz59gKNCSh4TdTIOJaBx1nUCek86hsM3cSmY2pF4g=";
        var hubName = "Notifications";

        var url = $"{serviceUrl}/client/?hub={hubName}";

        HubConnection hubConnection = new HubConnectionBuilder()
            .WithUrl(url, option =>
            {
                option.AccessTokenProvider = () =>
                {
                    var token = GenerateAccessToken(url, accessKey);
                    return Task.FromResult(token);
                };
            }).Build();

        hubConnection.On("ReceiveNotification", (Notification notification) =>
        {
            Console.WriteLine($"Notification received: {JsonConvert.SerializeObject(notification)}");
        });

        await hubConnection.StartAsync();

        Console.WriteLine("Listening");
        Console.ReadLine();

        await hubConnection.DisposeAsync();
    }

    private static string GenerateAccessToken(string audience, string accessKey)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(accessKey));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        JwtSecurityToken token = _jwtTokenHandler.CreateJwtSecurityToken(
            issuer: null,
            audience: audience,
            subject: null,
            expires: DateTime.UtcNow.Add(TimeSpan.FromHours(1)),
            signingCredentials: credentials);

        return _jwtTokenHandler.WriteToken(token);
    }
}

I believe the code itself is self-explanatory. I have three variables, my Azure SignalR Service URL, it's Key and the HubName. Here the HubName should be exactly what you have mentioned in our Azure Function. Then I am creating a HubConnection to our Hub and using an AccessToken to authenticate with it which was generated by using the Hub URL and Key. Then I have the client method "ReceiveNotification" which is exactly the same name I have used in the SignalRMessages' Target in the Azure Function. So SignalR Service will be invoking this method. And finally, I am starting the HubConnection and listening in.

So that's about it. Now let's run both the Azure Function App and Console App and invoke our Azure Function with a test Notification message.

Azure SignalR Service Working

Here it goes.

Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment