Skip to main content

Customizing message serialization and encryption

MagicOnion uses MessagePack for serialization by default, but it also provides extension points to customize serialization.

It allows for customization, such as encryption and the using of serializers other than MessagePack.

How to customize

Customizing serialization involves implementing a serializer that implements the IMagicOnionSerializerProvider interface and the IMagicOnionSerializer interface that the Create method returns.

You can use the implemented serializer provider by setting it to the MagicOnionSerializerProvider.Default property or passing it as an argument to MagicOnionClient and StramingHubClient.

API

/// <summary>
/// Provides a serializer for request/response of MagicOnion services and hub methods.
/// </summary>
public interface IMagicOnionSerializerProvider
{
IMagicOnionSerializer Create(MethodType methodType, MethodInfo? methodInfo);
}

/// <summary>
/// Provides a processing for message serialization.
/// </summary>
public interface IMagicOnionSerializer
{
void Serialize<T>(IBufferWriter<byte> writer, in T? value);
T? Deserialize<T>(in ReadOnlySequence<byte> bytes);
}

public static class MagicOnionSerializerProvider
{
/// <summary>
/// Gets or sets the <see cref="IMagicOnionSerializerProvider"/> to be used by default.
/// </summary>
public static IMagicOnionSerializerProvider Default { get; set; } = MessagePackMagicOnionSerializerProvider.Default;
}

Example code

The following code is a simple example of performing XOR encryption:

public class XorMessagePackMagicOnionSerializerProvider : IMagicOnionSerializerProvider
{
const int MagicNumber = 0x11;

readonly MessagePackSerializerOptions serializerOptions;

public static IMagicOnionSerializerProvider Instance { get; } = new XorMessagePackMagicOnionSerializerProvider(MessagePackSerializer.DefaultOptions);

XorMessagePackMagicOnionSerializerProvider(MessagePackSerializerOptions serializerOptions)
=> this.serializerOptions = serializerOptions;

class XorMessagePackMagicOnionSerializer : IMagicOnionSerializer
{
readonly MessagePackSerializerOptions serializerOptions;

public XorMessagePackMagicOnionSerializer(MessagePackSerializerOptions serializerOptions)
{
this.serializerOptions = serializerOptions;
}

public T Deserialize<T>(in ReadOnlySequence<byte> bytes)
{
var array = ArrayPool<byte>.Shared.Rent((int)bytes.Length);
try
{
bytes.CopyTo(array);
for (var i = 0; i < bytes.Length; i++)
{
array[i] ^= MagicNumber;
}
return MessagePackSerializer.Deserialize<T>(array.AsMemory(0, (int)bytes.Length), serializerOptions);
}
finally
{
ArrayPool<byte>.Shared.Return(array);
}
}

public void Serialize<T>(IBufferWriter<byte> writer, in T value)
{
var serialized = MessagePackSerializer.Serialize(value, serializerOptions);
for (var i = 0; i < serialized.Length; i++)
{
serialized[i] ^= MagicNumber;
}
writer.Write(serialized);
}
}

public IMagicOnionSerializer Create(MethodType methodType, MethodInfo? methodInfo)
=> new XorMessagePackMagicOnionSerializer(serializerOptions);
}