Преглед изворни кода

gRPC JSON transcoding: Support deserializing non-nested enum from string (#45273)

James Newton-King пре 3 година
родитељ
комит
600eb9aa53
20 измењених фајлова са 268 додато и 45 уклоњено
  1. 2 1
      src/Grpc/JsonTranscoding/perf/Microsoft.AspNetCore.Grpc.Microbenchmarks/Json/JsonReading.cs
  2. 2 1
      src/Grpc/JsonTranscoding/perf/Microsoft.AspNetCore.Grpc.Microbenchmarks/Json/JsonWriting.cs
  3. 6 2
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingOptions.cs
  4. 25 0
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingServiceExtensions.cs
  5. 5 5
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingProviderServiceBinder.cs
  6. 9 4
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingServiceMethodProvider.cs
  7. 73 0
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/DescriptorRegistry.cs
  8. 6 19
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/EnumConverter.cs
  9. 3 1
      src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/JsonContext.cs
  10. 6 0
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs
  11. 29 0
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/UnaryTests.cs
  12. 26 7
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterReadTests.cs
  13. 12 1
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterWriteTests.cs
  14. 2 2
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/JsonTranscodingServiceMethodProviderTests.cs
  15. 2 0
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj
  16. 11 0
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Proto/Issue045270/country.proto
  17. 27 0
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Proto/Issue045270/hello.proto
  18. 2 1
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ServerStreamingServerCallHandlerTests.cs
  19. 3 1
      src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/UnaryServerCallHandlerTests.cs
  20. 17 0
      src/Grpc/JsonTranscoding/test/testassets/IntegrationTestsWebsite/Protos/greet.proto

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

@@ -7,6 +7,7 @@ using Google.Protobuf;
 using Google.Protobuf.Reflection;
 using Greet;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 
 namespace Microsoft.AspNetCore.Grpc.Microbenchmarks.Json;
@@ -21,7 +22,7 @@ public class JsonReading
     public void GlobalSetup()
     {
         _requestJson = (new HelloRequest() { Name = "Hello world" }).ToString();
-        _serializerOptions = JsonConverterHelper.CreateSerializerOptions(new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty));
+        _serializerOptions = JsonConverterHelper.CreateSerializerOptions(new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty, new DescriptorRegistry()));
         _jsonFormatter = new JsonParser(new JsonParser.Settings(recursionLimit: 100));
     }
 

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

@@ -7,6 +7,7 @@ using Google.Protobuf;
 using Google.Protobuf.Reflection;
 using Greet;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 
 namespace Microsoft.AspNetCore.Grpc.Microbenchmarks.Json;
@@ -22,7 +23,7 @@ public class JsonWriting
     {
         _request = new HelloRequest() { Name = "Hello world" };
         _serializerOptions = JsonConverterHelper.CreateSerializerOptions(
-            new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty));
+            new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty, new DescriptorRegistry()));
         _jsonFormatter = new JsonFormatter(new JsonFormatter.Settings(formatDefaultValues: false));
     }
 

+ 6 - 2
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingOptions.cs

@@ -3,6 +3,7 @@
 
 using System.Text.Json;
 using Google.Protobuf.Reflection;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 
 namespace Microsoft.AspNetCore.Grpc.JsonTranscoding;
@@ -18,16 +19,19 @@ public sealed class GrpcJsonTranscodingOptions
     public GrpcJsonTranscodingOptions()
     {
         _unaryOptions = new Lazy<JsonSerializerOptions>(
-            () => JsonConverterHelper.CreateSerializerOptions(new JsonContext(JsonSettings, TypeRegistry)),
+            () => JsonConverterHelper.CreateSerializerOptions(new JsonContext(JsonSettings, TypeRegistry, DescriptorRegistry)),
             LazyThreadSafetyMode.ExecutionAndPublication);
         _serverStreamingOptions = new Lazy<JsonSerializerOptions>(
-            () => JsonConverterHelper.CreateSerializerOptions(new JsonContext(JsonSettings, TypeRegistry), isStreamingOptions: true),
+            () => JsonConverterHelper.CreateSerializerOptions(new JsonContext(JsonSettings, TypeRegistry, DescriptorRegistry), isStreamingOptions: true),
             LazyThreadSafetyMode.ExecutionAndPublication);
     }
 
     internal JsonSerializerOptions UnarySerializerOptions => _unaryOptions.Value;
     internal JsonSerializerOptions ServerStreamingSerializerOptions => _serverStreamingOptions.Value;
 
+    // Registry is set by DI during startup.
+    internal DescriptorRegistry DescriptorRegistry { get; set; } = default!;
+
     /// <summary>
     /// Gets or sets the <see cref="Google.Protobuf.Reflection.TypeRegistry"/> used to lookup types from type names.
     /// </summary>

+ 25 - 0
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingServiceExtensions.cs

@@ -4,8 +4,10 @@
 using Grpc.AspNetCore.Server;
 using Grpc.AspNetCore.Server.Model;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding;
 using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
 
 namespace Microsoft.Extensions.DependencyInjection;
 
@@ -27,6 +29,8 @@ public static class GrpcJsonTranscodingServiceExtensions
         }
 
         builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IServiceMethodProvider<>), typeof(JsonTranscodingServiceMethodProvider<>)));
+        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<GrpcJsonTranscodingOptions>, GrpcJsonTranscodingOptionsSetup>());
+        builder.Services.TryAddSingleton<DescriptorRegistry>();
 
         return builder;
     }
@@ -45,6 +49,27 @@ public static class GrpcJsonTranscodingServiceExtensions
         }
 
         builder.Services.Configure(configureOptions);
+
         return builder.AddJsonTranscoding();
     }
+
+    private sealed class GrpcJsonTranscodingOptionsSetup : IConfigureOptions<GrpcJsonTranscodingOptions>
+    {
+        private readonly DescriptorRegistry _descriptorRegistry;
+
+        public GrpcJsonTranscodingOptionsSetup(DescriptorRegistry descriptorRegistry)
+        {
+            _descriptorRegistry = descriptorRegistry;
+        }
+
+        public void Configure(GrpcJsonTranscodingOptions options)
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            options.DescriptorRegistry = _descriptorRegistry;
+        }
+    }
 }

+ 5 - 5
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingProviderServiceBinder.cs

@@ -34,7 +34,7 @@ internal sealed partial class JsonTranscodingProviderServiceBinder<TService> : S
     private readonly GrpcServiceOptions _globalOptions;
     private readonly GrpcServiceOptions<TService> _serviceOptions;
     private readonly IGrpcServiceActivator<TService> _serviceActivator;
-    private readonly GrpcJsonTranscodingOptions _JsonTranscodingOptions;
+    private readonly GrpcJsonTranscodingOptions _jsonTranscodingOptions;
     private readonly ILoggerFactory _loggerFactory;
     private readonly ILogger _logger;
 
@@ -46,7 +46,7 @@ internal sealed partial class JsonTranscodingProviderServiceBinder<TService> : S
         GrpcServiceOptions<TService> serviceOptions,
         ILoggerFactory loggerFactory,
         IGrpcServiceActivator<TService> serviceActivator,
-        GrpcJsonTranscodingOptions JsonTranscodingOptions)
+        GrpcJsonTranscodingOptions jsonTranscodingOptions)
     {
         _context = context;
         _invokerResolver = invokerResolver;
@@ -54,7 +54,7 @@ internal sealed partial class JsonTranscodingProviderServiceBinder<TService> : S
         _globalOptions = globalOptions;
         _serviceOptions = serviceOptions;
         _serviceActivator = serviceActivator;
-        _JsonTranscodingOptions = JsonTranscodingOptions;
+        _jsonTranscodingOptions = jsonTranscodingOptions;
         _loggerFactory = loggerFactory;
         _logger = loggerFactory.CreateLogger<JsonTranscodingProviderServiceBinder<TService>>();
     }
@@ -168,7 +168,7 @@ internal sealed partial class JsonTranscodingProviderServiceBinder<TService> : S
             methodInvoker,
             _loggerFactory,
             descriptorInfo,
-            _JsonTranscodingOptions.UnarySerializerOptions);
+            _jsonTranscodingOptions.UnarySerializerOptions);
 
         return (callHandler.HandleCallAsync, metadata);
     }
@@ -195,7 +195,7 @@ internal sealed partial class JsonTranscodingProviderServiceBinder<TService> : S
             methodInvoker,
             _loggerFactory,
             descriptorInfo,
-            _JsonTranscodingOptions.ServerStreamingSerializerOptions);
+            _jsonTranscodingOptions.ServerStreamingSerializerOptions);
 
         return (callHandler.HandleCallAsync, metadata);
     }

+ 9 - 4
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingServiceMethodProvider.cs

@@ -16,23 +16,26 @@ internal sealed partial class JsonTranscodingServiceMethodProvider<TService> : I
     private readonly ILogger<JsonTranscodingServiceMethodProvider<TService>> _logger;
     private readonly GrpcServiceOptions _globalOptions;
     private readonly GrpcServiceOptions<TService> _serviceOptions;
-    private readonly GrpcJsonTranscodingOptions _JsonTranscodingOptions;
+    private readonly GrpcJsonTranscodingOptions _jsonTranscodingOptions;
     private readonly ILoggerFactory _loggerFactory;
     private readonly IGrpcServiceActivator<TService> _serviceActivator;
+    private readonly DescriptorRegistry _serviceDescriptorRegistry;
 
     public JsonTranscodingServiceMethodProvider(
         ILoggerFactory loggerFactory,
         IOptions<GrpcServiceOptions> globalOptions,
         IOptions<GrpcServiceOptions<TService>> serviceOptions,
         IGrpcServiceActivator<TService> serviceActivator,
-        IOptions<GrpcJsonTranscodingOptions> JsonTranscodingOptions)
+        IOptions<GrpcJsonTranscodingOptions> jsonTranscodingOptions,
+        DescriptorRegistry serviceDescriptorRegistry)
     {
         _logger = loggerFactory.CreateLogger<JsonTranscodingServiceMethodProvider<TService>>();
         _globalOptions = globalOptions.Value;
         _serviceOptions = serviceOptions.Value;
-        _JsonTranscodingOptions = JsonTranscodingOptions.Value;
+        _jsonTranscodingOptions = jsonTranscodingOptions.Value;
         _loggerFactory = loggerFactory;
         _serviceActivator = serviceActivator;
+        _serviceDescriptorRegistry = serviceDescriptorRegistry;
     }
 
     public void OnServiceMethodDiscovery(ServiceMethodProviderContext<TService> context)
@@ -57,6 +60,8 @@ internal sealed partial class JsonTranscodingServiceMethodProvider<TService> : I
 
             if (serviceDescriptor != null)
             {
+                _serviceDescriptorRegistry.RegisterFileDescriptor(serviceDescriptor.File);
+
                 var binder = new JsonTranscodingProviderServiceBinder<TService>(
                     context,
                     new ReflectionServiceInvokerResolver<TService>(serviceParameter.ParameterType),
@@ -65,7 +70,7 @@ internal sealed partial class JsonTranscodingServiceMethodProvider<TService> : I
                     _serviceOptions,
                     _loggerFactory,
                     _serviceActivator,
-                    _JsonTranscodingOptions);
+                    _jsonTranscodingOptions);
 
                 try
                 {

+ 73 - 0
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/DescriptorRegistry.cs

@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Google.Protobuf.Reflection;
+
+namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
+
+internal sealed class DescriptorRegistry
+{
+    private readonly HashSet<FileDescriptor> _fileDescriptors = new HashSet<FileDescriptor>();
+    private readonly HashSet<EnumDescriptor> _enumDescriptors = new HashSet<EnumDescriptor>();
+
+    public void RegisterFileDescriptor(FileDescriptor fileDescriptor)
+    {
+        AddFileDescriptorsRecursive(fileDescriptor);
+    }
+
+    private void AddFileDescriptorsRecursive(FileDescriptor fileDescriptor)
+    {
+        var added = _fileDescriptors.Add(fileDescriptor);
+
+        // If a descriptor is already added then all its types and dependencies are already be present.
+        // In this case, exit immediately. This guards against the possibility of cyclical dependencies between files.
+        if (!added)
+        {
+            return;
+        }
+
+        // Non-nested enums.
+        foreach (var descriptor in fileDescriptor.EnumTypes)
+        {
+            _enumDescriptors.Add(descriptor);
+        }
+
+        // Search messages for nested enums.
+        foreach (var messageDescriptor in fileDescriptor.MessageTypes)
+        {
+            AddNestedEnumDescriptorsRecursive(messageDescriptor);
+        }
+
+        // Search imported files.
+        foreach (var dependencyFile in fileDescriptor.Dependencies)
+        {
+            AddFileDescriptorsRecursive(dependencyFile);
+        }
+    }
+
+    private void AddNestedEnumDescriptorsRecursive(MessageDescriptor messageDescriptor)
+    {
+        foreach (var enumDescriptor in messageDescriptor.EnumTypes)
+        {
+            _enumDescriptors.Add(enumDescriptor);
+        }
+
+        foreach (var nestedMessageDescriptor in messageDescriptor.NestedTypes)
+        {
+            AddNestedEnumDescriptorsRecursive(nestedMessageDescriptor);
+        }
+    }
+
+    public EnumDescriptor? FindEnumDescriptorByType(Type enumType)
+    {
+        foreach (var enumDescriptor in _enumDescriptors)
+        {
+            if (enumDescriptor.ClrType == enumType)
+            {
+                return enumDescriptor;
+            }
+        }
+
+        return null;
+    }
+}

+ 6 - 19
src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Json/EnumConverter.cs

@@ -37,26 +37,13 @@ internal sealed class EnumConverter<TEnum> : SettingsConverterBase<TEnum> where
         }
     }
 
-    private static EnumDescriptor? ResolveEnumDescriptor(Type typeToConvert)
+    private EnumDescriptor? ResolveEnumDescriptor(Type typeToConvert)
     {
-        var containingType = typeToConvert?.DeclaringType?.DeclaringType;
-
-        if (containingType != null)
-        {
-            var messageDescriptor = JsonConverterHelper.GetMessageDescriptor(containingType);
-            if (messageDescriptor != null)
-            {
-                for (var i = 0; i < messageDescriptor.EnumTypes.Count; i++)
-                {
-                    if (messageDescriptor.EnumTypes[i].ClrType == typeToConvert)
-                    {
-                        return messageDescriptor.EnumTypes[i];
-                    }
-                }
-            }
-        }
-
-        return null;
+        // 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)

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

@@ -7,12 +7,14 @@ namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 
 internal sealed class JsonContext
 {
-    public JsonContext(GrpcJsonSettings settings, TypeRegistry typeRegistry)
+    public JsonContext(GrpcJsonSettings settings, TypeRegistry typeRegistry, DescriptorRegistry descriptorRegistry)
     {
         Settings = settings;
         TypeRegistry = typeRegistry;
+        DescriptorRegistry = descriptorRegistry;
     }
 
     public GrpcJsonSettings Settings { get; }
     public TypeRegistry TypeRegistry { get; }
+    public DescriptorRegistry DescriptorRegistry { get; }
 }

+ 6 - 0
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs

@@ -1,6 +1,7 @@
 // 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;
@@ -40,6 +41,11 @@ 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);
+
             var unaryMethod = new UnaryServerMethod<DynamicService, TRequest, TResponse>((service, request, context) => callHandler(request, context));
             var binder = CreateJsonTranscodingBinder<TRequest, TResponse>(methodDescriptor, c, new DynamicServiceInvokerResolver(unaryMethod));
 

+ 29 - 0
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/UnaryTests.cs

@@ -222,4 +222,33 @@ public class UnaryTests : IntegrationTestBase
         Assert.Equal("utf-8", response.Content.Headers.ContentType!.CharSet);
         Assert.Contains(errorMessage, result.RootElement.GetProperty("message").GetString());
     }
+
+    [Fact]
+    public async Task Request_SendEnumString_Success()
+    {
+        // Arrange
+        Task<HelloReply> UnaryMethod(EnumHelloRequest request, ServerCallContext context)
+        {
+            return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}!" });
+        }
+        var method = Fixture.DynamicGrpc.AddUnaryMethod<EnumHelloRequest, HelloReply>(
+            UnaryMethod,
+            Greeter.Descriptor.FindMethodByName("SayHelloPostEnum"));
+
+        var client = new HttpClient(Fixture.Handler) { BaseAddress = new Uri("http://localhost") };
+
+        var requestMessage = new EnumHelloRequest { Name = NameOptions.Jane };
+        var content = new ByteArrayContent(Encoding.UTF8.GetBytes(requestMessage.ToString()));
+        content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
+
+        // Act
+        var response = await client.PostAsync("/v1/greeter_enum", content).DefaultTimeout();
+        var responseText = await response.Content.ReadAsStringAsync();
+        using var result = JsonDocument.Parse(responseText);
+
+        // Assert
+        Assert.Equal("application/json", response.Content.Headers.ContentType!.MediaType);
+        Assert.Equal("utf-8", response.Content.Headers.ContentType!.CharSet);
+        Assert.Equal("Hello Jane!", result.RootElement.GetProperty("message").GetString());
+    }
 }

+ 26 - 7
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterReadTests.cs

@@ -2,9 +2,11 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Text.Json;
+using Example.Hello;
 using Google.Protobuf;
 using Google.Protobuf.Reflection;
 using Google.Protobuf.WellKnownTypes;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 using Transcoding;
 using Xunit.Abstractions;
@@ -90,7 +92,10 @@ public class JsonConverterReadTests
   ""singleEnum"": ""NESTED_ENUM_UNSPECIFIED""
 }";
 
-        AssertReadJson<HelloRequest.Types.DataTypes>(json);
+        var serviceDescriptorRegistry = new DescriptorRegistry();
+        serviceDescriptorRegistry.RegisterFileDescriptor(JsonTranscodingGreeter.Descriptor.File);
+
+        AssertReadJson<HelloRequest.Types.DataTypes>(json, serviceDescriptorRegistry: serviceDescriptorRegistry);
     }
 
     [Theory]
@@ -446,7 +451,18 @@ public class JsonConverterReadTests
         AssertReadJson<Int64Value>(json);
     }
 
-    private TValue AssertReadJson<TValue>(string value, GrpcJsonSettings? settings = null) where TValue : IMessage, new()
+    [Fact]
+    public void Enum_Imported()
+    {
+        var json = @"{""name"":"""",""country"":""ALPHA_3_COUNTRY_CODE_AFG""}";
+
+        var serviceDescriptorRegistry = new DescriptorRegistry();
+        serviceDescriptorRegistry.RegisterFileDescriptor(HelloService.Descriptor.File);
+
+        AssertReadJson<SayRequest>(json, serviceDescriptorRegistry: serviceDescriptorRegistry);
+    }
+
+    private TValue AssertReadJson<TValue>(string value, GrpcJsonSettings? settings = null, DescriptorRegistry? serviceDescriptorRegistry = null) where TValue : IMessage, new()
     {
         var typeRegistery = TypeRegistry.FromFiles(
             HelloRequest.Descriptor.File,
@@ -458,7 +474,7 @@ public class JsonConverterReadTests
 
         var objectOld = formatter.Parse<TValue>(value);
 
-        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery);
+        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery, serviceDescriptorRegistry);
 
         var objectNew = JsonSerializer.Deserialize<TValue>(value, jsonSerializerOptions)!;
 
@@ -473,13 +489,13 @@ public class JsonConverterReadTests
         return objectNew;
     }
 
-    private void AssertReadJsonError<TValue>(string value, Action<Exception> assertException, GrpcJsonSettings? settings = null) where TValue : IMessage, new()
+    private void AssertReadJsonError<TValue>(string value, Action<Exception> assertException, GrpcJsonSettings? settings = null, DescriptorRegistry? serviceDescriptorRegistry = null) where TValue : IMessage, new()
     {
         var typeRegistery = TypeRegistry.FromFiles(
             HelloRequest.Descriptor.File,
             Timestamp.Descriptor.File);
 
-        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery);
+        var jsonSerializerOptions = CreateSerializerOptions(settings, typeRegistery, serviceDescriptorRegistry);
 
         var ex = Assert.ThrowsAny<Exception>(() => JsonSerializer.Deserialize<TValue>(value, jsonSerializerOptions));
         assertException(ex);
@@ -492,9 +508,12 @@ public class JsonConverterReadTests
         assertException(ex);
     }
 
-    internal static JsonSerializerOptions CreateSerializerOptions(GrpcJsonSettings? settings, TypeRegistry? typeRegistery)
+    internal static JsonSerializerOptions CreateSerializerOptions(GrpcJsonSettings? settings, TypeRegistry? typeRegistery, DescriptorRegistry? serviceDescriptorRegistry)
     {
-        var context = new JsonContext(settings ?? new GrpcJsonSettings(), typeRegistery ?? TypeRegistry.Empty);
+        var context = new JsonContext(
+            settings ?? new GrpcJsonSettings(),
+            typeRegistery ?? TypeRegistry.Empty,
+            serviceDescriptorRegistry ?? new DescriptorRegistry());
 
         return JsonConverterHelper.CreateSerializerOptions(context);
     }

+ 12 - 1
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ConverterTests/JsonConverterWriteTests.cs

@@ -3,9 +3,11 @@
 
 using System.Text;
 using System.Text.Json;
+using Example.Hello;
 using Google.Protobuf;
 using Google.Protobuf.Reflection;
 using Google.Protobuf.WellKnownTypes;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 using Transcoding;
 using Xunit.Abstractions;
@@ -466,6 +468,15 @@ public class JsonConverterWriteTests
         AssertWrittenJson(dataTypes, new GrpcJsonSettings { WriteEnumsAsIntegers = true, IgnoreDefaultValues = true });
     }
 
+    [Fact]
+    public void Enum_Imported()
+    {
+        var m = new SayRequest();
+        m.Country = Example.Country.Alpha3CountryCode.Afg;
+
+        AssertWrittenJson(m);
+    }
+
     private void AssertWrittenJson<TValue>(TValue value, GrpcJsonSettings? settings = null, bool? compareRawStrings = null) where TValue : IMessage
     {
         var typeRegistery = TypeRegistry.FromFiles(
@@ -500,7 +511,7 @@ public class JsonConverterWriteTests
 
     internal static JsonSerializerOptions CreateSerializerOptions(GrpcJsonSettings? settings, TypeRegistry? typeRegistery)
     {
-        var context = new JsonContext(settings ?? new GrpcJsonSettings(), typeRegistery ?? TypeRegistry.Empty);
+        var context = new JsonContext(settings ?? new GrpcJsonSettings(), typeRegistery ?? TypeRegistry.Empty, new DescriptorRegistry());
 
         return JsonConverterHelper.CreateSerializerOptions(context);
     }

+ 2 - 2
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/JsonTranscodingServiceMethodProviderTests.cs

@@ -247,9 +247,9 @@ public class JsonTranscodingServiceMethodProviderTests
         {
             configureLogging?.Invoke(log);
         });
-        serviceCollection.AddGrpc();
+        var builder = serviceCollection.AddGrpc();
         serviceCollection.RemoveAll(typeof(IServiceMethodProvider<>));
-        serviceCollection.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IServiceMethodProvider<>), typeof(JsonTranscodingServiceMethodProvider<>)));
+        builder.AddJsonTranscoding();
 
         IEndpointRouteBuilder endpointRouteBuilder = new TestEndpointRouteBuilder(serviceCollection.BuildServiceProvider());
 

+ 2 - 0
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj

@@ -7,6 +7,8 @@
   <ItemGroup>
     <Protobuf Include="Proto\httpbody.proto" GrpcServices="Both" />
     <Protobuf Include="Proto\transcoding.proto" GrpcServices="Both" />
+    <Protobuf Include="Proto\Issue045270\hello.proto" GrpcServices="Both" />
+    <Protobuf Include="Proto\Issue045270\country.proto" GrpcServices="Both" />
 
     <Compile Include="..\Shared\TestGrpcServiceActivator.cs" Link="Infrastructure\TestGrpcServiceActivator.cs" />
 

+ 11 - 0
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Proto/Issue045270/country.proto

@@ -0,0 +1,11 @@
+syntax = "proto3";
+
+package example.country;
+
+option csharp_namespace = "Example.Country";
+
+enum Alpha3CountryCode
+{
+  ALPHA_3_COUNTRY_CODE_UNSPECIFIED = 0;
+  ALPHA_3_COUNTRY_CODE_AFG = 4;
+}

+ 27 - 0
src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Proto/Issue045270/hello.proto

@@ -0,0 +1,27 @@
+syntax = "proto3";
+
+import "google/api/annotations.proto";
+import "Proto/Issue045270/country.proto";
+
+package example.hello;
+
+option csharp_namespace = "Example.Hello";
+
+service HelloService {
+  rpc Say(SayRequest) returns (SayResponse) {
+    option (google.api.http) = {
+      post: "/say",
+      body: "*"
+    };
+  };
+}
+
+message SayRequest {
+  string name = 1;
+  country.Alpha3CountryCode country = 2;
+}
+
+message SayResponse {
+  string message = 1;
+  country.Alpha3CountryCode country = 2;
+}

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

@@ -15,6 +15,7 @@ using Grpc.Core;
 using Grpc.Shared;
 using Grpc.Shared.Server;
 using Grpc.Tests.Shared;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure;
@@ -341,7 +342,7 @@ public class ServerStreamingServerCallHandlerTests : LoggedTest
             new TestGrpcServiceActivator<JsonTranscodingGreeterService>());
 
         var jsonSettings = jsonTranscodingOptions?.JsonSettings ?? new GrpcJsonSettings() { WriteIndented = false };
-        var jsonContext = new JsonContext(jsonSettings, jsonTranscodingOptions?.TypeRegistry ?? TypeRegistry.Empty);
+        var jsonContext = new JsonContext(jsonSettings, jsonTranscodingOptions?.TypeRegistry ?? TypeRegistry.Empty, new DescriptorRegistry());
 
         return new ServerStreamingServerCallHandler<JsonTranscodingGreeterService, TRequest, TResponse>(
             callInvoker,

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

@@ -16,6 +16,7 @@ using Grpc.Core.Interceptors;
 using Grpc.Shared;
 using Grpc.Shared.Server;
 using Grpc.Tests.Shared;
+using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
 using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure;
@@ -1272,7 +1273,8 @@ public class UnaryServerCallHandlerTests : LoggedTest
 
         var jsonContext = new JsonContext(
             jsonTranscodingOptions?.JsonSettings ?? new GrpcJsonSettings(),
-            jsonTranscodingOptions?.TypeRegistry ?? TypeRegistry.Empty);
+            jsonTranscodingOptions?.TypeRegistry ?? TypeRegistry.Empty,
+            new DescriptorRegistry());
 
         return new UnaryServerCallHandler<JsonTranscodingGreeterService, TRequest, TResponse>(
             unaryServerCallInvoker,

+ 17 - 0
src/Grpc/JsonTranscoding/test/testassets/IntegrationTestsWebsite/Protos/greet.proto

@@ -25,6 +25,12 @@ service Greeter {
       get: "/v1/greeter/{name=from/*}"
     };
   }
+  rpc SayHelloPostEnum (EnumHelloRequest) returns (HelloReply) {
+    option (google.api.http) = {
+      post: "/v1/greeter_enum",
+      body: "*"
+    };
+  }
   rpc SayHelloComplexCatchAll1 (HelloRequest) returns (HelloReply) {
     option (google.api.http) = {
       get: "/{name=v1/greeter/**/b}/c/d/one"
@@ -55,6 +61,17 @@ message ComplextHelloRequest {
   }
 }
 
+message EnumHelloRequest {
+  NameOptions name = 1;
+}
+
+enum NameOptions {
+    JAMES = 0;
+    JOHN = 1;
+    JANE = 2;
+    JESS = 3;
+}
+
 // The response message containing the greetings.
 message HelloReply {
   string message = 1;