Skip to content

Commit a5e0be9

Browse files
authored
Improve docs around using NerdbankMessagePackFormatter (#1439)
2 parents d7810f4 + c90f856 commit a5e0be9

9 files changed

Lines changed: 139 additions & 11 deletions

File tree

docfx/docs/extensibility.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,25 @@ StreamJsonRpc includes the following <xref:StreamJsonRpc.IJsonRpcMessageFormatte
8080
each message using the very fast and compact binary [MessagePack format][MessagePackFormat].
8181
This formatter is NativeAOT ready.
8282
Any RPC method parameters and return types that require custom serialization may provide it
83-
with a `MessagePackConverter<T>`-derived class.
83+
with a <xref:Nerdbank.MessagePack.MessagePackConverter`1>-derived class.
8484
All custom converters can be added to the serializer at <xref:StreamJsonRpc.NerdbankMessagePackFormatter.UserDataSerializer>.
8585
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).
8686

8787
This formatter is not fully wire format compatible with <xref:StreamJsonRpc.MessagePackFormatter>,
8888
so matching formatters on both sides of an RPC connection is recommended.
8989

90+
Create the formatter with the required <xref:StreamJsonRpc.NerdbankMessagePackFormatter.TypeShapeProvider>
91+
and optionally with serializer customizations.
92+
When customizing the serializer, always base the new serializer on the default one defined by <xref:StreamJsonRpc.NerdbankMessagePackFormatter.DefaultSerializer> as shown below:
93+
94+
[!code-csharp[](../../samples/Extensibility.cs#CreateNBMsgPackFormatter)]
95+
96+
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:
97+
98+
[!code-csharp[](../../samples/Extensibility.cs#PolyTypeWitness)]
99+
100+
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).
101+
90102
1. <xref:StreamJsonRpc.JsonMessageFormatter> - Uses Newtonsoft.Json to serialize each JSON-RPC message as actual JSON.
91103
The text encoding is configurable via a property.
92104
All RPC method parameters and return types must be serializable by Newtonsoft.Json.

samples/Directory.Build.props

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
3-
<Import Project="$([MSBuild]::GetPathOfFileAbove($(MSBuildThisFile), $(MSBuildThisFileDirectory)..))" />
4-
5-
<ItemGroup>
6-
<PackageReference Include="Microsoft.VisualStudio.Internal.MicroBuild.NonShipping" PrivateAssets="all" />
7-
</ItemGroup>
3+
<Import Project="../test/$(MSBuildThisFile)" />
84
</Project>

samples/Directory.Build.targets

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project>
3+
<Import Project="../test/$(MSBuildThisFile)" />
4+
</Project>

samples/Extensibility.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System.IO.Pipelines;
2+
using Microsoft.VisualStudio.Threading;
3+
using Nerdbank.Streams;
4+
using Xunit;
5+
6+
public partial class Extensibility
7+
{
8+
#region RpcContract
9+
[JsonRpcContract, GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
10+
public partial interface IWeatherService
11+
{
12+
Task<int> GetTemperatureAsync(string city, CancellationToken cancellationToken);
13+
}
14+
#endregion
15+
16+
#region ServiceImpl
17+
class WeatherService : IWeatherService
18+
{
19+
public Task<int> GetTemperatureAsync(string city, CancellationToken cancellationToken)
20+
{
21+
return Task.FromResult(city switch
22+
{
23+
"Seattle" => 55,
24+
"Denver" => 65,
25+
_ => throw new ArgumentException($"Unknown city: {city}"),
26+
});
27+
}
28+
}
29+
#endregion
30+
31+
#region PolyTypeWitness
32+
[GenerateShapeFor<int>]
33+
partial class Witness;
34+
#endregion
35+
36+
[Fact]
37+
public async Task ValidateNBMsgPackSample()
38+
{
39+
(IDuplexPipe clientPipe, IDuplexPipe serverPipe) = FullDuplexStream.CreatePipePair();
40+
await Task.WhenAll(
41+
this.NBMsgPackClient(clientPipe, TestContext.Current.CancellationToken),
42+
this.NBMsgPackServer(serverPipe, TestContext.Current.CancellationToken))
43+
.WithCancellation(TestContext.Current.CancellationToken);
44+
}
45+
46+
IJsonRpcMessageFormatter CreateNBMsgPackFormatter() =>
47+
#region CreateNBMsgPackFormatter
48+
new NerdbankMessagePackFormatter
49+
{
50+
TypeShapeProvider = Witness.GeneratedTypeShapeProvider,
51+
UserDataSerializer = NerdbankMessagePackFormatter.DefaultSerializer with
52+
{
53+
PerfOverSchemaStability = true,
54+
},
55+
};
56+
#endregion
57+
58+
async Task NBMsgPackClient(IDuplexPipe pipe, CancellationToken cancellationToken)
59+
{
60+
#region NBMsgPackClient
61+
IWeatherService proxy = JsonRpc.Attach<IWeatherService>(
62+
new LengthHeaderMessageHandler(
63+
pipe,
64+
this.CreateNBMsgPackFormatter()));
65+
using (proxy as IDisposable)
66+
{
67+
int temperature = await proxy.GetTemperatureAsync("Denver", cancellationToken);
68+
TestContext.Current.TestOutputHelper?.WriteLine($"Temp: {temperature}");
69+
}
70+
#endregion
71+
}
72+
73+
async Task NBMsgPackServer(IDuplexPipe pipe, CancellationToken cancellationToken)
74+
{
75+
#region NBMsgPackServer
76+
JsonRpc rpc = new(
77+
new LengthHeaderMessageHandler(
78+
pipe,
79+
this.CreateNBMsgPackFormatter()));
80+
rpc.AddLocalRpcTarget(RpcTargetMetadata.FromShape<IWeatherService>(), new WeatherService(), options: null);
81+
rpc.StartListening();
82+
await rpc.Completion;
83+
#endregion
84+
}
85+
}

samples/NativeAOT/NerdbankMessagePack.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using Nerdbank.Streams;
2-
using PolyType;
1+
#pragma warning disable CS8892 // These Main methods will be ignored
2+
3+
using Nerdbank.Streams;
34

45
namespace NativeAOT;
56

@@ -36,7 +37,8 @@ class Server : IServer
3637
public Task<int> AddAsync(int a, int b) => Task.FromResult(a + b);
3738
}
3839

39-
// Every data type used in the RPC methods must be annotated for serialization.
40+
// Just one attribute with any type argument is sufficient to trigger
41+
// a source generator to expose the TypeShapeProvider with all types used in the app.
4042
[GenerateShapeFor<int>]
4143
private partial class Witness;
4244
#endregion

samples/NativeAOT/SystemTextJson.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma warning disable CS8892 // These Main methods will be ignored
2+
13
using System.Diagnostics.CodeAnalysis;
24
using System.Text.Json.Serialization;
35
using Nerdbank.Streams;

samples/Samples.csproj

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0</TargetFramework>
4+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
5+
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(TargetFrameworks);net472</TargetFrameworks>
56
<IsPackable>false</IsPackable>
7+
<OutputType>exe</OutputType>
8+
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
9+
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
10+
<PolySharpIncludeRuntimeSupportedAttributes>true</PolySharpIncludeRuntimeSupportedAttributes>
611
</PropertyGroup>
712

813
<ItemGroup>
914
<ProjectReference Include="..\src\StreamJsonRpc\StreamJsonRpc.csproj" />
1015
</ItemGroup>
1116

17+
<ItemGroup>
18+
<PackageReference Include="xunit.v3.mtp-v2" />
19+
</ItemGroup>
20+
1221
<Import Project="$(RepoRootPath)src\AnalyzerUser.targets" />
1322
</Project>

src/StreamJsonRpc/NerdbankMessagePackFormatter.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,24 @@ public partial class NerdbankMessagePackFormatter : FormatterBase, IJsonRpcMessa
101101
/// <summary>
102102
/// Initializes a new instance of the <see cref="NerdbankMessagePackFormatter"/> class.
103103
/// </summary>
104+
/// <remarks>
105+
/// <para>
106+
/// Creating this formatter requires setting the <see cref="TypeShapeProvider"/> property.
107+
/// A type shape provider is best obtained from the <c>GeneratedTypeShapeProvider</c> property
108+
/// that is source generated onto a "witness" class, which is a <see langword="partial"/> <see langword="class"/>
109+
/// with at least one <see cref="GenerateShapeForAttribute{T}"/> attribute on it.
110+
/// </para>
111+
/// <para>
112+
/// When customizing the <see cref="UserDataSerializer"/> property, always base the new value on the
113+
/// <see cref="DefaultSerializer"/> property.
114+
/// </para>
115+
/// </remarks>
116+
/// <example>
117+
/// <code source="../../samples/Extensibility.cs" region="CreateNBMsgPackFormatter" lang="C#" />
118+
/// <code source="../../samples/Extensibility.cs" region="PolyTypeWitness" lang="C#" />
119+
/// </example>
120+
/// <seealso href="https://aarnott.github.io/Nerdbank.MessagePack/docs/type-shapes.html#witness-classes">Witness classes</seealso>
121+
/// <seealso href="https://aarnott.github.io/Nerdbank.MessagePack/docs/customizing-serialization.html">Customizing serialization</seealso>
104122
public NerdbankMessagePackFormatter()
105123
{
106124
// Set up initial options for our own message types.

tools/dirs.proj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.Build.Traversal">
22
<ItemGroup>
3-
<ProjectReference Include="$(RepoRootPath)samples\Samples.csproj" />
3+
<ProjectReference Include="$(RepoRootPath)samples\Samples.csproj" Publish="false" />
44
<ProjectReference Include="$(RepoRootPath)src\dirs.proj" />
55
<ProjectReference Include="$(RepoRootPath)test\dirs.proj" />
66
</ItemGroup>

0 commit comments

Comments
 (0)