Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docfx/docs/nativeAOT.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A consuming application can target NativeAOT while referencing StreamJsonRpc by
1. Use <xref:StreamJsonRpc.NerdbankMessagePackFormatter> or <xref:StreamJsonRpc.SystemTextJsonFormatter> instead of the default <xref:StreamJsonRpc.JsonMessageFormatter>.
<xref:StreamJsonRpc.NerdbankMessagePackFormatter> provides the best and safest experience and greatest set of functionality when you can use MessagePack encoding, but <xref:StreamJsonRpc.SystemTextJsonFormatter> must be used when UTF-8 JSON encoding is required.
1. Set <xref:System.Text.Json.JsonSerializerOptions.TypeInfoResolver> on the <xref:StreamJsonRpc.SystemTextJsonFormatter.JsonSerializerOptions?displayProperty=nameWithType> property to the `Default` property on your class that derives from <xref:System.Text.Json.Serialization.JsonSerializerContext>.
1. Use <xref:StreamJsonRpc.JsonRpc.AddLocalRpcMethod*> instead of <xref:StreamJsonRpc.JsonRpc.AddLocalRpcTarget*>.
1. Use <xref:StreamJsonRpc.JsonRpc.AddLocalRpcTarget(StreamJsonRpc.RpcTargetMetadata,System.Object,StreamJsonRpc.JsonRpcTargetOptions)?displayProperty=nameWithType> to add RPC target objects rather than other overloads.
1. When constructing proxies, use the <xref:StreamJsonRpc.JsonRpc.Attach*> methods with `typeof` arguments or specific generic type arguments.
1. When using named parameters (e.g. <xref:StreamJsonRpc.JsonRpc.NotifyWithParameterObjectAsync*> or <xref:StreamJsonRpc.JsonRpc.InvokeWithParameterObjectAsync*>), call the overloads that accept <xref:StreamJsonRpc.NamedArgs>.
1. Avoid [RPC marshalable objects](../exotic_types/rpc_marshalable_objects.md).
Expand Down
23 changes: 19 additions & 4 deletions samples/NativeAOT/SystemTextJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ partial class SystemTextJson
static async Task Main(string[] args)
{
(Stream clientPipe, Stream serverPipe) = FullDuplexStream.CreatePair();
JsonRpc serverRpc = new JsonRpc(new HeaderDelimitedMessageHandler(serverPipe, CreateFormatter()));
JsonRpc clientRpc = new JsonRpc(new HeaderDelimitedMessageHandler(clientPipe, CreateFormatter()));
serverRpc.AddLocalRpcMethod(nameof(Server.AddAsync), new Server().AddAsync);
JsonRpc serverRpc = new(new HeaderDelimitedMessageHandler(serverPipe, CreateFormatter()));
JsonRpc clientRpc = new(new HeaderDelimitedMessageHandler(clientPipe, CreateFormatter()));

RpcTargetMetadata.RegisterEventArgs<int>();
var targetMetadata = RpcTargetMetadata.FromInterface(new RpcTargetMetadata.InterfaceCollection(typeof(IServer)));

serverRpc.AddLocalRpcTarget(targetMetadata, new Server(), null);
serverRpc.StartListening();
IServer proxy = clientRpc.Attach<IServer>();
clientRpc.StartListening();
Expand All @@ -37,12 +41,23 @@ partial class SourceGenerationContext : JsonSerializerContext;
[JsonRpcContract]
internal partial interface IServer
{
event EventHandler<int> Added;

Task<int> AddAsync(int a, int b);
}

class Server : IServer
{
public Task<int> AddAsync(int a, int b) => Task.FromResult(a + b);
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);
}
#endregion
}
47 changes: 41 additions & 6 deletions src/StreamJsonRpc/JsonRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Runtime.Serialization;
using Microsoft.VisualStudio.Threading;
using Newtonsoft.Json;
using StreamJsonRpc;
using StreamJsonRpc.Protocol;
using StreamJsonRpc.Reflection;

Expand Down Expand Up @@ -921,7 +922,7 @@ public object Attach(ReadOnlySpan<Type> interfaceTypes, JsonRpcProxyOptions? opt
public void AddLocalRpcTarget<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(T target, JsonRpcTargetOptions? options)
where T : notnull => this.AddLocalRpcTarget(typeof(T), target, options);

/// <inheritdoc cref="RpcTargetInfo.AddLocalRpcTarget(Type, object, JsonRpcTargetOptions?, bool)"/>
/// <inheritdoc cref="RpcTargetInfo.AddLocalRpcTarget(RpcTargetMetadata, object, JsonRpcTargetOptions?, bool)"/>
/// <exception cref="InvalidOperationException">Thrown if called after <see cref="StartListening"/> is called and <see cref="AllowModificationWhileListening"/> is <see langword="false"/>.</exception>
[RequiresDynamicCode(RuntimeReasons.CloseGenerics)]
public void AddLocalRpcTarget(
Expand All @@ -932,9 +933,21 @@ public void AddLocalRpcTarget(
Requires.NotNull(exposingMembersOn, nameof(exposingMembersOn));
Requires.NotNull(target, nameof(target));
this.ThrowIfConfigurationLocked();

this.AddLocalRpcTargetInternal(exposingMembersOn, target, options, requestRevertOption: false);
}

/// <inheritdoc cref="AddLocalRpcTarget(Type, object, JsonRpcTargetOptions?)"/>
public void AddLocalRpcTarget(RpcTargetMetadata exposingMembersOn, object target, JsonRpcTargetOptions? options)
{
Requires.NotNull(exposingMembersOn);
Requires.NotNull(target);
this.ThrowIfConfigurationLocked();

options ??= JsonRpcTargetOptions.Default;
this.rpcTargetInfo.AddLocalRpcTarget(exposingMembersOn, target, options, requestRevertOption: false);
}

/// <summary>
/// Adds a remote rpc connection so calls can be forwarded to the remote target if local targets do not handle it.
/// </summary>
Expand Down Expand Up @@ -1440,29 +1453,51 @@ internal void AddLocalRpcMethod(MethodInfo handler, object? target, JsonRpcMetho
this.rpcTargetInfo.AddLocalRpcMethod(handler, target, methodRpcSettings, synchronizationContext);
}

/// <inheritdoc cref="RpcTargetInfo.AddLocalRpcTarget(Type, object, JsonRpcTargetOptions?, bool)"/>
/// <summary>
/// Adds the specified target as possible object to invoke when incoming messages are received.
/// </summary>
/// <param name="exposingMembersOn">
/// The type whose members define the RPC accessible members of the <paramref name="target"/> object.
/// If this type is not an interface, only public members become invokable unless <see cref="JsonRpcTargetOptions.AllowNonPublicInvocation"/> is set to true on the <paramref name="options"/> argument.
/// </param>
/// <param name="target">Target to invoke when incoming messages are received.</param>
/// <param name="options">A set of customizations for how the target object is registered. If <see langword="null"/>, default options will be used.</param>
/// <param name="requestRevertOption"><see langword="true"/> to receive an <see cref="IDisposable"/> that can remove the target object; <see langword="false" /> otherwise.</param>
/// <returns>An object that may be disposed of to revert the addition of the target object. Will be null if and only if <paramref name="requestRevertOption"/> is <see langword="false"/>.</returns>
/// <remarks>
/// When multiple target objects are added, the first target with a method that matches a request is invoked.
/// </remarks>
[RequiresDynamicCode(RuntimeReasons.CloseGenerics)]
internal RpcTargetInfo.RevertAddLocalRpcTarget? AddLocalRpcTargetInternal(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type exposingMembersOn,
object target,
JsonRpcTargetOptions? options,
bool requestRevertOption)
{
return this.rpcTargetInfo.AddLocalRpcTarget(exposingMembersOn, target, options, requestRevertOption);
RpcTargetMetadata.EnableDynamicEventHandlerCreation();

options ??= JsonRpcTargetOptions.Default;
RpcTargetMetadata mapping =
exposingMembersOn.IsInterface ? RpcTargetMetadata.FromInterface(exposingMembersOn) :
options.AllowNonPublicInvocation ? RpcTargetMetadata.FromClassNonPublic(exposingMembersOn) :
RpcTargetMetadata.FromClass(exposingMembersOn);

return this.rpcTargetInfo.AddLocalRpcTarget(mapping, target, options, requestRevertOption);
}

/// <summary>
/// Adds a new RPC interface to an existing target registering additional RPC methods.
/// </summary>
/// <param name="exposingMembersOn">The interface type whose members define the RPC accessible members of the <paramref name="target"/> object.</param>
/// <param name="targetMetadata">The interface type whose members define the RPC accessible members of the <paramref name="target"/> object.</param>
/// <param name="target">Target to invoke when incoming messages are received.</param>
/// <param name="options">A set of customizations for how the target object is registered. If <see langword="null" />, default options will be used.</param>
/// <param name="revertAddLocalRpcTarget">
/// An optional object that may be disposed of to revert the addition of the target object.
/// </param>
internal void AddRpcInterfaceToTargetInternal([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type exposingMembersOn, object target, JsonRpcTargetOptions? options, RpcTargetInfo.RevertAddLocalRpcTarget? revertAddLocalRpcTarget)
internal void AddRpcInterfaceToTargetInternal(RpcTargetMetadata targetMetadata, object target, JsonRpcTargetOptions? options, RpcTargetInfo.RevertAddLocalRpcTarget? revertAddLocalRpcTarget)
{
this.rpcTargetInfo.AddRpcInterfaceToTarget(exposingMembersOn, target, options, revertAddLocalRpcTarget);
options ??= JsonRpcTargetOptions.Default;
this.rpcTargetInfo.AddRpcInterfaceToTarget(targetMetadata, target, options, revertAddLocalRpcTarget);
}

/// <summary>
Expand Down
12 changes: 12 additions & 0 deletions src/StreamJsonRpc/PolyfillMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace StreamJsonRpc;

internal static class PolyfillMethods
{
#if NETSTANDARD2_0
internal static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value)
=> (key, value) = (pair.Key, pair.Value);
#endif
}
Loading