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:
- Smaller payloads mean less bandwidth.
- Faster serialisation and deserialisation.
- Native binary data support (no Base64 encoding needed).
The trade-off is that messages are no longer human-readable in network traces.
Server-Side Setup
Install the NuGet package:
dotnet add package Microsoft.AspNetCore.SignalR.Protocols.MessagePack
Add MessagePack to the SignalR configuration:
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:
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:
npm install @microsoft/signalr-protocol-msgpack
Configure the connection to use it:
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:
<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):
dotnet add package Microsoft.AspNetCore.SignalR.Protocols.MessagePack
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.
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%.
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:
- Use MessagePack for .NET clients where performance matters.
- Use JSON for web clients where debuggability is more important.
- Migrate gradually without breaking existing clients.
Debugging MessagePack Traffic
Since MessagePack is binary, you can't just read it in browser dev tools. A few approaches:
- Temporarily switch to JSON during development by removing the MessagePack protocol from the client.
- Log on the server side by adding a hub filter that logs invocations.
- Use the MessagePack CLI to decode captured traffic.
// 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
- MessagePack produces payloads 50-80% smaller than JSON with faster serialisation.
- Server setup is one NuGet package and one method call.
- Both JSON and MessagePack clients can connect to the same hub simultaneously.
- Use MessagePack for high-frequency data, large payloads, or binary content.
- Accept the debuggability trade-off — plan for server-side logging.