Back to Blog
2 min read

Azure SignalR Service: Real-Time Web at Scale

Azure SignalR Service handles the complexity of real-time connections. WebSockets, Server-Sent Events, and long polling—automatically scaled.

Creating SignalR Service

az signalr create \
    --name mysignalr \
    --resource-group myRG \
    --sku Standard_S1 \
    --unit-count 1 \
    --service-mode Default

Server-Side Hub

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR()
            .AddAzureSignalR("Endpoint=https://mysignalr.service.signalr.net;AccessKey=xxx;Version=1.0;");
}

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<ChatHub>("/chat");
    });
}
// ChatHub.cs
public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    public async Task JoinGroup(string groupName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
        await Clients.Group(groupName).SendAsync("UserJoined", Context.User.Identity.Name);
    }

    public async Task SendToGroup(string groupName, string message)
    {
        await Clients.Group(groupName).SendAsync("ReceiveMessage", Context.User.Identity.Name, message);
    }

    public override async Task OnConnectedAsync()
    {
        await Clients.All.SendAsync("UserConnected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId);
        await base.OnDisconnectedAsync(exception);
    }
}

JavaScript Client

import * as signalR from "@microsoft/signalr";

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chat")
    .withAutomaticReconnect()
    .build();

connection.on("ReceiveMessage", (user, message) => {
    console.log(`${user}: ${message}`);
    displayMessage(user, message);
});

connection.on("UserJoined", (user) => {
    console.log(`${user} joined`);
});

async function start() {
    try {
        await connection.start();
        console.log("Connected");
    } catch (err) {
        console.error(err);
        setTimeout(start, 5000);
    }
}

connection.onclose(start);
start();

// Send message
async function sendMessage(message) {
    await connection.invoke("SendMessage", currentUser, message);
}

Serverless Mode (Azure Functions)

// Negotiate function
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
    [SignalRConnectionInfo(HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")] SignalRConnectionInfo connectionInfo)
{
    return connectionInfo;
}

// Broadcast function
[FunctionName("broadcast")]
public static async Task Broadcast(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post")] object message,
    [SignalR(HubName = "chat")] IAsyncCollector<SignalRMessage> signalRMessages)
{
    await signalRMessages.AddAsync(new SignalRMessage
    {
        Target = "ReceiveMessage",
        Arguments = new[] { message }
    });
}

// Send to specific user
[FunctionName("sendToUser")]
public static async Task SendToUser(
    [HttpTrigger] dynamic data,
    [SignalR(HubName = "chat")] IAsyncCollector<SignalRMessage> signalRMessages)
{
    await signalRMessages.AddAsync(new SignalRMessage
    {
        UserId = data.userId,
        Target = "ReceiveMessage",
        Arguments = new[] { data.message }
    });
}

Authentication

// Add authentication to connection
services.AddSignalR()
    .AddAzureSignalR(options =>
    {
        options.ConnectionString = "...";
        options.ClaimsProvider = context => new[]
        {
            new Claim(ClaimTypes.NameIdentifier, context.Request.Query["userId"])
        };
    });

Scaling

# Scale units
az signalr update \
    --name mysignalr \
    --resource-group myRG \
    --unit-count 5
UnitsConnectionsMessages/sec
11,0001M
55,0005M
1010,00010M
100100,000100M

SignalR Service: real-time made scalable.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.