diff --git a/docfx/docs/extensibility.md b/docfx/docs/extensibility.md index 3b71e03a..6a7e0c56 100644 --- a/docfx/docs/extensibility.md +++ b/docfx/docs/extensibility.md @@ -80,13 +80,25 @@ StreamJsonRpc includes the following `-derived class. + with a -derived class. All custom converters can be added to the serializer at . All RPC method parameter or return types must have type shapes generated for them via [a witness class](https://aarnott.github.io/Nerdbank.MessagePack/docs/type-shapes.html). This formatter is not fully wire format compatible with , so matching formatters on both sides of an RPC connection is recommended. + Create the formatter with the required + and optionally with serializer customizations. + When customizing the serializer, always base the new serializer on the default one defined by as shown below: + + [!code-csharp[](../../samples/Extensibility.cs#CreateNBMsgPackFormatter)] + + In the above sample, the type shape provider comes from a [witness class](https://aarnott.github.io/Nerdbank.MessagePack/docs/type-shapes.html#witness-classes), which you can trivially define like this: + + [!code-csharp[](../../samples/Extensibility.cs#PolyTypeWitness)] + + Learn more about [witness classes](https://aarnott.github.io/Nerdbank.MessagePack/docs/type-shapes.html#witness-classes) and [customizing serialization](https://aarnott.github.io/Nerdbank.MessagePack/docs/customizing-serialization.html). + 1. - Uses Newtonsoft.Json to serialize each JSON-RPC message as actual JSON. The text encoding is configurable via a property. All RPC method parameters and return types must be serializable by Newtonsoft.Json. diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index be1f2865..f76b9b4b 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -1,8 +1,4 @@ - - - - - + diff --git a/samples/Directory.Build.targets b/samples/Directory.Build.targets new file mode 100644 index 00000000..f76b9b4b --- /dev/null +++ b/samples/Directory.Build.targets @@ -0,0 +1,4 @@ + + + + diff --git a/samples/Extensibility.cs b/samples/Extensibility.cs new file mode 100644 index 00000000..29417dfc --- /dev/null +++ b/samples/Extensibility.cs @@ -0,0 +1,85 @@ +using System.IO.Pipelines; +using Microsoft.VisualStudio.Threading; +using Nerdbank.Streams; +using Xunit; + +public partial class Extensibility +{ + #region RpcContract + [JsonRpcContract, GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)] + public partial interface IWeatherService + { + Task GetTemperatureAsync(string city, CancellationToken cancellationToken); + } + #endregion + + #region ServiceImpl + class WeatherService : IWeatherService + { + public Task GetTemperatureAsync(string city, CancellationToken cancellationToken) + { + return Task.FromResult(city switch + { + "Seattle" => 55, + "Denver" => 65, + _ => throw new ArgumentException($"Unknown city: {city}"), + }); + } + } + #endregion + + #region PolyTypeWitness + [GenerateShapeFor] + partial class Witness; + #endregion + + [Fact] + public async Task ValidateNBMsgPackSample() + { + (IDuplexPipe clientPipe, IDuplexPipe serverPipe) = FullDuplexStream.CreatePipePair(); + await Task.WhenAll( + this.NBMsgPackClient(clientPipe, TestContext.Current.CancellationToken), + this.NBMsgPackServer(serverPipe, TestContext.Current.CancellationToken)) + .WithCancellation(TestContext.Current.CancellationToken); + } + + IJsonRpcMessageFormatter CreateNBMsgPackFormatter() => + #region CreateNBMsgPackFormatter + new NerdbankMessagePackFormatter + { + TypeShapeProvider = Witness.GeneratedTypeShapeProvider, + UserDataSerializer = NerdbankMessagePackFormatter.DefaultSerializer with + { + PerfOverSchemaStability = true, + }, + }; + #endregion + + async Task NBMsgPackClient(IDuplexPipe pipe, CancellationToken cancellationToken) + { + #region NBMsgPackClient + IWeatherService proxy = JsonRpc.Attach( + new LengthHeaderMessageHandler( + pipe, + this.CreateNBMsgPackFormatter())); + using (proxy as IDisposable) + { + int temperature = await proxy.GetTemperatureAsync("Denver", cancellationToken); + TestContext.Current.TestOutputHelper?.WriteLine($"Temp: {temperature}"); + } + #endregion + } + + async Task NBMsgPackServer(IDuplexPipe pipe, CancellationToken cancellationToken) + { + #region NBMsgPackServer + JsonRpc rpc = new( + new LengthHeaderMessageHandler( + pipe, + this.CreateNBMsgPackFormatter())); + rpc.AddLocalRpcTarget(RpcTargetMetadata.FromShape(), new WeatherService(), options: null); + rpc.StartListening(); + await rpc.Completion; + #endregion + } +} diff --git a/samples/NativeAOT/NerdbankMessagePack.cs b/samples/NativeAOT/NerdbankMessagePack.cs index 4487ae29..7ed5e089 100644 --- a/samples/NativeAOT/NerdbankMessagePack.cs +++ b/samples/NativeAOT/NerdbankMessagePack.cs @@ -1,5 +1,6 @@ -using Nerdbank.Streams; -using PolyType; +#pragma warning disable CS8892 // These Main methods will be ignored + +using Nerdbank.Streams; namespace NativeAOT; @@ -36,7 +37,8 @@ class Server : IServer public Task AddAsync(int a, int b) => Task.FromResult(a + b); } - // Every data type used in the RPC methods must be annotated for serialization. + // Just one attribute with any type argument is sufficient to trigger + // a source generator to expose the TypeShapeProvider with all types used in the app. [GenerateShapeFor] private partial class Witness; #endregion diff --git a/samples/NativeAOT/SystemTextJson.cs b/samples/NativeAOT/SystemTextJson.cs index 8c88aa1f..2c293ad3 100644 --- a/samples/NativeAOT/SystemTextJson.cs +++ b/samples/NativeAOT/SystemTextJson.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS8892 // These Main methods will be ignored + using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Nerdbank.Streams; diff --git a/samples/Samples.csproj b/samples/Samples.csproj index cd6d28d5..4e05b74e 100644 --- a/samples/Samples.csproj +++ b/samples/Samples.csproj @@ -1,13 +1,22 @@  - net9.0 + net8.0;net9.0 + $(TargetFrameworks);net472 false + exe + true + true + true + + + + diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs index a647780a..f1e474b9 100644 --- a/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs +++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs @@ -101,6 +101,24 @@ public partial class NerdbankMessagePackFormatter : FormatterBase, IJsonRpcMessa /// /// Initializes a new instance of the class. /// + /// + /// + /// Creating this formatter requires setting the property. + /// A type shape provider is best obtained from the GeneratedTypeShapeProvider property + /// that is source generated onto a "witness" class, which is a + /// with at least one attribute on it. + /// + /// + /// When customizing the property, always base the new value on the + /// property. + /// + /// + /// + /// + /// + /// + /// Witness classes + /// Customizing serialization public NerdbankMessagePackFormatter() { // Set up initial options for our own message types. diff --git a/tools/dirs.proj b/tools/dirs.proj index 1d08760d..37291503 100644 --- a/tools/dirs.proj +++ b/tools/dirs.proj @@ -1,6 +1,6 @@ - +