Reputation: 4341
I have a web app that's running on Azure App Services. It's a Blazor Server backed by a SignalR Service. I also have some background processing that's performed by an Azure Function. Once the Function is complete, I want to send a notification to the web app via SignalR.
I have achieved this by enabling the SignalR output binding on the Azure Function. As long as I treat the Function App as a SignalR hub (i.e. the Blazor Server creates a HubConnection
to the Function App) - I'm able to receive messages sent from the Azure Function.
However, in the above scenario I had to configure a second SignalR instance that's running in "Serverless" mode. So I'm wondering, is there an alternative method I could use that side-steps the serverless design? Can I just use the SignalR client inside my Azure Function (and manually create a HubConnection
to the first SignalR Service)?
Or is that a little too heavy for an Azure Function? Would I be better off creating a push notification another way (e.g. the Blazor Server reacts to a Service Bus queue and then sends a message via SignalR)?
Upvotes: 0
Views: 1033
Reputation: 4341
So I came up with 2 possible answers. In the first version I added the SignalR client to the Azure Function:
[Function("SignalRFunction")]
public static async Task RunAsync([ServiceBusTrigger("import-request")] string json, FunctionContext context)
{
var conn = new HubConnectionBuilder()
.WithUrl( "https://localhost:5001" )
.Build();
await conn.StartAsync();
await conn.InvokeAsync( "Broadcast", "me", "some message" );
}
This connects to a hub on my Blazor Server and invokes the Broadcast message on my ImportHub
:
public class ImportHub : Hub
{
public const string HubUrl = "/import";
public async Task Broadcast(string username, string message)
{
await Clients.All.SendAsync("Broadcast", username, message);
}
}
But using this method, I'm creating a SignalR connection per Function call. Probably not the best plan. I could turn conn
into a static
object (like you might do with HttpClient
), but I still need to call StartAsync()
. I'm not sure what the best practice would be for a HubConnection
.
Instead, I've opted for answer 2 - the Azure Function makes an api call to the Blazor server. Here's my function now:
[Function("SignalRFunction")]
public static async Task RunAsync([ServiceBusTrigger("import-request")] string json, FunctionContext context)
{
var client = RestService.For<IImportHubApi>( "https://localhost:5001" );
await client.Broadcast("me", json);
}
For the client, I'm using Refit:
public interface IImportHubApi
{
[Get("/api/broadcast")]
Task Broadcast( string user, string message );
}
On my Blazor server, I have the following API controller:
[Route("api/broadcast")]
[ApiController]
public class BroadcastController : ControllerBase
{
private readonly IHubContext<ImportHub> _hub;
public BroadcastController(IHubContext<ImportHub> hub)
{
_hub = hub;
}
[HttpGet]
public async Task Get(string user, string message)
{
await _hub.Clients.All.SendAsync("Broadcast", user, message);
}
}
So I think that allows my Azure Function to scale (if it were relevant) without worrying about managing connections whilst utilising the one SignalR Service (in Default mode).
However, whether that's a good idea (from a security point of view) is another question. I figure as long as the push notification isn't used for evil (i.e. is used to refresh the display, not to kick off some process) then it's a matter of making sure my server code just uses the notification to prompt a UI refresh.
We do have an API server running on App Service. So I could move this API call to there and put it behind a firewall (API Management). That way, the broadcast would only be possible within the VPC. I'm assuming I could then lock down ImportHub
so you have to be authenticated first.
But I'm interested in what other people might have to say. Any advice is welcome!
Upvotes: 2