MessagePack Serialization
BareWire.Serialization.MsgPack adds a MessagePack serializer and deserializer that plug into BareWire's zero-copy pipeline. MessagePack is a compact binary format: payloads are smaller than JSON and encode/decode faster, which lowers bandwidth and per-message allocation on high-throughput streams. The package stays raw-first — it produces a bare MessagePack body with no envelope, exactly like the default raw-JSON serializer.
It is backed by MessagePack-CSharp with the ContractlessStandardResolver, so plain record message types work without [MessagePackObject] attributes. The serializer reports a content type of application/x-msgpack.
dotnet add package BareWire.Serialization.MsgPack
Registration
The package exposes two extension methods on IServiceCollection. They do different jobs, and most setups call them together.
AddBareWireMessagePackSerializer()
Registers the serializer and deserializer in DI:
MessagePackSerializerasIMessageSerializerMessagePackDeserializerasIMessageDeserializerMessagePackDeserializerunder its concrete type as well, so per-endpoint overrides (UseDeserializer<MessagePackDeserializer>()) resolve at bus start
All three are singletons (the types are stateless). Registration uses TryAdd*, so a custom serializer registered earlier is not replaced — call this method after any custom serializer registration. This method does not activate Content-Type routing; the default raw-JSON consume path is left untouched.
// Publish path: MessagePack becomes the IMessageSerializer
services.AddBareWireMessagePackSerializer();
AddBareWireMessagePackDeserializerRouting()
Activates Content-Type routing on the consume path by decorating the existing IDeserializerResolver. After this call, inbound messages with Content-Type: application/x-msgpack are routed to the MessagePack deserializer; every other content type (including application/json and null) continues to use the underlying raw-JSON deserializer.
This method requires a base IDeserializerResolver to already be registered — register the JSON serializer first. If none is present it throws InvalidOperationException (fail-fast). Calling the method more than once is idempotent: the decorator is never stacked.
// 1. Base resolver (raw-JSON) — REQUIRED first
services.AddBareWireJsonSerializer();
// 2. Optional: register the MsgPack serializer for the publish path
services.AddBareWireMessagePackSerializer();
// 3. Activate Content-Type routing — decorates the resolver from step 1
services.AddBareWireMessagePackDeserializerRouting();
Order matters.
AddBareWireMessagePackDeserializerRouting()must run afterAddBareWireJsonSerializer()(or anything else that registers anIDeserializerResolver). Calling it first throwsInvalidOperationException.
Bus-Level vs Per-Endpoint Use
Use AddBareWireMessagePackSerializer() when MessagePack is your default wire format for outgoing messages — it sets MessagePackSerializer as the application-wide IMessageSerializer.
To force MessagePack on a single receive endpoint regardless of the incoming Content-Type, override the deserializer on that endpoint:
wire.ReceiveEndpoint("my-queue", ep =>
{
ep.UseDeserializer<MessagePackDeserializer>();
ep.Consumer<MyConsumer, MyMessage>();
});
A per-endpoint override bypasses Content-Type routing entirely and applies to every message on that endpoint. It requires AddBareWireMessagePackSerializer() so the concrete MessagePackDeserializer is resolvable from DI. See Custom Serializers for the general per-endpoint override mechanism.
Content-Type Deserializer Routing
When endpoints carry mixed payloads — some raw JSON, some MessagePack — use routing instead of a per-endpoint override. Once AddBareWireMessagePackDeserializerRouting() is registered, the router selects the deserializer per message from its Content-Type header:
Inbound Content-Type |
Deserializer used |
|---|---|
application/x-msgpack |
MessagePackDeserializer |
application/json, any other value |
inner resolver (raw-JSON) |
null / unregistered |
inner resolver (raw-JSON) |
Matching is exact, case-insensitive. Parameterised variants such as application/x-msgpack; charset=utf-8 do not match and fall through to raw-JSON (fail-closed). This keeps the raw-first default intact: anything the router does not explicitly recognise stays on the JSON path.
When to Use What
| Scenario | Approach |
|---|---|
| Publish everything as MessagePack | AddBareWireMessagePackSerializer() (bus-level) |
| One endpoint is always MessagePack | ep.UseDeserializer<MessagePackDeserializer>() |
| Mixed JSON + MessagePack on the same endpoints | AddBareWireJsonSerializer() + AddBareWireMessagePackDeserializerRouting() |
Security and Type Requirements
The serializer and deserializer share a hardened options profile: the UntrustedData security mode (SipHash-seeded hashing and a recursion-depth limit to resist denial-of-service), with LZ4 compression and Typeless resolvers deliberately disabled. Deserialization always targets a known, closed CLR type via Deserialize<T>. On failure both paths throw BareWireSerializationException; the deserializer never embeds the raw binary payload in the exception.
Message types must be
public. TheContractlessStandardResolvergenerates formatters only forpublictypes at runtime.
Both operations are zero-copy: the serializer writes straight to an IBufferWriter<byte>, and the deserializer reads straight from a ReadOnlySequence<byte> (including multi-segment sequences) without copying into a contiguous buffer. An empty sequence deserializes to null.