diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index a7155c4c..30eadd85 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -32,6 +32,8 @@ jobs:
- run: dotnet docfx docfx/docfx.json
name: 📚 Generate documentation
+ env:
+ DocFx: true # Workaround https://github.com/dotnet/docfx/issues/10808
- name: Upload artifact
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
diff --git a/.github/workflows/docs_validate.yml b/.github/workflows/docs_validate.yml
index a6bbb1e8..f07e11d0 100644
--- a/.github/workflows/docs_validate.yml
+++ b/.github/workflows/docs_validate.yml
@@ -29,3 +29,5 @@ jobs:
run: dotnet build -c Release src/StreamJsonRpc.Analyzers.CodeFixes
- name: 📚 Verify docfx build
run: dotnet docfx docfx/docfx.json --warningsAsErrors --disableGitFeatures
+ env:
+ DocFx: true # Workaround https://github.com/dotnet/docfx/issues/10808
diff --git a/Directory.Build.props b/Directory.Build.props
index cd89f8e7..a7be4067 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -38,6 +38,9 @@
true
true
snupkg
+
+
+ $(DefineConstants);NOTDOCFX
diff --git a/src/StreamJsonRpc/JsonMessageFormatter.cs b/src/StreamJsonRpc/JsonMessageFormatter.cs
index 1d4d153e..e9fe066f 100644
--- a/src/StreamJsonRpc/JsonMessageFormatter.cs
+++ b/src/StreamJsonRpc/JsonMessageFormatter.cs
@@ -1050,6 +1050,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer
/// Converts an enumeration token to an .
///
[RequiresDynamicCode(RuntimeReasons.CloseGenerics)]
+ [RequiresUnreferencedCode(RuntimeReasons.CloseGenerics)]
private class AsyncEnumerableConsumerConverter : JsonConverter
{
private static readonly MethodInfo ReadJsonOpenGenericMethod = typeof(AsyncEnumerableConsumerConverter).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Single(m => m.Name == nameof(ReadJson) && m.IsGenericMethod);
@@ -1109,6 +1110,7 @@ private IAsyncEnumerable ReadJson(JsonReader reader, JsonSerializer serial
/// Converts an instance of to an enumeration token.
///
[RequiresDynamicCode(RuntimeReasons.CloseGenerics)]
+ [RequiresUnreferencedCode(RuntimeReasons.CloseGenerics)]
private class AsyncEnumerableGeneratorConverter : JsonConverter
{
private static readonly MethodInfo WriteJsonOpenGenericMethod = typeof(AsyncEnumerableGeneratorConverter).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(m => m.Name == nameof(WriteJson) && m.IsGenericMethod);
diff --git a/src/StreamJsonRpc/RpcTargetMetadata.cs b/src/StreamJsonRpc/RpcTargetMetadata.cs
index 43ae0317..5cd8285f 100644
--- a/src/StreamJsonRpc/RpcTargetMetadata.cs
+++ b/src/StreamJsonRpc/RpcTargetMetadata.cs
@@ -293,13 +293,6 @@ public static RpcTargetMetadata FromShape()
where TProvider : IShapeable => FromShape(TProvider.GetTypeShape());
#endif
- ///
- /// The type for which a shape should be obtained and generated for.
- /// The provider of type shapes from which to obtain the shape.
- /// An instance initialized from the shape of the .
- [Obsolete("Use FromShape(ITypeShape) instead. This API will be removed soon.")]
- public static RpcTargetMetadata FromShape(ITypeShapeProvider provider) => FromShape(provider.GetTypeShapeOrThrow());
-
///
/// Creates an instance from the specified shape.
///
diff --git a/src/StreamJsonRpc/RpcTargetMetadataExtensions.cs b/src/StreamJsonRpc/RpcTargetMetadataExtensions.cs
new file mode 100644
index 00000000..61425b74
--- /dev/null
+++ b/src/StreamJsonRpc/RpcTargetMetadataExtensions.cs
@@ -0,0 +1,60 @@
+#if NOTDOCFX // Workaround https://github.com/dotnet/docfx/issues/10808
+
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using PolyType.Abstractions;
+
+namespace StreamJsonRpc;
+
+///
+/// Extension methods for the class.
+///
+public static class RpcTargetMetadataExtensions
+{
+ extension(RpcTargetMetadata)
+ {
+ ///
+ /// Creates an instance from the specified shape.
+ ///
+ /// The type for which a shape should be obtained and generated for.
+ /// An instance initialized from the shape of the .
+#if NET8_0
+ [RequiresDynamicCode(ResolveDynamicMessage)]
+#endif
+#if NET
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Use the RpcTargetMetadata.FromShape() method instead. If using the extension method syntax, check that your type argument actually has a [GenerateShape] attribute or otherwise implements IShapeable to avoid a runtime failure.", error: true)]
+#endif
+ public static RpcTargetMetadata FromShape()
+ => RpcTargetMetadata.FromShape(TypeShapeResolver.ResolveDynamicOrThrow());
+
+ ///
+ /// Creates an instance from the specified shape.
+ ///
+ /// The type for which a shape should be obtained and generated for.
+ /// The provider of type shapes from which to obtain the shape.
+ /// An instance initialized from the shape of the .
+#if NET8_0
+ [RequiresDynamicCode(ResolveDynamicMessage)]
+#endif
+#if NET
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Use the RpcTargetMetadata.FromShape() method instead. If using the extension method syntax, check that your type argument actually has a [GenerateShape] attribute or otherwise implements IShapeable to avoid a runtime failure.", error: true)]
+#endif
+ public static RpcTargetMetadata FromShape()
+ => RpcTargetMetadata.FromShape(TypeShapeResolver.ResolveDynamicOrThrow());
+ }
+
+#if NET8_0
+ ///
+ /// A message to use as the argument to
+ /// for methods that call into .
+ ///
+ ///
+ private const string ResolveDynamicMessage =
+ "Dynamic resolution of IShapeable interface may require dynamic code generation in .NET 8 Native AOT. " +
+ "It is recommended to switch to statically resolved IShapeable APIs or upgrade your app to .NET 9 or later.";
+#endif
+}
+
+#endif
diff --git a/src/StreamJsonRpc/StreamJsonRpc.csproj b/src/StreamJsonRpc/StreamJsonRpc.csproj
index 4a278bff..bcc6ee61 100644
--- a/src/StreamJsonRpc/StreamJsonRpc.csproj
+++ b/src/StreamJsonRpc/StreamJsonRpc.csproj
@@ -1,6 +1,6 @@

- netstandard2.0;netstandard2.1;net8.0
+ netstandard2.0;netstandard2.1;net8.0;net9.0
prompt
4
true
diff --git a/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs b/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs
index c4c39558..3291c450 100644
--- a/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs
+++ b/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs
@@ -43,7 +43,27 @@ internal partial interface IShapedContract
[Fact]
public void FromShape()
{
- RpcTargetMetadata metadata = RpcTargetMetadata.FromShape(PolyType.SourceGenerator.TypeShapeProvider_StreamJsonRpc_Tests.Default.IShapedContract);
+ RpcTargetMetadata metadata = RpcTargetMetadata.FromShape();
+
+ var addAsync = Assert.Single(metadata.Methods["AddAsync"]);
+ var add = Assert.Single(metadata.AliasedMethods["Add"]);
+ Assert.Same(addAsync, add);
+
+ var subtract = Assert.Single(metadata.Methods["Subtract"]);
+ Assert.False(metadata.Methods.ContainsKey("SubtractAsync"));
+
+ // Verify that JsonRpcMethod.Name takes precedence over MethodShape.Name.
+ var multiply = Assert.Single(metadata.Methods["Times"]);
+
+ // Fail the test when support for events is added so we can update the test.
+ Assert.Equal(3, metadata.Methods.Count);
+ Assert.Single(metadata.AliasedMethods);
+ }
+
+ [Fact]
+ public void FromShape_TProvider()
+ {
+ RpcTargetMetadata metadata = RpcTargetMetadata.FromShape();
var addAsync = Assert.Single(metadata.Methods["AddAsync"]);
var add = Assert.Single(metadata.AliasedMethods["Add"]);
@@ -128,5 +148,6 @@ internal class RpcContractDerivedClass : IRpcContractDerived
}
[GenerateShapeFor]
+ [GenerateShapeFor]
private partial class Witness;
}
diff --git a/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj b/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj
index 53cda5e5..85ad9aa7 100644
--- a/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj
+++ b/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj
@@ -1,7 +1,7 @@

- net8.0
+ net8.0;net9.0
$(TargetFrameworks);net472
exe