MessagePack Serialization for High-Performance .NET Services

JSON is human-readable, ubiquitous, and perfectly adequate for most APIs. But when you're serializing millions of messages between internal services, or pushing real-time data over SignalR, the overhead of text-based serialization adds up. MessagePack is a binary serialization format that's typically 2-4x smaller and 5-10x faster than JSON — and the .NET library MessagePack-CSharp makes it straightforward to adopt.

Getting started

Install the NuGet package:

dotnet add package MessagePack

Annotate your types with [MessagePackObject] and mark each property with a [Key]:

Example.cs
[MessagePackObject]
public class SensorReading
{
    [Key(0)]
    public int DeviceId { get; set; }

    [Key(1)]
    public double Temperature { get; set; }

    [Key(2)]
    public DateTime Timestamp { get; set; }
}

The integer keys produce the smallest payloads. Each property is identified by its index rather than its name, so the serialized bytes contain no property name strings at all.

Serialize and deserialize:

Example.cs
var reading = new SensorReading
{
    DeviceId = 42,
    Temperature = 21.5,
    Timestamp = DateTime.UtcNow
};

byte[] bytes = MessagePackSerializer.Serialize(reading);
var deserialized = MessagePackSerializer.Deserialize<SensorReading>(bytes);

String keys for flexibility

If you prefer readability in debugging tools or need looser coupling between versions, use string keys:

Example.cs
[MessagePackObject(keyAsPropertyName: true)]
public class SensorReading
{
    public int DeviceId { get; set; }
    public double Temperature { get; set; }
    public DateTime Timestamp { get; set; }
}

This embeds property names in the payload, making it slightly larger but more resilient to reordering or adding properties.

Using with SignalR

SignalR supports MessagePack as a hub protocol out of the box. Install the package:

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

Configure the server:

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

And the JavaScript client:

script.js
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/sensorHub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

For high-frequency real-time scenarios — live dashboards, game state sync, IoT telemetry — the bandwidth savings are significant.

Source generation for AOT

MessagePack-CSharp v3 introduced a source generator that eliminates the reflection-based resolver, making it compatible with Native AOT and trimming:

Example.cs
[MessagePackObject]
public partial class SensorReading
{
    [Key(0)]
    public int DeviceId { get; set; }

    [Key(1)]
    public double Temperature { get; set; }
}

Enable the source generator in your project file:

config.xml
<PropertyGroup>
    <MessagePackGeneratedResolverEnabled>true</MessagePackGeneratedResolverEnabled>
</PropertyGroup>

Then register the generated resolver:

Example.cs
var options = MessagePackSerializerOptions.Standard
    .WithResolver(GeneratedResolver.Instance);

var bytes = MessagePackSerializer.Serialize(reading, options);

Union types (polymorphism)

MessagePack-CSharp supports discriminated unions using the [Union] attribute:

Example.cs
[Union(0, typeof(TextMessage))]
[Union(1, typeof(ImageMessage))]
public interface IChatMessage
{
    string Sender { get; }
}

[MessagePackObject]
public class TextMessage : IChatMessage
{
    [Key(0)]
    public string Sender { get; set; } = "";

    [Key(1)]
    public string Content { get; set; } = "";
}

[MessagePackObject]
public class ImageMessage : IChatMessage
{
    [Key(0)]
    public string Sender { get; set; } = "";

    [Key(1)]
    public byte[] ImageData { get; set; } = [];
}

The integer tag in [Union] acts as the type discriminator in the serialized payload — compact and fast.

When to use MessagePack over JSON

MessagePack shines in these scenarios:

Stick with JSON when you need:

Inspecting MessagePack payloads

For debugging, convert binary payloads to JSON:

Example.cs
var json = MessagePackSerializer.ConvertToJson(bytes);
Console.WriteLine(json);

This produces a JSON representation of the MessagePack structure, which is invaluable when diagnosing serialization issues.

Wrapping up

MessagePack-CSharp is a mature, battle-tested library that drops into .NET applications with minimal ceremony. If your bottleneck is serialization throughput or payload size — particularly in internal services or real-time communication — it's a compelling alternative to JSON. Start with one internal boundary, benchmark the difference, and expand from there.