-
Notifications
You must be signed in to change notification settings - Fork 6
Serialization
AppDomainAlternative has a default serializer that uses a custom serializer designed to serialize reference types and value types. It can also pass references to remoting instances shared between domains/processes similar to AppDomains passing references between domains for classes the inherit from MarshalByRefObject
. It can serialize these shared instances directly to a reference id (long), or it can serialize an object that has a field/property that references one of these shared instances (by serializing the field/property value as a reference id). This serializer is not intended for use with untrusted domains/processes because it was not designed to sanitize and validate serialized data from an untrusted source.
When an object instance needs to be shared across domains/processes, a proxy for that instance is created on one domain/process and the hosting instance for that proxy exist in the other domain/process. When serializing data the IResolveProxyIds should be used to know which object instance is a shared instance. This will be needed to serialize/deserialize the data correctly so that the shared instance is passed correctly across the domain/process barrier.
A custom serializer must implement the IAmASerializer interface to replace the default one. Below is an example serializer that uses the Newtonsoft JSON.Net serializer:
public class MyJsonSerializer : IAmASerializer
{
public MyJsonSerializer(Func<IResolveProxyIds, JsonSerializerSettings> settingsFactory) =>
SettingsFactory = settingsFactory ?? throw new ArgumentNullException(nameof(settingsFactory));
public Func<IResolveProxyIds, JsonSerializerSettings> SettingsFactory { get; }
public Task Serialize(BinaryWriter writer, Type valueType, object value, IResolveProxyIds resolver)
{
writer.Write(JsonConvert.SerializeObject(value, SettingsFactory(resolver)));
return Task.CompletedTask;
}
public Task<object> Deserialize(BinaryReader reader, Type valueType, IResolveProxyIds resolver, CancellationToken token)
{
return Task.FromResult(JsonConvert.DeserializeObject(reader.ReadString(), valueType, SettingsFactory(resolver)));
}
public bool CanSerialize(Type type) => true;
public string Name { get; } = "[email protected]";
}
The Name
property of the serializer is used to identify the correct serializer to use for IPC. A name schema pattern of {name of serializer}@{major.minor.patch}
is recommended for versioning support where multiple versions of a serializer could be used and backwards compatibility is important.
Each method for serializing and deserializing gets an IResolveProxyIds instance which should be used to resolve shared instances used for remoting between domains/processes. The remoting instance id can be used as a reference value to resolve the instance for both sides of the parent and child domain/process pair.
After creating the serializer, a resolver needs to be added to the DomainConfiguration so a child process will know which serializer to use for IPC with the parent. This setting should be modified like the example below:
DomainConfiguration.SerializerResolver = name =>
{
var match = Regex.Match(name, @"^MyJsonSerializer@(?<MAJOR>\d+)\.(?<MINOR>\d+)\.(?<PATCH>\d+)$");
if (!match.Success ||
!int.TryParse(match.Groups["MAJOR"].Value, out var major) ||
!int.TryParse(match.Groups["MINOR"].Value, out var minor) ||
!int.TryParse(match.Groups["PATCH"].Value, out var patch))
{
return null;//if the serializer is not found then the resolver should return null
}
if (major == 1)
{
return new MyJsonSerializer(resolver => new JsonSerializerSettings
{
//todo: generate serialization settings with a json converter that can serialize shared references using the resolver
});
}
return null;//if the serializer is not found then the resolver should return null
};