Using Azure SignalR Service in Static Web Apps (SWA) Managed Functions

Using Azure SignalR Service in Static Web Apps (SWA) Managed Functions

I recently migrated my YouTube Remote project from ASP.NET 4 to use serverless computing, such as Azure SignalR Service and Azure Functions with Static Web Apps.

This aim of the YouTube Remote is to have a “display” which shows a YouTube video, and a “remote” to have play and pause controls. The remote could be a different browser window, or an entirely different device such as a mobile phone.

In the ASP.NET 4 Web App version of the project, all the communication between the display and remote control was handled through SignalR. I was intending for this to still be the case after moving the project to use the serverless cloud based services.

The Problem

During the migration, I discovered that the managed functions provided by Azure Static Web Apps only support HTTP Triggers. This means that the SignalR Trigger I intended to use for receiving commands from the remote-control would not work. The build process would also seem to fail if it detected a non-HTTP Trigger in the project.

Possible Solutions

As far as I could tell there were two options available to move forward with the migration.

Add an Azure Functions App as a linked API

Option one was to create a separate Azure Functions App to handle the API for the project. This could then be deployed and added to the Static Web App as a linked API. In this case, the SignalR Trigger should be supported for functions and could be used to handle the communication.

However, this would involve creating a separate app and adding another Azure service to support it. To me, this seemed a bit complicated for such a small project.

Use HTTP Triggers for commands

The other option was to split the communication. This would involve using SignalR to receive commands, while using HTTP requests to send commands. This would work around the issue, as managed functions can still queue SignalR messages to be sent to connected devices.

What I chose

I chose to use HTTP requests for sending commands and then using SignalR to receive commands. This allowed me to keep the API in the same project as the Static Web App which it was supporting. Considering both are relatively small, this seemed appropriate.

The code

With this in mind, the key parts of the function which handles the play command looks like the code below.

[FunctionName("play")]
public static async Task Play([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "play/{groupName}")] HttpRequest req,
string groupName,
[SignalR(HubName = "HubName")] IAsyncCollector<SignalRMessage> signalRMessages) {
  await signalRMessages.AddAsync(new SignalRMessage {
    GroupName = groupName,
    Target = "receiveCommand",
    Arguments = new [] {
      "play"
    }
  });
}

This function is triggered by a HTTP POST request, and also receives a binding to the SignalR hub provided by the Azure SignalR Service. Because the app has been coded to arrange devices into their own groups, a SignalR message will need to be sent to all devices within a group. This can be done by setting the GroupName to the appropriate group before adding it to the collector.

When the function completes, the SignalR message will be passed to the Azure SignalR Service to be sent to the connected devices.

Summary

Azure Functions which are managed by a Static Web App only accept HTTP Triggers. One option would be to move the API to a separate Functions App, but a workaround in my use case was to use HTTP POST requests to trigger the appropriate Azure function instead. The function would then pass SignalR messages to the Azure SignalR Service which are then sent to the appropriate devices.