MessagePack Protocol for SignalR: Faster, Smaller Messages

SignalR uses JSON by default. It's readable, debuggable, and universally supported. But JSON has overhead — verbose property names, string encoding, no native binary support. For applications where message size and serialisation speed matter, MessagePack is a compelling alternative.

What is MessagePack?

MessagePack is a binary serialisation format. Think of it as JSON but binary — it supports the same data types (maps, arrays, strings, numbers, booleans, null) but encodes them more efficiently. A typical MessagePack payload is 50-80% smaller than the equivalent JSON.

Key advantages for SignalR:

The trade-off is that messages are no longer human-readable in network traces.

Server-Side Setup

Install the NuGet package:

Example.cs
dotnet add package Microsoft.AspNetCore.SignalR.Protocols.MessagePack

Add MessagePack to the SignalR configuration:

Program.cs
builder.Services.AddSignalR()
    .AddMessagePackProtocol();

That's all the server needs. SignalR now supports both JSON and MessagePack — the client chooses which protocol to use during negotiation.

Configuring MessagePack Options

You can customise the MessagePack serialiser:

Example.cs
builder.Services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.SerializerOptions = MessagePackSerializerOptions.Standard
            .WithResolver(CompositeResolver.Create(
                // Custom resolvers first
                NativeDateTimeResolver.Instance,
                ContractlessStandardResolver.Instance,
                StandardResolver.Instance
            ))
            .WithSecurity(MessagePackSecurity.UntrustedData);
    });

The WithSecurity(MessagePackSecurity.UntrustedData) call is important — it protects against denial-of-service attacks through deeply nested objects.

JavaScript Client Setup

Install the MessagePack protocol package:

script.js
npm install @microsoft/signalr-protocol-msgpack

Configure the connection to use it:

script.js
import * as signalR from "@microsoft/signalr";
import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack";

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/hubs/data")
    .withHubProtocol(new MessagePackHubProtocol())
    .withAutomaticReconnect()
    .build();

If you're using script tags instead of a bundler:

page.html
<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>
<script>
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/hubs/data")
        .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
        .build();
</script>

.NET Client Setup

For .NET clients (console apps, MAUI, Blazor WASM):

Example.cs
dotnet add package Microsoft.AspNetCore.SignalR.Protocols.MessagePack
Example.cs
var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/hubs/data")
    .AddMessagePackProtocol()
    .Build();

When MessagePack Shines

MessagePack is most beneficial when you're sending:

Large or frequent payloads. A dashboard pushing data every second to hundreds of clients benefits significantly from smaller messages.

Example.cs
public class DashboardHub : Hub<IDashboardClient>
{
    public async Task SubscribeToMetrics()
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, "metrics");
    }
}

public interface IDashboardClient
{
    // This object might contain dozens of properties — MessagePack
    // keeps the payload compact
    Task MetricsUpdated(DashboardMetrics metrics);
}

public record DashboardMetrics(
    double CpuUsage,
    long MemoryBytes,
    int ActiveConnections,
    Dictionary<string, double> EndpointLatencies,
    DateTime Timestamp);

Binary data. If you're sending images, files, or byte arrays, MessagePack handles byte[] natively. With JSON, you'd need Base64 encoding, which increases size by roughly 33%.

Example.cs
public interface IFileClient
{
    // byte[] is sent as binary, not Base64-encoded
    Task FileChunkReceived(string fileName, byte[] chunk, int index);
}

Mixed Protocol Support

You can support both protocols simultaneously. The server accepts both by default when you call AddMessagePackProtocol() — JSON support isn't removed. This lets you:

Debugging MessagePack Traffic

Since MessagePack is binary, you can't just read it in browser dev tools. A few approaches:

  1. Temporarily switch to JSON during development by removing the MessagePack protocol from the client.
  2. Log on the server side by adding a hub filter that logs invocations.
  3. Use the MessagePack CLI to decode captured traffic.
Example.cs
// A hub filter for debugging invocations
public class LoggingHubFilter : IHubFilter
{
    private readonly ILogger<LoggingHubFilter> _logger;

    public LoggingHubFilter(ILogger<LoggingHubFilter> logger)
    {
        _logger = logger;
    }

    public async ValueTask<object?> InvokeMethodAsync(
        HubInvocationContext context,
        Func<HubInvocationContext, ValueTask<object?>> next)
    {
        _logger.LogDebug("Hub method: {Method}, Args: {Args}",
            context.HubMethodName,
            string.Join(", ", context.HubMethodArguments));

        return await next(context);
    }
}

Key Takeaways