NativeAOT / Trimming
StreamJsonRpc is partially NativeAOT safe.
A consuming application can target NativeAOT while referencing StreamJsonRpc by observing these guidelines and restrictions:
Set the
EnableStreamJsonRpcInterceptors
MSBuild property totrue
in the project files covering all uses of JsonRpc.Attach. This includes those of your dependencies as well. Activating this feature will cause requests for proxies to fail if those requests include interfaces for which proxies have not been source generated. The source generator can predict these types in limited cases, and by default any JsonRpcContractAttribute interface will have a proxy generated for it.Use the JsonRpcProxyInterfaceGroupAttribute to specify sets of interfaces that should be supported.
Set the JsonRpcProxyOptions.AcceptProxyWithExtraInterfaces property to
true
to reduce the number of predefined groups for which proxies must be specially generated.Use SystemTextJsonFormatter instead of the default JsonMessageFormatter.
Set TypeInfoResolver on the SystemTextJsonFormatter.JsonSerializerOptions property to the
Default
property on your class that derives from JsonSerializerContext.Use JsonRpc.AddLocalRpcTarget(RpcTargetMetadata, object, JsonRpcTargetOptions?) to add RPC target objects rather than other overloads.
When constructing proxies, use the Attach methods with
typeof
arguments or specific generic type arguments.When using named parameters (e.g. NotifyWithParameterObjectAsync or InvokeWithParameterObjectAsync), call the overloads that accept NamedArgs.
Avoid RPC marshalable objects.
Sample program
The following program can execute in a NativeAOT published application:
UTF-8 JSON
The SystemTextJsonFormatter provides a semi-safe NativeAOT experience for those that require UTF-8 encoded JSON:
static async Task Main(string[] args)
{
(Stream clientPipe, Stream serverPipe) = FullDuplexStream.CreatePair();
JsonRpc serverRpc = new(new HeaderDelimitedMessageHandler(serverPipe, CreateFormatter()));
JsonRpc clientRpc = new(new HeaderDelimitedMessageHandler(clientPipe, CreateFormatter()));
#if POLYTYPE
RpcTargetMetadata.RegisterEventArgs<int>();
#endif
var targetMetadata = RpcTargetMetadata.FromInterface(new RpcTargetMetadata.InterfaceCollection(typeof(IServer)));
serverRpc.AddLocalRpcTarget(targetMetadata, new Server(), null);
serverRpc.StartListening();
IServer proxy = clientRpc.Attach<IServer>();
clientRpc.StartListening();
int sum = await proxy.AddAsync(2, 5);
Console.WriteLine($"2 + 5 = {sum}");
}
// When properly configured, this formatter is safe in Native AOT scenarios for
// the very limited use case shown in this program.
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Using the Json source generator.")]
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Using the Json source generator.")]
static IJsonRpcMessageFormatter CreateFormatter() => new SystemTextJsonFormatter()
{
JsonSerializerOptions = { TypeInfoResolver = SourceGenerationContext.Default },
};
// Every data type used in the RPC methods must be annotated for serialization.
[JsonSerializable(typeof(int))]
partial class SourceGenerationContext : JsonSerializerContext;
[JsonRpcContract, GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
internal partial interface IServer
{
event EventHandler<int> Added;
Task<int> AddAsync(int a, int b);
}
class Server : IServer
{
public event EventHandler<int>? Added;
public Task<int> AddAsync(int a, int b)
{
int sum = a + b;
this.OnAdded(sum);
return Task.FromResult(sum);
}
protected virtual void OnAdded(int sum) => this.Added?.Invoke(this, sum);
}