Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gRPC JSON transcoding + Microsoft.AspNetCore.OpenApi #56067

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Grpc/Grpc.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,28 @@
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
"src\\Hosting\\TestHost\\src\\Microsoft.AspNetCore.TestHost.csproj",
"src\\Http\\Authentication.Abstractions\\src\\Microsoft.AspNetCore.Authentication.Abstractions.csproj",
"src\\Http\\Authentication.Core\\src\\Microsoft.AspNetCore.Authentication.Core.csproj",
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
"src\\Http\\Http.Extensions\\gen\\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj",
"src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
"src\\Http\\Http.Results\\src\\Microsoft.AspNetCore.Http.Results.csproj",
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
"src\\Http\\Metadata\\src\\Microsoft.AspNetCore.Metadata.csproj",
"src\\Http\\Routing.Abstractions\\src\\Microsoft.AspNetCore.Routing.Abstractions.csproj",
"src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj",
"src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
"src\\Middleware\\OutputCaching\\src\\Microsoft.AspNetCore.OutputCaching.csproj",
"src\\Middleware\\ResponseCaching.Abstractions\\src\\Microsoft.AspNetCore.ResponseCaching.Abstractions.csproj",
"src\\Mvc\\Mvc.Abstractions\\src\\Microsoft.AspNetCore.Mvc.Abstractions.csproj",
"src\\Mvc\\Mvc.ApiExplorer\\src\\Microsoft.AspNetCore.Mvc.ApiExplorer.csproj",
"src\\Mvc\\Mvc.Core\\src\\Microsoft.AspNetCore.Mvc.Core.csproj",
"src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj",
"src\\OpenApi\\src\\Microsoft.AspNetCore.OpenApi.csproj",
"src\\Security\\Authorization\\Core\\src\\Microsoft.AspNetCore.Authorization.csproj",
"src\\Security\\Authorization\\Policy\\src\\Microsoft.AspNetCore.Authorization.Policy.csproj",
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
"src\\Testing\\src\\Microsoft.AspNetCore.InternalTesting.csproj"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Grpc.Shared;
using Microsoft.AspNetCore.Grpc.JsonTranscoding;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

Expand All @@ -27,6 +29,7 @@ public static IGrpcServerBuilder AddJsonTranscoding(this IGrpcServerBuilder buil

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IServiceMethodProvider<>), typeof(JsonTranscodingServiceMethodProvider<>)));
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<GrpcJsonTranscodingOptions>, GrpcJsonTranscodingOptionsSetup>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<JsonOptions>, JsonOptionsSetup>());
builder.Services.TryAddSingleton<DescriptorRegistry>();

return builder;
Expand Down Expand Up @@ -63,4 +66,25 @@ public void Configure(GrpcJsonTranscodingOptions options)
options.DescriptorRegistry = _descriptorRegistry;
}
}

private sealed class JsonOptionsSetup : IConfigureOptions<JsonOptions>
{
private readonly DescriptorRegistry _descriptorRegistry;
private readonly GrpcJsonTranscodingOptions _transcodingOptions;

public JsonOptionsSetup(DescriptorRegistry descriptorRegistry, IOptions<GrpcJsonTranscodingOptions> transcodingOptions)
{
_descriptorRegistry = descriptorRegistry;
_transcodingOptions = transcodingOptions.Value;
}

public void Configure(JsonOptions options)
{
ArgumentNullException.ThrowIfNull(options);

var context = new JsonContext(_transcodingOptions.JsonSettings, _transcodingOptions.TypeRegistry, _descriptorRegistry);

JsonConverterHelper.ApplyConverterAndTypeInfoSerializerOptions(options.SerializerOptions, context);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,26 @@ internal static JsonSerializerOptions CreateSerializerOptions(JsonContext contex
// For streaming to work, indenting must be disabled when streaming.
var writeIndented = !isStreamingOptions ? context.Settings.WriteIndented : false;

var typeInfoResolver = JsonTypeInfoResolver.Combine(
new MessageTypeInfoResolver(context),
new DefaultJsonTypeInfoResolver());

var options = new JsonSerializerOptions
{
WriteIndented = writeIndented,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = typeInfoResolver
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

ApplyConverterAndTypeInfoSerializerOptions(options, context);

return options;
}

internal static void ApplyConverterAndTypeInfoSerializerOptions(JsonSerializerOptions options, JsonContext context)
{
var typeInfoResolver = JsonTypeInfoResolver.Combine(
new MessageTypeInfoResolver(context),
options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver());

options.TypeInfoResolver = typeInfoResolver;

options.Converters.Add(new NullValueConverter());
options.Converters.Add(new ByteStringConverter());
options.Converters.Add(new Int64Converter(context));
Expand All @@ -56,8 +65,6 @@ internal static JsonSerializerOptions CreateSerializerOptions(JsonContext contex
options.Converters.Add(new JsonConverterFactoryForEnum(context));
options.Converters.Add(new JsonConverterFactoryForWrappers(context));
options.Converters.Add(new JsonConverterFactoryForWellKnownTypes(context));

return options;
}

internal static Type GetFieldType(FieldDescriptor descriptor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint,
{
ApiResponseFormats = { new ApiResponseFormat { MediaType = "application/json" } },
ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(responseType)),
StatusCode = 200
StatusCode = 200,
Type = responseType
});
apiDescription.SupportedResponseTypes.Add(new ApiResponseType
{
ApiResponseFormats = { new ApiResponseFormat { MediaType = "application/json" } },
ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(typeof(Google.Rpc.Status))),
IsDefaultResponse = true
IsDefaultResponse = true,
Type = typeof(Google.Rpc.Status)
});
var explorerSettings = routeEndpoint.Metadata.GetMetadata<ApiExplorerSettingsAttribute>();
if (explorerSettings != null)
Expand All @@ -106,40 +108,56 @@ private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint,
var field = routeParameter.Value.DescriptorsPath.Last();
var parameterName = ServiceDescriptorHelpers.FormatUnderscoreName(field.Name, pascalCase: true, preservePeriod: false);
var propertyInfo = field.ContainingType.ClrType.GetProperty(parameterName);
var fieldType = MessageDescriptorHelpers.ResolveFieldType(field);

// If from a property, create model as property to get its XML comments.
var identity = propertyInfo != null
? ModelMetadataIdentity.ForProperty(propertyInfo, MessageDescriptorHelpers.ResolveFieldType(field), field.ContainingType.ClrType)
: ModelMetadataIdentity.ForType(MessageDescriptorHelpers.ResolveFieldType(field));
? ModelMetadataIdentity.ForProperty(propertyInfo, fieldType, field.ContainingType.ClrType)
: ModelMetadataIdentity.ForType(fieldType);

apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
{
Name = routeParameter.Value.JsonPath,
ModelMetadata = new GrpcModelMetadata(identity),
Source = BindingSource.Path,
DefaultValue = string.Empty
DefaultValue = string.Empty,
Type = fieldType
});
}

var bodyDescriptor = ServiceDescriptorHelpers.ResolveBodyDescriptor(httpRule.Body, methodMetadata.ServiceType, methodDescriptor);
if (bodyDescriptor != null)
{
// If from a property, create model as property to get its XML comments.
var identity = bodyDescriptor.PropertyInfo != null
? ModelMetadataIdentity.ForProperty(bodyDescriptor.PropertyInfo, bodyDescriptor.PropertyInfo.PropertyType, bodyDescriptor.PropertyInfo.DeclaringType!)
: ModelMetadataIdentity.ForType(bodyDescriptor.Descriptor.ClrType);
ModelMetadataIdentity identity;
Type type;
ControllerParameterDescriptor? parameterDescriptor = null;

// Or if from a parameter, create model as parameter to get its XML comments.
var parameterDescriptor = bodyDescriptor.ParameterInfo != null
? new ControllerParameterDescriptor { ParameterInfo = bodyDescriptor.ParameterInfo }
: null;
if (bodyDescriptor.PropertyInfo != null)
{
// If from a property, create model as property to get its XML comments.
identity = ModelMetadataIdentity.ForProperty(bodyDescriptor.PropertyInfo, bodyDescriptor.PropertyInfo.PropertyType, bodyDescriptor.PropertyInfo.DeclaringType!);
type = bodyDescriptor.PropertyInfo.PropertyType;
}
else if (bodyDescriptor.ParameterInfo != null)
{
// Or if from a parameter, create model as parameter to get its XML comments.
identity = ModelMetadataIdentity.ForType(bodyDescriptor.Descriptor.ClrType);
type = bodyDescriptor.Descriptor.ClrType;
parameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = bodyDescriptor.ParameterInfo };
}
else
{
identity = ModelMetadataIdentity.ForType(bodyDescriptor.Descriptor.ClrType);
type = bodyDescriptor.Descriptor.ClrType;
}

apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
{
Name = "Input",
ModelMetadata = new GrpcModelMetadata(identity),
Source = BindingSource.Body,
ParameterDescriptor = parameterDescriptor!
ParameterDescriptor = parameterDescriptor!,
Type = type
});
}

Expand All @@ -148,18 +166,20 @@ private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint,
{
var field = queryDescription.Value;
var propertyInfo = field.ContainingType.ClrType.GetProperty(field.PropertyName);
var fieldType = MessageDescriptorHelpers.ResolveFieldType(field);

// If from a property, create model as property to get its XML comments.
var identity = propertyInfo != null
? ModelMetadataIdentity.ForProperty(propertyInfo, MessageDescriptorHelpers.ResolveFieldType(field), field.ContainingType.ClrType)
: ModelMetadataIdentity.ForType(MessageDescriptorHelpers.ResolveFieldType(field));
? ModelMetadataIdentity.ForProperty(propertyInfo, fieldType, field.ContainingType.ClrType)
: ModelMetadataIdentity.ForType(fieldType);

apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
{
Name = queryDescription.Key,
ModelMetadata = new GrpcModelMetadata(identity),
Source = BindingSource.Query,
DefaultValue = string.Empty
DefaultValue = string.Empty,
Type = fieldType
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand All @@ -10,5 +12,6 @@
<Reference Include="Grpc.AspNetCore" />
<Reference Include="Microsoft.AspNetCore.Grpc.JsonTranscoding" />
<Reference Include="Microsoft.AspNetCore.Grpc.Swagger" />
<Reference Include="Microsoft.AspNetCore.OpenApi" />
</ItemGroup>
</Project>
15 changes: 13 additions & 2 deletions src/Grpc/JsonTranscoding/test/testassets/Sandbox/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Options;
using System.Reflection;
using Microsoft.OpenApi.Models;

namespace Server;
Expand All @@ -10,12 +12,19 @@ public class Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc().AddJsonTranscoding();
services.AddMvc();
//services.AddMvc();

services.AddOpenApi();

#region Secret
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });

var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
xmlFilename = Path.Combine(AppContext.BaseDirectory, xmlFilename);
c.IncludeXmlComments(xmlFilename);
c.IncludeGrpcXmlComments(xmlFilename, includeControllerXmlComments: true);
});
services.AddGrpcSwagger();
#endregion
Expand All @@ -35,6 +44,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.SwaggerEndpoint("/openapi/v1.json", "OpenApi");
});
}
#endregion
Expand All @@ -43,7 +53,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<JsonTranscodingGreeterService>();
endpoints.MapOpenApi();
//endpoints.MapGrpcService<JsonTranscodingGreeterService>();
endpoints.MapGrpcService<GreeterService>();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import "google/api/annotations.proto";

package greet;

// The greeting service definition.
service Greeter {
// Sends a greeting.
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/greeter/{name}"
};
}
// Sends a greeting from someone.
rpc SayHelloFrom (HelloRequestFrom) returns (HelloReply) {
option (google.api.http) = {
post: "/v1/greeter"
Expand All @@ -22,15 +25,19 @@ service Greeter {
}

message HelloRequest {
// Name to greet.
string name = 1;
}

message HelloRequestFrom {
// Name to greet.
string name = 1;
// Greeting from.
string from = 2;
}

message HelloReply {
// Greeting message.
string message = 1;
HelloReply nested = 2;
}
Loading