소스 검색

gRPC JSON transcoding: Resolve all descriptors from registry (#45436)

James Newton-King 3 년 전
부모
커밋
1546adda3a
16개의 변경된 파일123개의 추가작업 그리고 71개의 파일을 삭제
  1. 4 1
      src/Grpc/JsonTranscoding/perf/Microsoft.AspNetCore.Grpc.Microbenchmarks/Json/JsonReading.cs
  2. 4 1
      src/Grpc/JsonTranscoding/perf/Microsoft.AspNetCore.Grpc.Microbenchmarks/Json/JsonWriting.cs
  3. 29 18
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/DescriptorRegistry.cs
  4. 9 0
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/AnyConverter.cs
  5. 1 10
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/EnumConverter.cs
  6. 2 2
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonConverterFactoryForWellKnownTypes.cs
  7. 1 1
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonConverterFactoryForWrappers.cs
  8. 0 11
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonConverterHelper.cs
  9. 3 3
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/MessageTypeInfoResolver.cs
  10. 1 1
      src/Grpc/JsonTranscoding/src/Shared/ServiceDescriptorHelpers.cs
  11. 11 5
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs
  12. 13 11
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterReadTests.cs
  13. 17 5
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterWriteTests.cs
  14. 17 0
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Infrastructure/TestHelpers.cs
  15. 6 1
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ServerStreamingServerCallHandlerTests.cs
  16. 5 1
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/UnaryServerCallHandlerTests.cs

+ 4 - 1
src/Grpc/JsonTranscoding/perf/Microsoft.AspNetCore.Grpc.Microbenchmarks/Json/JsonReading.cs

@@ -21,8 +21,11 @@ public class JsonReading
     [GlobalSetup]
     public void GlobalSetup()
     {
+        var descriptorRegistry = new DescriptorRegistry();
+        descriptorRegistry.RegisterFileDescriptor(HelloRequest.Descriptor.File);
+
         _requestJson = (new HelloRequest() { Name = "Hello world" }).ToString();
-        _serializerOptions = JsonConverterHelper.CreateSerializerOptions(new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty, new DescriptorRegistry()));
+        _serializerOptions = JsonConverterHelper.CreateSerializerOptions(new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty, descriptorRegistry));
         _jsonFormatter = new JsonParser(new JsonParser.Settings(recursionLimit: 100));
     }
 

+ 4 - 1
src/Grpc/JsonTranscoding/perf/Microsoft.AspNetCore.Grpc.Microbenchmarks/Json/JsonWriting.cs

@@ -21,9 +21,12 @@ public class JsonWriting
     [GlobalSetup]
     public void GlobalSetup()
     {
+        var descriptorRegistry = new DescriptorRegistry();
+        descriptorRegistry.RegisterFileDescriptor(HelloRequest.Descriptor.File);
+
         _request = new HelloRequest() { Name = "Hello world" };
         _serializerOptions = JsonConverterHelper.CreateSerializerOptions(
-            new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty, new DescriptorRegistry()));
+            new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty, descriptorRegistry));
         _jsonFormatter = new JsonFormatter(new JsonFormatter.Settings(formatDefaultValues: false));
     }
 

+ 29 - 18
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/DescriptorRegistry.cs

@@ -1,18 +1,30 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Collections.Concurrent;
 using Google.Protobuf.Reflection;
+using Google.Rpc;
 
 namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 
 internal sealed class DescriptorRegistry
 {
+    private readonly object _lock = new object();
     private readonly HashSet<FileDescriptor> _fileDescriptors = new HashSet<FileDescriptor>();
-    private readonly HashSet<EnumDescriptor> _enumDescriptors = new HashSet<EnumDescriptor>();
+    private readonly ConcurrentDictionary<Type, DescriptorBase> _typeDescriptorMap = new ConcurrentDictionary<Type, DescriptorBase>();
+
+    public DescriptorRegistry()
+    {
+        // For Grpc.Rpc.Status, which is used to send error responses.
+        AddFileDescriptorsRecursive(StatusReflection.Descriptor);
+    }
 
     public void RegisterFileDescriptor(FileDescriptor fileDescriptor)
     {
-        AddFileDescriptorsRecursive(fileDescriptor);
+        lock (_lock)
+        {
+            AddFileDescriptorsRecursive(fileDescriptor);
+        }
     }
 
     private void AddFileDescriptorsRecursive(FileDescriptor fileDescriptor)
@@ -27,15 +39,15 @@ internal sealed class DescriptorRegistry
         }
 
         // Non-nested enums.
-        foreach (var descriptor in fileDescriptor.EnumTypes)
+        foreach (var enumDescriptor in fileDescriptor.EnumTypes)
         {
-            _enumDescriptors.Add(descriptor);
+            _typeDescriptorMap[enumDescriptor.ClrType] = enumDescriptor;
         }
 
         // Search messages for nested enums.
         foreach (var messageDescriptor in fileDescriptor.MessageTypes)
         {
-            AddNestedEnumDescriptorsRecursive(messageDescriptor);
+            AddDescriptorsRecursive(messageDescriptor);
         }
 
         // Search imported files.
@@ -45,29 +57,28 @@ internal sealed class DescriptorRegistry
         }
     }
 
-    private void AddNestedEnumDescriptorsRecursive(MessageDescriptor messageDescriptor)
+    private void AddDescriptorsRecursive(MessageDescriptor messageDescriptor)
     {
+        // Type is null for map entry message types. Just skip adding them.
+        if (messageDescriptor.ClrType != null)
+        {
+            _typeDescriptorMap[messageDescriptor.ClrType] = messageDescriptor;
+        }
+
         foreach (var enumDescriptor in messageDescriptor.EnumTypes)
         {
-            _enumDescriptors.Add(enumDescriptor);
+            _typeDescriptorMap[enumDescriptor.ClrType] = enumDescriptor;
         }
 
         foreach (var nestedMessageDescriptor in messageDescriptor.NestedTypes)
         {
-            AddNestedEnumDescriptorsRecursive(nestedMessageDescriptor);
+            AddDescriptorsRecursive(nestedMessageDescriptor);
         }
     }
 
-    public EnumDescriptor? FindEnumDescriptorByType(Type enumType)
+    public DescriptorBase? FindDescriptorByType(Type enumType)
     {
-        foreach (var enumDescriptor in _enumDescriptors)
-        {
-            if (enumDescriptor.ClrType == enumType)
-            {
-                return enumDescriptor;
-            }
-        }
-
-        return null;
+        _typeDescriptorMap.TryGetValue(enumType, out var value);
+        return value;
     }
 }

+ 9 - 0
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/AnyConverter.cs

@@ -35,6 +35,10 @@ internal sealed class AnyConverter<TMessage> : SettingsConverterBase<TMessage> w
             throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'.");
         }
 
+        // Ensure the payload descriptor is registered. It's possible the payload type isn't in a proto referenced by the service, and is only in the user-specified TypeRegistry.
+        // There isn't a way to enumerate the contents of the TypeRegistry so we have to ensure the descriptor is present every time.
+        Context.DescriptorRegistry.RegisterFileDescriptor(descriptor.File);
+
         IMessage data;
         if (ServiceDescriptorHelpers.IsWellKnownType(descriptor))
         {
@@ -67,6 +71,11 @@ internal sealed class AnyConverter<TMessage> : SettingsConverterBase<TMessage> w
         {
             throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'.");
         }
+
+        // Ensure the payload descriptor is registered. It's possible the payload type isn't in a proto referenced by the service, and is only in the user-specified TypeRegistry.
+        // There isn't a way to enumerate the contents of the TypeRegistry so we have to ensure the descriptor is present every time.
+        Context.DescriptorRegistry.RegisterFileDescriptor(descriptor.File);
+        
         var valueMessage = descriptor.Parser.ParseFrom(data);
         writer.WriteStartObject();
         writer.WriteString(AnyTypeUrlField, typeUrl);

+ 1 - 10
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/EnumConverter.cs

@@ -20,7 +20,7 @@ internal sealed class EnumConverter<TEnum> : SettingsConverterBase<TEnum> where
         switch (reader.TokenType)
         {
             case JsonTokenType.String:
-                var enumDescriptor = ResolveEnumDescriptor(typeToConvert);
+                var enumDescriptor = (EnumDescriptor?)Context.DescriptorRegistry.FindDescriptorByType(typeToConvert);
                 if (enumDescriptor == null)
                 {
                     throw new InvalidOperationException($"Unable to resolve descriptor for {typeToConvert}.");
@@ -37,15 +37,6 @@ internal sealed class EnumConverter<TEnum> : SettingsConverterBase<TEnum> where
         }
     }
 
-    private EnumDescriptor? ResolveEnumDescriptor(Type typeToConvert)
-    {
-        // Enum types in generated DTOs are regular enum types. That means there is no static Descriptor property
-        // to get the enum descriptor from.
-        //
-        // Search for enum descriptor using the enum type in a registry of descriptors.
-        return Context.DescriptorRegistry.FindEnumDescriptorByType(typeToConvert);
-    }
-
     public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
     {
         if (Context.Settings.WriteEnumsAsIntegers)

+ 2 - 2
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonConverterFactoryForWellKnownTypes.cs

@@ -25,7 +25,7 @@ internal sealed class JsonConverterFactoryForWellKnownTypes : JsonConverterFacto
             return false;
         }
 
-        var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert);
+        var descriptor = _context.DescriptorRegistry.FindDescriptorByType(typeToConvert);
         if (descriptor == null)
         {
             return false;
@@ -37,7 +37,7 @@ internal sealed class JsonConverterFactoryForWellKnownTypes : JsonConverterFacto
     public override JsonConverter CreateConverter(
         Type typeToConvert, JsonSerializerOptions options)
     {
-        var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert)!;
+        var descriptor = _context.DescriptorRegistry.FindDescriptorByType(typeToConvert)!;
         var converterType = JsonConverterHelper.WellKnownTypeNames[descriptor.FullName];
 
         var converter = (JsonConverter)Activator.CreateInstance(

+ 1 - 1
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonConverterFactoryForWrappers.cs

@@ -26,7 +26,7 @@ internal sealed class JsonConverterFactoryForWrappers : JsonConverterFactory
             return false;
         }
 
-        var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert);
+        var descriptor = _context.DescriptorRegistry.FindDescriptorByType(typeToConvert);
         if (descriptor == null)
         {
             return false;

+ 0 - 11
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonConverterHelper.cs

@@ -131,17 +131,6 @@ internal static class JsonConverterHelper
         }
     }
 
-    internal static MessageDescriptor? GetMessageDescriptor(Type typeToConvert)
-    {
-        var property = typeToConvert.GetProperty("Descriptor", BindingFlags.Static | BindingFlags.Public, binder: null, typeof(MessageDescriptor), Type.EmptyTypes, modifiers: null);
-        if (property == null)
-        {
-            return null;
-        }
-
-        return property.GetValue(null, null) as MessageDescriptor;
-    }
-
     public static void PopulateMap(ref Utf8JsonReader reader, JsonSerializerOptions options, IMessage message, FieldDescriptor fieldDescriptor)
     {
         var mapFields = fieldDescriptor.MessageType.Fields.InFieldNumberOrder();

+ 3 - 3
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/MessageTypeInfoResolver.cs

@@ -59,7 +59,7 @@ internal sealed class MessageTypeInfoResolver : IJsonTypeInfoResolver
         return typeInfo;
     }
 
-    private static bool IsStandardMessage(Type type, [NotNullWhen(true)] out MessageDescriptor? messageDescriptor)
+    private bool IsStandardMessage(Type type, [NotNullWhen(true)] out MessageDescriptor? messageDescriptor)
     {
         if (!typeof(IMessage).IsAssignableFrom(type))
         {
@@ -67,10 +67,10 @@ internal sealed class MessageTypeInfoResolver : IJsonTypeInfoResolver
             return false;
         }
 
-        messageDescriptor = JsonConverterHelper.GetMessageDescriptor(type);
+        messageDescriptor = (MessageDescriptor?) _context.DescriptorRegistry.FindDescriptorByType(type);
         if (messageDescriptor == null)
         {
-            return false;
+            throw new InvalidOperationException("Couldn't resolve descriptor for message type: " + type);
         }
 
         // Wrappers and well known types are handled by converters.

+ 1 - 1
src/Grpc/JsonTranscoding/src/Shared/ServiceDescriptorHelpers.cs

@@ -49,7 +49,7 @@ internal static class ServiceDescriptorHelpers
     internal static bool IsWellKnownType(MessageDescriptor messageDescriptor) => messageDescriptor.File.Package == "google.protobuf" &&
         WellKnownTypeNames.Contains(messageDescriptor.File.Name);
 
-    internal static bool IsWrapperType(MessageDescriptor m) =>
+    internal static bool IsWrapperType(DescriptorBase m) =>
         m.File.Package == "google.protobuf" && m.File.Name == "google/protobuf/wrappers.proto";
 
     public static ServiceDescriptor? GetServiceDescriptor(Type serviceReflectionType)

+ 11 - 5
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs

@@ -1,7 +1,6 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
 using Google.Api;
 using Google.Protobuf;
 using Google.Protobuf.Reflection;
@@ -41,10 +40,7 @@ public class DynamicGrpcServiceRegistry
 
         AddServiceCore(c =>
         {
-            // File descriptor is done in JsonTranscodingServiceMethodProvider.
-            // Need to replicate that logic here so tests that lookup descriptors are successful.
-            var descriptorRegistry = _serviceProvider.GetRequiredService<DescriptorRegistry>();
-            descriptorRegistry.RegisterFileDescriptor(methodDescriptor.File);
+            RegisterDescriptor(methodDescriptor);
 
             var unaryMethod = new UnaryServerMethod<DynamicService, TRequest, TResponse>((service, request, context) => callHandler(request, context));
             var binder = CreateJsonTranscodingBinder<TRequest, TResponse>(methodDescriptor, c, new DynamicServiceInvokerResolver(unaryMethod));
@@ -63,6 +59,8 @@ public class DynamicGrpcServiceRegistry
 
         AddServiceCore(c =>
         {
+            RegisterDescriptor(methodDescriptor);
+
             var serverStreamingMethod = new ServerStreamingServerMethod<DynamicService, TRequest, TResponse>((service, request, stream, context) => callHandler(request, stream, context));
             var binder = CreateJsonTranscodingBinder<TRequest, TResponse>(methodDescriptor, c, new DynamicServiceInvokerResolver(serverStreamingMethod));
 
@@ -112,6 +110,14 @@ public class DynamicGrpcServiceRegistry
             });
     }
 
+    private void RegisterDescriptor(MethodDescriptor methodDescriptor)
+    {
+        // File descriptor is done in JsonTranscodingServiceMethodProvider.
+        // Need to replicate that logic here so tests that lookup descriptors are successful.
+        var descriptorRegistry = _serviceProvider.GetRequiredService<DescriptorRegistry>();
+        descriptorRegistry.RegisterFileDescriptor(methodDescriptor.File);
+    }
+
     private class DynamicEndpointRouteBuilder : IEndpointRouteBuilder
     {
         public DynamicEndpointRouteBuilder(IServiceProvider serviceProvider)

+ 13 - 11
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterReadTests.cs

@@ -8,6 +8,7 @@ using Google.Protobuf.Reflection;
 using Google.Protobuf.WellKnownTypes;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure;
 using Transcoding;
 using Xunit.Abstractions;
 
@@ -95,7 +96,7 @@ public class JsonConverterReadTests
         var serviceDescriptorRegistry = new DescriptorRegistry();
         serviceDescriptorRegistry.RegisterFileDescriptor(JsonTranscodingGreeter.Descriptor.File);
 
-        AssertReadJson<HelloRequest.Types.DataTypes>(json, serviceDescriptorRegistry: serviceDescriptorRegistry);
+        AssertReadJson<HelloRequest.Types.DataTypes>(json, descriptorRegistry: serviceDescriptorRegistry);
     }
 
     [Theory]
@@ -456,13 +457,10 @@ public class JsonConverterReadTests
     {
         var json = @"{""name"":"""",""country"":""ALPHA_3_COUNTRY_CODE_AFG""}";
 
-        var serviceDescriptorRegistry = new DescriptorRegistry();
-        serviceDescriptorRegistry.RegisterFileDescriptor(HelloService.Descriptor.File);
-
-        AssertReadJson<SayRequest>(json, serviceDescriptorRegistry: serviceDescriptorRegistry);
+        AssertReadJson<SayRequest>(json);
     }
 
-    private TValue AssertReadJson<TValue>(string value, GrpcJsonSettings? settings = null, DescriptorRegistry? serviceDescriptorRegistry = null) where TValue : IMessage, new()
+    private TValue AssertReadJson<TValue>(string value, GrpcJsonSettings? settings = null, DescriptorRegistry? descriptorRegistry = null) where TValue : IMessage, new()
     {
         var typeRegistery = TypeRegistry.FromFiles(
             HelloRequest.Descriptor.File,
@@ -474,7 +472,9 @@ public class JsonConverterReadTests
 
         var objectOld = formatter.Parse<TValue>(value);
 
-        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery, serviceDescriptorRegistry);
+        descriptorRegistry ??= new DescriptorRegistry();
+        descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(typeof(TValue)).File);
+        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery, descriptorRegistry);
 
         var objectNew = JsonSerializer.Deserialize<TValue>(value, jsonSerializerOptions)!;
 
@@ -489,13 +489,15 @@ public class JsonConverterReadTests
         return objectNew;
     }
 
-    private void AssertReadJsonError<TValue>(string value, Action<Exception> assertException, GrpcJsonSettings? settings = null, DescriptorRegistry? serviceDescriptorRegistry = null) where TValue : IMessage, new()
+    private void AssertReadJsonError<TValue>(string value, Action<Exception> assertException, GrpcJsonSettings? settings = null, DescriptorRegistry? descriptorRegistry = null) where TValue : IMessage, new()
     {
         var typeRegistery = TypeRegistry.FromFiles(
             HelloRequest.Descriptor.File,
             Timestamp.Descriptor.File);
 
-        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery, serviceDescriptorRegistry);
+        descriptorRegistry ??= new DescriptorRegistry();
+        descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(typeof(TValue)).File);
+        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery, descriptorRegistry);
 
         var ex = Assert.ThrowsAny<Exception>(() => JsonSerializer.Deserialize<TValue>(value, jsonSerializerOptions));
         assertException(ex);
@@ -508,12 +510,12 @@ public class JsonConverterReadTests
         assertException(ex);
     }
 
-    internal static JsonSerializerOptions CreateSerializerOptions(GrpcJsonSettings? settings, TypeRegistry? typeRegistery, DescriptorRegistry? serviceDescriptorRegistry)
+    internal static JsonSerializerOptions CreateSerializerOptions(GrpcJsonSettings? settings, TypeRegistry? typeRegistery, DescriptorRegistry descriptorRegistry)
     {
         var context = new JsonContext(
             settings ?? new GrpcJsonSettings(),
             typeRegistery ?? TypeRegistry.Empty,
-            serviceDescriptorRegistry ?? new DescriptorRegistry());
+            descriptorRegistry);
 
         return JsonConverterHelper.CreateSerializerOptions(context);
     }

+ 17 - 5
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterWriteTests.cs

@@ -9,8 +9,10 @@ using Google.Protobuf.Reflection;
 using Google.Protobuf.WellKnownTypes;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure;
 using Transcoding;
 using Xunit.Abstractions;
+using Type = System.Type;
 
 namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.ConverterTests;
 
@@ -221,8 +223,9 @@ public class JsonConverterWriteTests
     {
         var v = new Int64Value { Value = 1 };
 
+        var descriptorRegistry = CreateDescriptorRegistry(typeof(Int64Value));
         var settings = new GrpcJsonSettings { WriteInt64sAsStrings = writeInt64sAsStrings };
-        var jsonSerializerOptions = CreateSerializerOptions(settings, TypeRegistry.Empty);
+        var jsonSerializerOptions = CreateSerializerOptions(settings, TypeRegistry.Empty, descriptorRegistry);
         var json = JsonSerializer.Serialize(v, jsonSerializerOptions);
 
         Assert.Equal(expectedJson, json);
@@ -235,8 +238,9 @@ public class JsonConverterWriteTests
     {
         var v = new UInt64Value { Value = 2 };
 
+        var descriptorRegistry = CreateDescriptorRegistry(typeof(UInt64Value));
         var settings = new GrpcJsonSettings { WriteInt64sAsStrings = writeInt64sAsStrings };
-        var jsonSerializerOptions = CreateSerializerOptions(settings, TypeRegistry.Empty);
+        var jsonSerializerOptions = CreateSerializerOptions(settings, TypeRegistry.Empty, descriptorRegistry);
         var json = JsonSerializer.Serialize(v, jsonSerializerOptions);
 
         Assert.Equal(expectedJson, json);
@@ -496,7 +500,8 @@ public class JsonConverterWriteTests
         _output.WriteLine("Old:");
         _output.WriteLine(jsonOld);
 
-        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery);
+        var descriptorRegistry = CreateDescriptorRegistry(typeof(TValue));
+        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery, descriptorRegistry);
         var jsonNew = JsonSerializer.Serialize(value, jsonSerializerOptions);
 
         _output.WriteLine("New:");
@@ -509,9 +514,16 @@ public class JsonConverterWriteTests
         Assert.True(comparer.Equals(doc1.RootElement, doc2.RootElement));
     }
 
-    internal static JsonSerializerOptions CreateSerializerOptions(GrpcJsonSettings? settings, TypeRegistry? typeRegistery)
+    private static DescriptorRegistry CreateDescriptorRegistry(Type type)
     {
-        var context = new JsonContext(settings ?? new GrpcJsonSettings(), typeRegistery ?? TypeRegistry.Empty, new DescriptorRegistry());
+        var descriptorRegistry = new DescriptorRegistry();
+        descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(type).File);
+        return descriptorRegistry;
+    }
+
+    internal static JsonSerializerOptions CreateSerializerOptions(GrpcJsonSettings? settings, TypeRegistry? typeRegistery, DescriptorRegistry descriptorRegistry)
+    {
+        var context = new JsonContext(settings ?? new GrpcJsonSettings(), typeRegistery ?? TypeRegistry.Empty, descriptorRegistry);
 
         return JsonConverterHelper.CreateSerializerOptions(context);
     }

+ 17 - 0
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Infrastructure/TestHelpers.cs

@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Net;
+using System.Reflection;
 using Google.Protobuf.Reflection;
 using Grpc.AspNetCore.Server;
 using Grpc.Core.Interceptors;
@@ -31,6 +32,22 @@ internal static class TestHelpers
         return httpContext;
     }
 
+    internal static MessageDescriptor GetMessageDescriptor(Type typeToConvert)
+    {
+        var property = typeToConvert.GetProperty("Descriptor", BindingFlags.Static | BindingFlags.Public, binder: null, typeof(MessageDescriptor), Type.EmptyTypes, modifiers: null);
+        if (property == null)
+        {
+            throw new InvalidOperationException("Couldn't find Descriptor property on message type: " + typeToConvert);
+        }
+
+        var descriptor = property.GetValue(null, null) as MessageDescriptor;
+        if (descriptor == null)
+        {
+            throw new InvalidOperationException("Couldn't resolve MessageDescriptor for message type: " + typeToConvert);
+        }
+        return descriptor;
+    }
+
     private class TestInterceptorActivator<T> : IGrpcInterceptorActivator<T> where T : Interceptor
     {
         public GrpcActivatorHandle<Interceptor> Create(IServiceProvider serviceProvider, InterceptorRegistration interceptorRegistration)

+ 6 - 1
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ServerStreamingServerCallHandlerTests.cs

@@ -342,7 +342,12 @@ public class ServerStreamingServerCallHandlerTests : LoggedTest
             new TestGrpcServiceActivator<JsonTranscodingGreeterService>());
 
         var jsonSettings = jsonTranscodingOptions?.JsonSettings ?? new GrpcJsonSettings() { WriteIndented = false };
-        var jsonContext = new JsonContext(jsonSettings, jsonTranscodingOptions?.TypeRegistry ?? TypeRegistry.Empty, new DescriptorRegistry());
+
+        var descriptorRegistry = new DescriptorRegistry();
+        descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(typeof(TRequest)).File);
+        descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(typeof(TResponse)).File);
+
+        var jsonContext = new JsonContext(jsonSettings, jsonTranscodingOptions?.TypeRegistry ?? TypeRegistry.Empty, descriptorRegistry);
 
         return new ServerStreamingServerCallHandler<JsonTranscodingGreeterService, TRequest, TResponse>(
             callInvoker,

+ 5 - 1
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/UnaryServerCallHandlerTests.cs

@@ -1271,10 +1271,14 @@ public class UnaryServerCallHandlerTests : LoggedTest
             MethodOptions.Create(new[] { serviceOptions }),
             new TestGrpcServiceActivator<JsonTranscodingGreeterService>());
 
+        var descriptorRegistry = new DescriptorRegistry();
+        descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(typeof(TRequest)).File);
+        descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(typeof(TResponse)).File);
+
         var jsonContext = new JsonContext(
             jsonTranscodingOptions?.JsonSettings ?? new GrpcJsonSettings(),
             jsonTranscodingOptions?.TypeRegistry ?? TypeRegistry.Empty,
-            new DescriptorRegistry());
+            descriptorRegistry);
 
         return new UnaryServerCallHandler<JsonTranscodingGreeterService, TRequest, TResponse>(
             unaryServerCallInvoker,