1

I am trying to build a test app so that my HTML page can send a message to an long running method on an Azure function via SignalR, and the long running function can use SignalR to report progress back to the HTML page.

Does anyone know how to do this? I am using .NET Core 3.1 for the function and the Azure SignalR service in serverless mode. I have looked around on the web but it all seems to be about chat whereas I would have thought this is quite a common requirement.

This is what I have so far...

using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace AzureSignalRFunctionTest
{
    public static class Function1
    {
        // Input Binding
        [FunctionName("negotiate")]
        public static SignalRConnectionInfo Negotiate(
            [HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req,
            [SignalRConnectionInfo(HubName = "TestHub")] SignalRConnectionInfo connectionInfo)
        {
            return connectionInfo;
        }

        // Trigger Binding
        [FunctionName("longrunningtask")]
        public static void LongRunningTask([SignalRTrigger("longrunningtask", "messages", "SendMessage")] InvocationContext invocationContext, [SignalRParameter] string message, ILogger logger)
        {
            logger.LogInformation($"Receive {message} from {invocationContext.ConnectionId}.");

            // what to put here?
            //var clients = invocationContext.GetClientsAsync().Result;

            //ReportProgress(invocationContext.ConnectionId, "Progress", ...);
            
            // Simulate Long running task
            System.Threading.Thread.Sleep(30000);
             
            // ReportProgress etc
        
        }

        // Output Binding
        [FunctionName("reportprogress")]
        public static Task ReportProgress(string connectionId, string message,
            [SignalR(HubName = "TestHub")] IAsyncCollector<SignalRMessage> signalRMessages)
        {
            return signalRMessages.AddAsync(
                new SignalRMessage
                {
                    ConnectionId = connectionId,
                    Target = "reportProgress",
                    Arguments = new[] { message }
                });
        }

    }
}
Philip Johnson
  • 1,091
  • 10
  • 24

1 Answers1

1

The answer was to do this:

using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace MyAzureFunction
{
    public class TestHub : ServerlessHub
    {
        // Input Binding
        [FunctionName("negotiate")]
        public SignalRConnectionInfo Negotiate(
            [HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req,
            [SignalRConnectionInfo(HubName="testhub")]SignalRConnectionInfo connectionInfo, ILogger logger)
        {
            logger.LogInformation($"ConnectionInfo: {connectionInfo.Url} {connectionInfo.AccessToken} {req.Path} {req.Query}");

            return connectionInfo;
        }

        // Trigger Binding
        [FunctionName("longrunningtask")]
        public async Task LongRunningTask(
            [SignalRTrigger] InvocationContext invocationContext,
            string message, ILogger logger)
        {
            logger.LogInformation($"Receive {message} from {invocationContext.ConnectionId}.");

            await Clients.Client(invocationContext.ConnectionId).SendAsync("reportProgress", message + " has started.");

            System.Threading.Thread.Sleep(10000);

            await Clients.Client(invocationContext.ConnectionId).SendAsync("reportProgress", message + " has ended.");
        }
    }
}

The Html looked like this:

<h2>SignalR</h2>

<div id="messages"></div>

<form>
    <button type="submit" id="submitbtn">Submit</button>
</form>

@section Scripts {

    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.19/signalr.min.js"></script>
    <script>
        let messages = document.querySelector('#messages');
        const apiBaseUrl = 'https://myazurefunction.azurewebsites.net';
        const connection = new signalR.HubConnectionBuilder()
            .withUrl(apiBaseUrl + '/api')
            .configureLogging(signalR.LogLevel.Information)
            .build();

        connection.on('reportProgress', (message) => {
            document.getElementById("messages").innerHTML = message;
        });

        function onConnected(connection) {
            document.getElementById('submitbtn').addEventListener('click', function (event) {
                connection.send('longrunningtask', 'this is my message');
                event.preventDefault();
            });
        }

        connection.start()
            .then(function () {
                onConnected(connection)
            })
            .catch(console.error);
    </script>

}

If you move away from the calling page, and return while the function app is running, it does not show the end result. If this is a requirement rather than just a simple progress bar, then I think the answer is to authentication via a user id, and then send messages to that user id.

Philip Johnson
  • 1,091
  • 10
  • 24