diff --git a/Directory.Packages.props b/Directory.Packages.props
index e27e8283f..c30db430f 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -30,6 +30,7 @@
+
diff --git a/docfx/analyzers/StreamJsonRpc0008.md b/docfx/analyzers/StreamJsonRpc0008.md
index f839a2ab5..a9cbb125c 100644
--- a/docfx/analyzers/StreamJsonRpc0008.md
+++ b/docfx/analyzers/StreamJsonRpc0008.md
@@ -3,7 +3,7 @@
RPC interfaces attributed with or should also be attributed for PolyType method shape generation.
This ensures the interface can be used for RPC target objects in NativeAOT environments and with formatters prepared for those environments such as .
-This diagnostic may be disabled when running in a trimmed application is not in scope and not using a formatter that requires it, such as the .
+This diagnostic may be disabled when running in a trimmed application is not in scope and not using a formatter that requires it, as would.
## Example violation
diff --git a/docfx/analyzers/StreamJsonRpc0009.md b/docfx/analyzers/StreamJsonRpc0009.md
new file mode 100644
index 000000000..ecb325a10
--- /dev/null
+++ b/docfx/analyzers/StreamJsonRpc0009.md
@@ -0,0 +1,18 @@
+# StreamJsonRpc0009: Use GenerateShapeAttribute on optional marshalable interface
+
+RPC interfaces attributed with where is `true` should also be attributed for PolyType method shape generation using .
+This ensures the interface can be used for RPC target objects in NativeAOT environments and with formatters prepared for those environments such as .
+
+This diagnostic may be disabled when running in a trimmed application is not in scope and not using a formatter that requires it, as would.
+
+## Example violation
+
+The following interface serves as an optional RPC marshalable interface but uses instead of :
+
+[!code-csharp[](../../samples/Analyzers/StreamJsonRpc0009.cs#Violation)]
+
+## Resolution
+
+Switch to :
+
+[!code-csharp[](../../samples/Analyzers/StreamJsonRpc0009.cs#Fix)]
diff --git a/docfx/analyzers/index.md b/docfx/analyzers/index.md
index e9914ffed..06e96b566 100644
--- a/docfx/analyzers/index.md
+++ b/docfx/analyzers/index.md
@@ -12,8 +12,9 @@ Some of these diagnostics will include a suggested code fix that can apply the c
| [StreamJsonRpc0004](StreamJsonRpc0004.md) | Usage | Warning | Use interfaces for proxies |
| [StreamJsonRpc0005](StreamJsonRpc0005.md) | Usage | Error | RpcMarshalable interfaces must be IDisposable |
| [StreamJsonRpc0006](StreamJsonRpc0006.md) | Usage | Error | All interfaces in a proxy group must be attributed |
-| [StreamJsonRpc0007](StreamJsonRpc0007.md) | Usage | Error | Use RpcMarshalableAttribute on optional marshalable interface |
+| [StreamJsonRpc0007](StreamJsonRpc0007.md) | Usage | Error | Use RpcMarshalableAttribute on optional marshalable interface |
| [StreamJsonRpc0008](StreamJsonRpc0008.md) | Usage | Warning | Add methods to PolyType shape for RPC contract interface |
+| [StreamJsonRpc0009](StreamJsonRpc0009.md) | Usage | Warning | Use GenerateShapeAttribute on optional marshalable interface |
| [StreamJsonRpc0011](StreamJsonRpc0011.md) | Usage | Error | RPC methods use supported return types |
| [StreamJsonRpc0012](StreamJsonRpc0012.md) | Usage | Error | Unsupported member |
| [StreamJsonRpc0013](StreamJsonRpc0013.md) | Usage | Error | No generic methods |
diff --git a/docfx/analyzers/toc.yml b/docfx/analyzers/toc.yml
index 56ffba886..ac38962cc 100644
--- a/docfx/analyzers/toc.yml
+++ b/docfx/analyzers/toc.yml
@@ -8,6 +8,7 @@ items:
- href: StreamJsonRpc0006.md
- href: StreamJsonRpc0007.md
- href: StreamJsonRpc0008.md
+- href: StreamJsonRpc0009.md
- href: StreamJsonRpc0011.md
- href: StreamJsonRpc0012.md
- href: StreamJsonRpc0013.md
diff --git a/samples/Analyzers/StreamJsonRpc0007.cs b/samples/Analyzers/StreamJsonRpc0007.cs
index 341ee65e7..95c870ad6 100644
--- a/samples/Analyzers/StreamJsonRpc0007.cs
+++ b/samples/Analyzers/StreamJsonRpc0007.cs
@@ -24,7 +24,7 @@ partial interface IMyObject : IDisposable
{
}
- [RpcMarshalable(IsOptional = true), TypeShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
+ [RpcMarshalable(IsOptional = true), GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
partial interface IMyObject2 : IDisposable
{
}
diff --git a/samples/Analyzers/StreamJsonRpc0009.cs b/samples/Analyzers/StreamJsonRpc0009.cs
new file mode 100644
index 000000000..ba3d81a69
--- /dev/null
+++ b/samples/Analyzers/StreamJsonRpc0009.cs
@@ -0,0 +1,21 @@
+namespace StreamJsonRpc0009.Violation
+{
+#pragma warning disable StreamJsonRpc0009
+ #region Violation
+ [RpcMarshalable(IsOptional = true), TypeShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
+ partial interface IMyObject : IDisposable
+ {
+ }
+ #endregion
+#pragma warning restore StreamJsonRpc0009
+}
+
+namespace StreamJsonRpc0009.Fix
+{
+ #region Fix
+ [RpcMarshalable(IsOptional = true), GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
+ partial interface IMyObject : IDisposable
+ {
+ }
+ #endregion
+}
diff --git a/samples/Analyzers/StreamJsonRpc0050.cs b/samples/Analyzers/StreamJsonRpc0050.cs
index 9c440a014..eebb8ec8f 100644
--- a/samples/Analyzers/StreamJsonRpc0050.cs
+++ b/samples/Analyzers/StreamJsonRpc0050.cs
@@ -9,7 +9,7 @@ partial interface IMyObject : IDisposable
{
}
- [TypeShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
+ [GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
[RpcMarshalable(IsOptional = true)]
partial interface IMyObject2 : IDisposable
{
@@ -35,7 +35,7 @@ partial interface IMyObject : IDisposable
{
}
- [TypeShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
+ [GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]
[RpcMarshalable(IsOptional = true)]
partial interface IMyObject2 : IDisposable
{
diff --git a/samples/NativeAOT/SystemTextJson.cs b/samples/NativeAOT/SystemTextJson.cs
index a61758145..8c88aa1fa 100644
--- a/samples/NativeAOT/SystemTextJson.cs
+++ b/samples/NativeAOT/SystemTextJson.cs
@@ -13,8 +13,7 @@ static async Task Main(string[] args)
JsonRpc serverRpc = new(new HeaderDelimitedMessageHandler(serverPipe, CreateFormatter()));
JsonRpc clientRpc = new(new HeaderDelimitedMessageHandler(clientPipe, CreateFormatter()));
- RpcTargetMetadata.RegisterEventArgs();
- var targetMetadata = RpcTargetMetadata.FromInterface(new RpcTargetMetadata.InterfaceCollection(typeof(IServer)));
+ var targetMetadata = RpcTargetMetadata.FromShape();
serverRpc.AddLocalRpcTarget(targetMetadata, new Server(), null);
serverRpc.StartListening();
diff --git a/src/StreamJsonRpc.Analyzers/AnalyzerReleases.Unshipped.md b/src/StreamJsonRpc.Analyzers/AnalyzerReleases.Unshipped.md
index 86b52a344..4bddd2a2a 100644
--- a/src/StreamJsonRpc.Analyzers/AnalyzerReleases.Unshipped.md
+++ b/src/StreamJsonRpc.Analyzers/AnalyzerReleases.Unshipped.md
@@ -13,6 +13,7 @@ StreamJsonRpc0005 | Usage | Error | RpcMarshalable interfaces must be IDisposabl
StreamJsonRpc0006 | Usage | Error | All interfaces in a proxy group must be attributed
StreamJsonRpc0007 | Usage | Error | Use RpcMarshalableAttribute on optional marshalable interface
StreamJsonRpc0008 | Usage | Warning | Add methods to PolyType shape for RPC contract interface
+StreamJsonRpc0009 | Usage | Warning | Use GenerateShapeAttribute on optional marshalable interface
StreamJsonRpc0011 | Usage | Error | Unsupported RPC method return type
StreamJsonRpc0012 | Usage | Error | RPC contracts may not include this type of member
StreamJsonRpc0013 | Usage | Error | RPC contracts may not include generic methods
diff --git a/src/StreamJsonRpc.Analyzers/JsonRpcContractAnalyzer.cs b/src/StreamJsonRpc.Analyzers/JsonRpcContractAnalyzer.cs
index 5672a5053..b95f951e7 100644
--- a/src/StreamJsonRpc.Analyzers/JsonRpcContractAnalyzer.cs
+++ b/src/StreamJsonRpc.Analyzers/JsonRpcContractAnalyzer.cs
@@ -42,6 +42,11 @@ public class JsonRpcContractAnalyzer : DiagnosticAnalyzer
///
public const string GeneratePolyTypeMethodsOnRpcContractInterfaceId = "StreamJsonRpc0008";
+ ///
+ /// Diagnostic ID for StreamJsonRpc0009: Use GenerateShapeAttribute on optional marshalable interface.
+ ///
+ public const string UseGenerateShapeOnOptionalMarshalableInterfaceId = "StreamJsonRpc0009";
+
///
/// Diagnostic ID for StreamJsonRpc0011: RPC methods use supported return types.
///
@@ -132,6 +137,18 @@ public class JsonRpcContractAnalyzer : DiagnosticAnalyzer
isEnabledByDefault: true,
helpLinkUri: AnalyzerUtilities.GetHelpLink(GeneratePolyTypeMethodsOnRpcContractInterfaceId));
+ ///
+ /// Diagnostic for StreamJsonRpc0009: Use GenerateShapeAttribute on optional marshalable interface.
+ ///
+ public static readonly DiagnosticDescriptor UseGenerateShapeOnOptionalMarshalableInterface = new(
+ id: UseGenerateShapeOnOptionalMarshalableInterfaceId,
+ title: Strings.StreamJsonRpc0009_Title,
+ messageFormat: Strings.StreamJsonRpc0009_MessageFormat,
+ category: "Usage",
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ helpLinkUri: AnalyzerUtilities.GetHelpLink(UseGenerateShapeOnOptionalMarshalableInterfaceId));
+
///
/// Diagnostic for StreamJsonRpc0011: RPC methods use supported return types.
///
@@ -213,6 +230,7 @@ public class JsonRpcContractAnalyzer : DiagnosticAnalyzer
RpcMarshableDisposable,
UseRpcMarshalableAttributeOnOptionalInterfaces,
GeneratePolyTypeMethodsOnRpcContractInterface,
+ UseGenerateShapeOnOptionalMarshalableInterface,
UnsupportedReturnType,
UnsupportedMemberType,
NoGenericMethods,
@@ -294,6 +312,7 @@ private void InspectSymbol(SymbolStartAnalysisContext context, KnownSymbols know
}
bool isRpcMarshalable = SymbolEqualityComparer.Default.Equals(rpcContractAttribute.AttributeClass, knownSymbols.RpcMarshalableAttribute);
+ bool isRpcMarshalableOptional = isRpcMarshalable && rpcContractAttribute.NamedArguments.FirstOrDefault(a => a.Key == Types.RpcMarshalableAttribute.IsOptional).Value.Value is true;
bool isCallScopedLifetime = rpcContractAttribute.NamedArguments.FirstOrDefault(a => a.Key == Types.RpcMarshalableAttribute.CallScopedLifetime).Value.Value is true;
ImmutableList diagnostics = [];
Location typeLocation = namedType.Locations.FirstOrDefault() ?? Location.None;
@@ -303,16 +322,26 @@ private void InspectSymbol(SymbolStartAnalysisContext context, KnownSymbols know
// GenerateShapeAttribute is ineffective on open generic types, so ignore it in that case.
AttributeData? generateShapeAttribute = hasGenericTypeParameters ? null : namedType.GetAttributes().FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, knownSymbols.GenerateShapeAttribute));
AttributeData? typeShapeAttribute = namedType.GetAttributes().FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, knownSymbols.TypeShapeAttribute));
+ bool preferGenerateShape = generateShapeAttribute is not null || !isRpcMarshalable || isRpcMarshalableOptional;
+ string proposedShapeFix = preferGenerateShape ? "[GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]" : "[TypeShape(IncludeMethods = MethodShapeFlags.PublicInstance)]";
if (!this.IncludesPublicMethods(typeShapeAttribute) && !this.IncludesPublicMethods(generateShapeAttribute))
{
- bool preferGenerateShape = generateShapeAttribute is not null || !isRpcMarshalable;
diagnostics = diagnostics.Add(Diagnostic.Create(
GeneratePolyTypeMethodsOnRpcContractInterface,
typeLocation,
[(generateShapeAttribute ?? typeShapeAttribute)?.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken)?.GetLocation() ?? Location.None],
ImmutableDictionary.Empty.Add("PreferGenerateShape", preferGenerateShape ? "true" : "false"),
namedType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
- preferGenerateShape ? "[GenerateShape(IncludeMethods = MethodShapeFlags.PublicInstance)]" : "[TypeShape(IncludeMethods = MethodShapeFlags.PublicInstance)]"));
+ proposedShapeFix));
+ }
+ else if (generateShapeAttribute is null && isRpcMarshalableOptional)
+ {
+ diagnostics = diagnostics.Add(Diagnostic.Create(
+ UseGenerateShapeOnOptionalMarshalableInterface,
+ typeShapeAttribute?.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken)?.GetLocation() ?? typeLocation ?? Location.None,
+ ImmutableDictionary.Empty.Add("PreferGenerateShape", preferGenerateShape ? "true" : "false"),
+ namedType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat),
+ proposedShapeFix));
}
AttributeData[] optionalIfaceAttrs = [.. namedType.GetAttributes().Where(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, knownSymbols.RpcMarshalableOptionalInterface))];
diff --git a/src/StreamJsonRpc.Analyzers/Strings.resx b/src/StreamJsonRpc.Analyzers/Strings.resx
index 740d1f801..6f52df9cb 100644
--- a/src/StreamJsonRpc.Analyzers/Strings.resx
+++ b/src/StreamJsonRpc.Analyzers/Strings.resx
@@ -1,17 +1,17 @@
-
@@ -201,6 +201,12 @@
The RPC contract type '{0}' should also have the {1} applied so that it works with more formatters.
+
+ Use GenerateShapeAttribute on optional marshalable interface
+
+
+ The type '{0}' should use {1} since it is an optional RPC marshalable contract type.
+
JsonRpcProxyAttribute<T> should be applied only to generic interfaces
@@ -225,4 +231,4 @@
Use IClientProxy.Is, JsonRpcExtensions.As or the source-generated extension methods by the same names on your RPC marshalable interfaces to cast or type check between RpcMarshalable interfaces {0} and {1}.
-
\ No newline at end of file
+
diff --git a/src/StreamJsonRpc/FormatterBase.cs b/src/StreamJsonRpc/FormatterBase.cs
index 53194da4f..ae7367c78 100644
--- a/src/StreamJsonRpc/FormatterBase.cs
+++ b/src/StreamJsonRpc/FormatterBase.cs
@@ -19,8 +19,6 @@ namespace StreamJsonRpc;
///
public abstract class FormatterBase : IJsonRpcFormatterState, IJsonRpcInstanceContainer, IDisposable
{
- private readonly ProxyFactory proxyFactory;
-
private JsonRpc? rpc;
///
@@ -57,17 +55,10 @@ public abstract class FormatterBase : IJsonRpcFormatterState, IJsonRpcInstanceCo
///
/// Initializes a new instance of the class.
///
- [RequiresDynamicCode(RuntimeReasons.RefEmit), RequiresUnreferencedCode(RuntimeReasons.RefEmit)]
public FormatterBase()
- : this(ProxyFactory.Default)
{
}
- private protected FormatterBase(ProxyFactory proxyFactory)
- {
- this.proxyFactory = proxyFactory;
- }
-
///
/// An interface implemented by all the -derived nested types (, , ) to allow them to carry arbitrary top-level properties on behalf of the application.
///
@@ -99,7 +90,7 @@ JsonRpc IJsonRpcInstanceContainer.Rpc
this.rpc = value;
this.formatterProgressTracker = new MessageFormatterProgressTracker(value, this);
- this.rpcMarshaledContextTracker = new MessageFormatterRpcMarshaledContextTracker(value, this.proxyFactory, this);
+ this.rpcMarshaledContextTracker = this.CreateMessageFormatterRpcMarshaledContextTracker(value);
this.enumerableTracker = new MessageFormatterEnumerableTracker(value, this, this.rpcMarshaledContextTracker);
this.duplexPipeTracker = new MessageFormatterDuplexPipeTracker(value, this) { MultiplexingStream = this.MultiplexingStream };
}
@@ -219,6 +210,8 @@ protected virtual void Dispose(bool disposing)
/// A value to dispose of when serialization has completed.
protected SerializationTracking TrackSerialization(JsonRpcMessage message) => new(this, message);
+ private protected abstract MessageFormatterRpcMarshaledContextTracker CreateMessageFormatterRpcMarshaledContextTracker(JsonRpc rpc);
+
private protected void TryHandleSpecialIncomingMessage(JsonRpcMessage message)
{
switch (message)
diff --git a/src/StreamJsonRpc/JsonMessageFormatter.cs b/src/StreamJsonRpc/JsonMessageFormatter.cs
index 78a8e5c40..1d4d153e2 100644
--- a/src/StreamJsonRpc/JsonMessageFormatter.cs
+++ b/src/StreamJsonRpc/JsonMessageFormatter.cs
@@ -34,6 +34,8 @@ public class JsonMessageFormatter : FormatterBase, IJsonRpcAsyncMessageTextForma
///
internal const string ExceptionDataKey = "JToken";
+ private static readonly ProxyFactory ProxyFactory = ProxyFactory.Default;
+
///
/// JSON parse settings.
///
@@ -366,6 +368,9 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+ ///
+ private protected override MessageFormatterRpcMarshaledContextTracker CreateMessageFormatterRpcMarshaledContextTracker(JsonRpc rpc) => new MessageFormatterRpcMarshaledContextTracker.Dynamic(rpc, ProxyFactory, this);
+
private static IReadOnlyDictionary PartiallyParseNamedArguments(JObject args)
{
Requires.NotNull(args, nameof(args));
diff --git a/src/StreamJsonRpc/JsonRpc.cs b/src/StreamJsonRpc/JsonRpc.cs
index f43781545..389561e26 100644
--- a/src/StreamJsonRpc/JsonRpc.cs
+++ b/src/StreamJsonRpc/JsonRpc.cs
@@ -1456,8 +1456,6 @@ internal void AddLocalRpcMethod(MethodInfo handler, object? target, JsonRpcMetho
JsonRpcTargetOptions? options,
bool requestRevertOption)
{
- RpcTargetMetadata.EnableDynamicEventHandlerCreation();
-
options ??= JsonRpcTargetOptions.Default;
RpcTargetMetadata mapping =
exposingMembersOn.IsInterface ? RpcTargetMetadata.FromInterface(exposingMembersOn) :
diff --git a/src/StreamJsonRpc/MessagePackFormatter.cs b/src/StreamJsonRpc/MessagePackFormatter.cs
index dc101e142..e722b08ce 100644
--- a/src/StreamJsonRpc/MessagePackFormatter.cs
+++ b/src/StreamJsonRpc/MessagePackFormatter.cs
@@ -30,6 +30,8 @@ namespace StreamJsonRpc;
[RequiresDynamicCode(RuntimeReasons.Formatters), RequiresUnreferencedCode(RuntimeReasons.Formatters)]
public class MessagePackFormatter : FormatterBase, IJsonRpcMessageFormatter, IJsonRpcFormatterTracingCallbacks, IJsonRpcMessageFactory
{
+ private static readonly ProxyFactory ProxyFactory = ProxyFactory.Default;
+
///
/// The constant "jsonrpc", in its various forms.
///
@@ -248,6 +250,9 @@ void IJsonRpcFormatterTracingCallbacks.OnSerializationComplete(JsonRpcMessage me
}
}
+ ///
+ private protected override MessageFormatterRpcMarshaledContextTracker CreateMessageFormatterRpcMarshaledContextTracker(JsonRpc rpc) => new MessageFormatterRpcMarshaledContextTracker.Dynamic(rpc, ProxyFactory, this);
+
private static ReadOnlySequence GetSliceForNextToken(ref MessagePackReader reader)
{
SequencePosition startingPosition = reader.Position;
diff --git a/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs b/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs
index a147896f7..497e0a97d 100644
--- a/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs
+++ b/src/StreamJsonRpc/NerdbankMessagePackFormatter.cs
@@ -72,6 +72,8 @@ public partial class NerdbankMessagePackFormatter : FormatterBase, IJsonRpcMessa
],
}.WithObjectConverter();
+ private static readonly ProxyFactory ProxyFactory = ProxyFactory.NoDynamic;
+
private static readonly JsonRpcProxyOptions DefaultRpcMarshalableProxyOptions = new JsonRpcProxyOptions(JsonRpcProxyOptions.Default) { AcceptProxyWithExtraInterfaces = true, IsFrozen = true };
///
@@ -101,7 +103,6 @@ public partial class NerdbankMessagePackFormatter : FormatterBase, IJsonRpcMessa
/// Initializes a new instance of the class.
///
public NerdbankMessagePackFormatter()
- : base(ProxyFactory.NoDynamic)
{
// Set up initial options for our own message types.
this.envelopeSerializer = DefaultSerializer with
@@ -212,6 +213,8 @@ void IJsonRpcFormatterTracingCallbacks.OnSerializationComplete(JsonRpcMessage me
}
}
+ private protected override MessageFormatterRpcMarshaledContextTracker CreateMessageFormatterRpcMarshaledContextTracker(JsonRpc rpc) => new MessageFormatterRpcMarshaledContextTracker.PolyTypeShape(rpc, ProxyFactory, this, this.TypeShapeProvider);
+
private static MessagePackConverter GetRpcMarshalableConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.PublicProperties)] T>(ITypeShape shape)
where T : class
{
diff --git a/src/StreamJsonRpc/ProxyGeneration.cs b/src/StreamJsonRpc/ProxyGeneration.cs
index 1a0cdb255..e09fc21a9 100644
--- a/src/StreamJsonRpc/ProxyGeneration.cs
+++ b/src/StreamJsonRpc/ProxyGeneration.cs
@@ -70,10 +70,6 @@ internal static TypeInfo Get(ProxyInputs inputs)
ReadOnlySpan additionalContractInterfaces = inputs.AdditionalContractInterfaces.Span;
ReadOnlySpan<(Type Type, int Code)> implementedOptionalInterfaces = inputs.ImplementedOptionalInterfaces.Span;
- // Dynamic proxy generation requires the ability to generate dynamic event handlers.
- // Not a problem, since by calling into this method the user has already committed to running on a runtime that supports dynamic code.
- RpcTargetMetadata.EnableDynamicEventHandlerCreation();
-
VerifySupported(contractInterface.IsInterface, Resources.ClientProxyTypeArgumentMustBeAnInterface, contractInterface);
foreach (TypeInfo additionalContract in additionalContractInterfaces)
{
diff --git a/src/StreamJsonRpc/Reflection/MessageFormatterRpcMarshaledContextTracker.cs b/src/StreamJsonRpc/Reflection/MessageFormatterRpcMarshaledContextTracker.cs
index c3607823a..b4a82b421 100644
--- a/src/StreamJsonRpc/Reflection/MessageFormatterRpcMarshaledContextTracker.cs
+++ b/src/StreamJsonRpc/Reflection/MessageFormatterRpcMarshaledContextTracker.cs
@@ -19,7 +19,7 @@ namespace StreamJsonRpc.Reflection;
///
/// Tracks objects that get marshaled using the general marshaling protocol.
///
-internal partial class MessageFormatterRpcMarshaledContextTracker
+internal abstract partial class MessageFormatterRpcMarshaledContextTracker
{
private static readonly IReadOnlyCollection<(Type ImplicitlyMarshaledType, JsonRpcProxyOptions ProxyOptions, JsonRpcTargetOptions TargetOptions, RpcMarshalableAttribute Attribute)> ImplicitlyMarshaledTypes =
[
@@ -73,7 +73,7 @@ internal partial class MessageFormatterRpcMarshaledContextTracker
///
private ImmutableDictionary> outboundRequestIdMarshalMap = ImmutableDictionary>.Empty;
- internal MessageFormatterRpcMarshaledContextTracker(JsonRpc jsonRpc, ProxyFactory proxyFactory, IJsonRpcFormatterState formatterState)
+ protected MessageFormatterRpcMarshaledContextTracker(JsonRpc jsonRpc, ProxyFactory proxyFactory, IJsonRpcFormatterState formatterState)
{
this.jsonRpc = jsonRpc;
this.proxyFactory = proxyFactory;
@@ -276,7 +276,7 @@ internal MarshalToken GetToken(
optionalInterfacesCodes.Add(attribute.OptionalInterfaceCode);
this.jsonRpc.AddRpcInterfaceToTargetInternal(
- RpcTargetMetadata.FromInterface(attribute.OptionalInterface),
+ this.GetRpcTargetMetadata(attribute.OptionalInterface),
context.Proxy,
new JsonRpcTargetOptions(context.JsonRpcTargetOptions)
{
@@ -421,6 +421,8 @@ internal MarshalToken GetToken(
}
}
+ protected abstract RpcTargetMetadata GetRpcTargetMetadata([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType);
+
private static bool TryGetMarshalOptionsForTypeHelper(
Type type,
JsonRpcProxyOptions defaultProxyOptions,
@@ -598,6 +600,17 @@ public MarshalToken(int marshaled, long handle, string? lifetime = null, int[]?
public int[]? OptionalInterfacesCodes { get; set; }
}
+ [RequiresDynamicCode(RuntimeReasons.CloseGenerics)]
+ internal class Dynamic(JsonRpc jsonRpc, ProxyFactory proxyFactory, IJsonRpcFormatterState formatterState) : MessageFormatterRpcMarshaledContextTracker(jsonRpc, proxyFactory, formatterState)
+ {
+ protected override RpcTargetMetadata GetRpcTargetMetadata([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType) => RpcTargetMetadata.FromInterface(interfaceType);
+ }
+
+ internal class PolyTypeShape(JsonRpc jsonRpc, ProxyFactory proxyFactory, IJsonRpcFormatterState formatterState, ITypeShapeProvider typeShapeProvider) : MessageFormatterRpcMarshaledContextTracker(jsonRpc, proxyFactory, formatterState)
+ {
+ protected override RpcTargetMetadata GetRpcTargetMetadata([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType) => RpcTargetMetadata.FromShape(typeShapeProvider.GetTypeShapeOrThrow(interfaceType));
+ }
+
///
/// Defines the values of the "lifetime" property in the .
///
diff --git a/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs b/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs
index 2b7f7c1ce..baf324a2d 100644
--- a/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs
+++ b/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs
@@ -408,7 +408,7 @@ private class EventReceiver : IDisposable
{
private readonly JsonRpc jsonRpc;
private readonly object server;
- private readonly EventInfo eventInfo;
+ private readonly Action