Example #1
0
    public void OnProvidersExecuting(ApiDescriptionProviderContext context)
    {
        var endpoints = _endpointDataSource.Endpoints;

        foreach (var endpoint in endpoints)
        {
            if (endpoint is RouteEndpoint routeEndpoint)
            {
                var grpcMetadata = endpoint.Metadata.GetMetadata <GrpcJsonTranscodingMetadata>();

                if (grpcMetadata != null)
                {
                    var httpRule         = grpcMetadata.HttpRule;
                    var methodDescriptor = grpcMetadata.MethodDescriptor;

                    if (ServiceDescriptorHelpers.TryResolvePattern(grpcMetadata.HttpRule, out var pattern, out var verb))
                    {
                        var apiDescription = CreateApiDescription(routeEndpoint, httpRule, methodDescriptor, pattern, verb);

                        context.Results.Add(apiDescription);
                    }
                }
            }
        }
    }
Example #2
0
        private async Task <IMessage> CreateMessage(HttpRequest request)
        {
            IMessage?requestMessage;

            if (_bodyDescriptor != null)
            {
                if (request.ContentType == null ||
                    !request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
                {
                    throw new InvalidOperationException("Request content-type of application/json is required.");
                }

                if (!request.Body.CanSeek)
                {
                    // JsonParser does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                    // read everything into a buffer, and then seek back to the beginning.
                    request.EnableBuffering();
                    Debug.Assert(request.Body.CanSeek);

                    await request.Body.DrainAsync(CancellationToken.None);

                    request.Body.Seek(0L, SeekOrigin.Begin);
                }

                var encoding = RequestEncoding.SelectCharacterEncoding(request);
                // TODO: Handle unsupported encoding

                using (var requestReader = new HttpRequestStreamReader(request.Body, encoding))
                {
                    if (_bodyDescriptorRepeated)
                    {
                        var containingMessage = ParseRepeatedContent(requestReader);

                        if (_resolvedBodyFieldDescriptors !.Count > 0)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, _resolvedBodyFieldDescriptors,
                                                                       containingMessage);
                        }
                        else
                        {
                            requestMessage = containingMessage;
                        }
                    }
                    else
                    {
                        var bodyContent = JsonParser.Default.Parse(requestReader, _bodyDescriptor);

                        if (_bodyFieldDescriptors != null)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, _bodyFieldDescriptors,
                                                                       bodyContent);
                        }
                        else
                        {
                            requestMessage = bodyContent;
                        }
                    }
                }
Example #3
0
    private static bool IsStandardMessage(Type type, [NotNullWhen(true)] out MessageDescriptor?messageDescriptor)
    {
        if (!typeof(IMessage).IsAssignableFrom(type))
        {
            messageDescriptor = null;
            return(false);
        }

        messageDescriptor = JsonConverterHelper.GetMessageDescriptor(type);
        if (messageDescriptor == null)
        {
            return(false);
        }

        // Wrappers and well known types are handled by converters.
        if (ServiceDescriptorHelpers.IsWrapperType(messageDescriptor))
        {
            return(false);
        }
        if (JsonConverterHelper.WellKnownTypeNames.ContainsKey(messageDescriptor.FullName))
        {
            return(false);
        }

        return(true);
    }
Example #4
0
    private static Type GetFieldTypeCore(FieldDescriptor descriptor)
    {
        switch (descriptor.FieldType)
        {
        case FieldType.Bool:
            return(typeof(bool));

        case FieldType.Bytes:
            return(typeof(ByteString));

        case FieldType.String:
            return(typeof(string));

        case FieldType.Double:
            return(typeof(double));

        case FieldType.SInt32:
        case FieldType.Int32:
        case FieldType.SFixed32:
            return(typeof(int));

        case FieldType.Enum:
            return(descriptor.EnumType.ClrType);

        case FieldType.Fixed32:
        case FieldType.UInt32:
            return(typeof(uint));

        case FieldType.Fixed64:
        case FieldType.UInt64:
            return(typeof(ulong));

        case FieldType.SFixed64:
        case FieldType.Int64:
        case FieldType.SInt64:
            return(typeof(long));

        case FieldType.Float:
            return(typeof(float));

        case FieldType.Message:
        case FieldType.Group:     // Never expect to get this, but...
            if (ServiceDescriptorHelpers.IsWrapperType(descriptor.MessageType))
            {
                var t = GetFieldType(descriptor.MessageType.Fields[WrapperValueFieldNumber]);
                if (t.IsValueType)
                {
                    return(typeof(Nullable <>).MakeGenericType(t));
                }

                return(t);
            }

            return(descriptor.MessageType.ClrType);

        default:
            throw new ArgumentException("Invalid field type");
        }
    }
Example #5
0
 private static List <FieldDescriptor>?GetPathDescriptors(HttpApiServerCallContext serverCallContext, IMessage requestMessage, string path)
 {
     return(serverCallContext.DescriptorInfo.PathDescriptorsCache.GetOrAdd(path, p =>
     {
         ServiceDescriptorHelpers.TryResolveDescriptors(requestMessage.Descriptor, p, out var pathDescriptors);
         return pathDescriptors;
     }));
 }
Example #6
0
 public override void AddMethod <TRequest, TResponse>(Method <TRequest, TResponse> method,
                                                      ServerStreamingServerMethod <TRequest, TResponse> handler)
 {
     if (TryGetMethodDescriptor(method.Name, out var methodDescriptor) &&
         ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out _))
     {
         Log.StreamingMethodNotSupported(_logger, method.Name, typeof(TService));
     }
 }
        public async Task HandleCallAsync_SubRepeatedBodySet_SetOnRequestMessage()
        {
            // Arrange
            HelloRequest?request = null;
            UnaryServerMethod <HttpApiGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
            {
                request = r;
                return(Task.FromResult(new HelloReply {
                    Message = $"Hello {r.Name}"
                }));
            };

            ServiceDescriptorHelpers.TryResolveDescriptors(HelloRequest.Descriptor, "repeated_strings", out var bodyFieldDescriptors);

            var descriptorInfo = TestHelpers.CreateDescriptorInfo(
                bodyDescriptor: HelloRequest.Types.SubMessage.Descriptor,
                bodyDescriptorRepeated: true,
                bodyFieldDescriptors: bodyFieldDescriptors);
            var unaryServerCallHandler = CreateCallHandler(
                invoker,
                descriptorInfo);
            var httpContext = TestHelpers.CreateHttpContext();

            var sdf = new RepeatedField <string>
            {
                "One",
                "Two",
                "Three"
            };

            var sw = new StringWriter();

            JsonFormatter.Default.WriteValue(sw, sdf);

            httpContext.Request.Body  = new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
            httpContext.Request.Query = new QueryCollection(new Dictionary <string, StringValues>
            {
                ["name"]          = "QueryStringTestName!",
                ["sub.subfield"]  = "QueryStringTestSubfield!",
                ["sub.subfields"] = "QueryStringTestSubfields!"
            });
            httpContext.Request.ContentType = "application/json";

            // Act
            await unaryServerCallHandler.HandleCallAsync(httpContext);

            // Assert
            Assert.NotNull(request);
            Assert.Equal("QueryStringTestName!", request !.Name);
            Assert.Equal("QueryStringTestSubfield!", request !.Sub.Subfield);
            Assert.Equal(3, request !.RepeatedStrings.Count);
            Assert.Equal("One", request !.RepeatedStrings[0]);
            Assert.Equal("Two", request !.RepeatedStrings[1]);
            Assert.Equal("Three", request !.RepeatedStrings[2]);
        }
        private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, HttpRule httpRule, MethodDescriptor methodDescriptor, string pattern, string verb)
        {
            var apiDescription = new ApiDescription();

            apiDescription.HttpMethod       = verb;
            apiDescription.ActionDescriptor = new ActionDescriptor
            {
                RouteValues = new Dictionary <string, string>
                {
                    // Swagger uses this to group endpoints together.
                    // Group methods together using the service name.
                    ["controller"] = methodDescriptor.Service.FullName
                }
            };
            apiDescription.RelativePath = pattern.TrimStart('/');
            apiDescription.SupportedRequestFormats.Add(new ApiRequestFormat {
                MediaType = "application/json"
            });
            apiDescription.SupportedResponseTypes.Add(new ApiResponseType
            {
                ApiResponseFormats = { new ApiResponseFormat {
                                           MediaType = "application/json"
                                       } },
                ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(methodDescriptor.OutputType.ClrType)),
                StatusCode    = 200
            });

            var routeParameters = ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(routeEndpoint.RoutePattern, methodDescriptor.InputType);

            foreach (var routeParameter in routeParameters)
            {
                var field = routeParameter.Value.Last();

                apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
                {
                    Name          = routeParameter.Key,
                    ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(MessageDescriptorHelpers.ResolveFieldType(field))),
                    Source        = BindingSource.Path,
                    DefaultValue  = string.Empty
                });
            }

            ServiceDescriptorHelpers.ResolveBodyDescriptor(httpRule.Body, methodDescriptor, out var bodyDescriptor, out _, out _);
            if (bodyDescriptor != null)
            {
                apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
                {
                    Name          = "Input",
                    ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(bodyDescriptor.ClrType)),
                    Source        = BindingSource.Body
                });
            }

            return(apiDescription);
        }
Example #9
0
 public override void AddMethod <TRequest, TResponse>(Method <TRequest, TResponse> method,
                                                      UnaryServerMethod <TRequest, TResponse> handler)
 {
     if (TryGetMethodDescriptor(method.Name, out var methodDescriptor))
     {
         if (ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out var httpRule))
         {
             ProcessHttpRule(method, methodDescriptor, httpRule);
         }
     }
     else
     {
         Log.MethodDescriptorNotFound(_logger, method.Name, typeof(TService));
     }
 }
Example #10
0
        private void ProcessHttpRule <TRequest, TResponse>(Method <TRequest, TResponse> method,
                                                           MethodDescriptor methodDescriptor, HttpRule httpRule)
            where TRequest : class
            where TResponse : class
        {
            if (ServiceDescriptorHelpers.TryResolvePattern(httpRule, out var pattern, out var httpVerb))
            {
                AddMethodCore(method, httpRule, pattern, httpVerb, httpRule.Body, httpRule.ResponseBody,
                              methodDescriptor);
            }

            foreach (var additionalRule in httpRule.AdditionalBindings)
            {
                ProcessHttpRule(method, methodDescriptor, additionalRule);
            }
        }
Example #11
0
    public override bool CanConvert(Type typeToConvert)
    {
        if (!typeof(IMessage).IsAssignableFrom(typeToConvert))
        {
            return(false);
        }

        var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert);

        if (descriptor == null)
        {
            return(false);
        }

        return(ServiceDescriptorHelpers.IsWrapperType(descriptor));
    }
        public async Task HandleCallAsync_SubBodySet_SetOnRequestMessage()
        {
            // Arrange
            HelloRequest?request = null;
            UnaryServerMethod <HttpApiGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
            {
                request = r;
                return(Task.FromResult(new HelloReply {
                    Message = $"Hello {r.Name}"
                }));
            };

            ServiceDescriptorHelpers.TryResolveDescriptors(HelloRequest.Descriptor, "sub", out var bodyFieldDescriptors);

            var unaryServerCallHandler = CreateCallHandler(
                invoker,
                bodyDescriptor: HelloRequest.Types.SubMessage.Descriptor,
                bodyFieldDescriptors: bodyFieldDescriptors);
            var httpContext = CreateHttpContext();

            httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(JsonFormatter.Default.Format(new HelloRequest.Types.SubMessage
            {
                Subfield = "Subfield!"
            })));
            httpContext.Request.Query = new QueryCollection(new Dictionary <string, StringValues>
            {
                ["name"]          = "QueryStringTestName!",
                ["sub.subfield"]  = "QueryStringTestSubfield!",
                ["sub.subfields"] = "QueryStringTestSubfields!"
            });
            httpContext.Request.ContentType = "application/json";

            // Act
            await unaryServerCallHandler.HandleCallAsync(httpContext);

            // Assert
            Assert.IsNotNull(request);
            Assert.AreEqual("QueryStringTestName!", request !.Name);
            Assert.AreEqual("Subfield!", request !.Sub.Subfield);
            Assert.AreEqual(0, request !.Sub.Subfields.Count);
        }
Example #13
0
    public override TMessage?Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var d = JsonDocument.ParseValue(ref reader);
        if (!d.RootElement.TryGetProperty(AnyTypeUrlField, out var urlField))
        {
            throw new InvalidOperationException("Any message with no @type.");
        }

        var typeUrl  = urlField.GetString();
        var typeName = Any.GetTypeName(typeUrl);

        var descriptor = Context.TypeRegistry.Find(typeName);

        if (descriptor == null)
        {
            throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'.");
        }

        IMessage data;

        if (ServiceDescriptorHelpers.IsWellKnownType(descriptor))
        {
            if (!d.RootElement.TryGetProperty(AnyWellKnownTypeValueField, out var valueField))
            {
                throw new InvalidOperationException($"Expected '{AnyWellKnownTypeValueField}' property for well-known type Any body.");
            }

            data = (IMessage)JsonSerializer.Deserialize(valueField, descriptor.ClrType, options) !;
        }
        else
        {
            data = (IMessage)JsonSerializer.Deserialize(d.RootElement, descriptor.ClrType, options) !;
        }

        var message = new TMessage();

        message.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.SetValue(message, typeUrl);
        message.Descriptor.Fields[Any.ValueFieldNumber].Accessor.SetValue(message, data.ToByteString());

        return(message);
    }
Example #14
0
    public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
    {
        var typeUrl    = (string)value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
        var data       = (ByteString)value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
        var typeName   = Any.GetTypeName(typeUrl);
        var descriptor = Context.TypeRegistry.Find(typeName);

        if (descriptor == null)
        {
            throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'.");
        }
        var valueMessage = descriptor.Parser.ParseFrom(data);

        writer.WriteStartObject();
        writer.WriteString(AnyTypeUrlField, typeUrl);

        if (ServiceDescriptorHelpers.IsWellKnownType(descriptor))
        {
            writer.WritePropertyName(AnyWellKnownTypeValueField);
            if (ServiceDescriptorHelpers.IsWrapperType(descriptor))
            {
                var wrappedValue = valueMessage.Descriptor.Fields[JsonConverterHelper.WrapperValueFieldNumber].Accessor.GetValue(valueMessage);
                JsonSerializer.Serialize(writer, wrappedValue, wrappedValue.GetType(), options);
            }
            else
            {
                JsonSerializer.Serialize(writer, valueMessage, valueMessage.GetType(), options);
            }
        }
        else
        {
            MessageConverter <Any> .WriteMessageFields(writer, valueMessage, Context.Settings, options);
        }

        writer.WriteEndObject();
    }
Example #15
0
        private void AddMethodCore <TRequest, TResponse>(
            Method <TRequest, TResponse> method,
            HttpRule httpRule,
            string pattern,
            string httpVerb,
            string body,
            string responseBody,
            MethodDescriptor methodDescriptor)
            where TRequest : class
            where TResponse : class
        {
            try
            {
                if (!pattern.StartsWith('/'))
                {
                    // This validation is consistent with grpc-gateway code generation.
                    // We should match their validation to be a good member of the eco-system.
                    throw new InvalidOperationException($"Path template must start with /: {pattern}");
                }

                var(invoker, metadata) = CreateModelCore <UnaryServerMethod <TService, TRequest, TResponse> >(
                    method.Name,
                    new[] { typeof(TRequest), typeof(ServerCallContext) },
                    httpVerb,
                    httpRule,
                    methodDescriptor);

                var methodContext = MethodOptions.Create(new[] { _globalOptions, _serviceOptions });

                var routePattern = RoutePatternFactory.Parse(pattern);
                var routeParameterDescriptors =
                    ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(routePattern, methodDescriptor.InputType);

                ServiceDescriptorHelpers.ResolveBodyDescriptor(body, methodDescriptor, out var bodyDescriptor,
                                                               out var bodyFieldDescriptors, out var bodyDescriptorRepeated);

                FieldDescriptor?responseBodyDescriptor = null;
                if (!string.IsNullOrEmpty(responseBody))
                {
                    responseBodyDescriptor = methodDescriptor.OutputType.FindFieldByName(responseBody);
                    if (responseBodyDescriptor == null)
                    {
                        throw new InvalidOperationException(
                                  $"Couldn't find matching field for response body '{responseBody}' on {methodDescriptor.OutputType.Name}.");
                    }
                }

                var unaryInvoker =
                    new UnaryServerMethodInvoker <TService, TRequest, TResponse>(invoker, method, methodContext,
                                                                                 _serviceActivator);
                var unaryServerCallHandler = new UnaryServerCallHandler <TService, TRequest, TResponse>(
                    unaryInvoker,
                    responseBodyDescriptor,
                    bodyDescriptor,
                    bodyDescriptorRepeated,
                    bodyFieldDescriptors,
                    routeParameterDescriptors);

                _context.AddMethod(method, routePattern, metadata, unaryServerCallHandler.HandleCallAsync);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException(
                          $"Error binding {method.Name} on {typeof(TService).Name} to HTTP API.", ex);
            }
        }
    private DataContract ConvertMessage(MessageDescriptor messageDescriptor)
    {
        if (ServiceDescriptorHelpers.IsWellKnownType(messageDescriptor))
        {
            if (ServiceDescriptorHelpers.IsWrapperType(messageDescriptor))
            {
                var field = messageDescriptor.Fields[Int32Value.ValueFieldNumber];

                return(_innerContractResolver.GetDataContractForType(MessageDescriptorHelpers.ResolveFieldType(field)));
            }
            if (messageDescriptor.FullName == Timestamp.Descriptor.FullName ||
                messageDescriptor.FullName == Duration.Descriptor.FullName ||
                messageDescriptor.FullName == FieldMask.Descriptor.FullName)
            {
                return(DataContract.ForPrimitive(messageDescriptor.ClrType, DataType.String, dataFormat: null));
            }
            if (messageDescriptor.FullName == Struct.Descriptor.FullName)
            {
                return(DataContract.ForObject(messageDescriptor.ClrType, Array.Empty <DataProperty>(), extensionDataType: typeof(Value)));
            }
            if (messageDescriptor.FullName == ListValue.Descriptor.FullName)
            {
                return(DataContract.ForArray(messageDescriptor.ClrType, typeof(Value)));
            }
            if (messageDescriptor.FullName == Value.Descriptor.FullName)
            {
                return(DataContract.ForPrimitive(messageDescriptor.ClrType, DataType.Unknown, dataFormat: null));
            }
            if (messageDescriptor.FullName == Any.Descriptor.FullName)
            {
                var anyProperties = new List <DataProperty>
                {
                    new DataProperty("@type", typeof(string), isRequired: true)
                };
                return(DataContract.ForObject(messageDescriptor.ClrType, anyProperties, extensionDataType: typeof(Value)));
            }
        }

        var properties = new List <DataProperty>();

        foreach (var field in messageDescriptor.Fields.InFieldNumberOrder())
        {
            // Enum type will later be used to call this contract resolver.
            // Register the enum type so we know to resolve its names from the descriptor.
            if (field.FieldType == FieldType.Enum)
            {
                _enumTypeMapping.TryAdd(field.EnumType.ClrType, field.EnumType);
            }

            Type fieldType;
            if (field.IsMap)
            {
                var mapFields = field.MessageType.Fields.InFieldNumberOrder();
                var valueType = MessageDescriptorHelpers.ResolveFieldType(mapFields[1]);
                fieldType = typeof(IDictionary <,>).MakeGenericType(typeof(string), valueType);
            }
            else if (field.IsRepeated)
            {
                fieldType = typeof(IList <>).MakeGenericType(MessageDescriptorHelpers.ResolveFieldType(field));
            }
            else
            {
                fieldType = MessageDescriptorHelpers.ResolveFieldType(field);
            }

            var propertyName = ServiceDescriptorHelpers.FormatUnderscoreName(field.Name, pascalCase: true, preservePeriod: false);
            var propertyInfo = messageDescriptor.ClrType.GetProperty(propertyName);

            properties.Add(new DataProperty(field.JsonName, fieldType, memberInfo: propertyInfo));
        }

        var schema = DataContract.ForObject(messageDescriptor.ClrType, properties: properties);

        return(schema);
    }
Example #17
0
        public static async Task <TRequest> ReadMessage <TRequest>(HttpApiServerCallContext serverCallContext, JsonSerializerOptions serializerOptions) where TRequest : class
        {
            try
            {
                GrpcServerLog.ReadingMessage(serverCallContext.Logger);

                IMessage requestMessage;
                if (serverCallContext.DescriptorInfo.BodyDescriptor != null)
                {
                    if (!serverCallContext.IsJsonRequestContent)
                    {
                        GrpcServerLog.UnsupportedRequestContentType(serverCallContext.Logger, serverCallContext.HttpContext.Request.ContentType);
                        throw new RpcException(new Status(StatusCode.InvalidArgument, "Request content-type of application/json is required."));
                    }

                    var(stream, usesTranscodingStream) = GetStream(serverCallContext.HttpContext.Request.Body, serverCallContext.RequestEncoding);

                    try
                    {
                        if (serverCallContext.DescriptorInfo.BodyDescriptorRepeated)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();

                            // TODO: JsonSerializer currently doesn't support deserializing values onto an existing object or collection.
                            // Either update this to use new functionality in JsonSerializer or improve work-around perf.
                            var type     = JsonConverterHelper.GetFieldType(serverCallContext.DescriptorInfo.BodyFieldDescriptors !.Last());
                            var listType = typeof(List <>).MakeGenericType(type);

                            GrpcServerLog.DeserializingMessage(serverCallContext.Logger, listType);
                            var repeatedContent = (IList)(await JsonSerializer.DeserializeAsync(stream, listType, serializerOptions)) !;

                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, serverCallContext.DescriptorInfo.BodyFieldDescriptors !, repeatedContent);
                        }
                        else
                        {
                            IMessage bodyContent;

                            try
                            {
                                GrpcServerLog.DeserializingMessage(serverCallContext.Logger, serverCallContext.DescriptorInfo.BodyDescriptor.ClrType);
                                bodyContent = (IMessage)(await JsonSerializer.DeserializeAsync(stream, serverCallContext.DescriptorInfo.BodyDescriptor.ClrType, serializerOptions)) !;
                            }
                            catch (JsonException)
                            {
                                throw new RpcException(new Status(StatusCode.InvalidArgument, "Request JSON payload is not correctly formatted."));
                            }
                            catch (Exception exception)
                            {
                                throw new RpcException(new Status(StatusCode.InvalidArgument, exception.Message));
                            }

                            if (serverCallContext.DescriptorInfo.BodyFieldDescriptors != null)
                            {
                                requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                                ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, serverCallContext.DescriptorInfo.BodyFieldDescriptors, bodyContent !); // TODO - check nullability
                            }
                            else
                            {
                                requestMessage = bodyContent;
                            }
                        }
                    }
                    finally
                    {
                        if (usesTranscodingStream)
                        {
                            await stream.DisposeAsync();
                        }
                    }
                }
                else
                {
                    requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                }

                foreach (var parameterDescriptor in serverCallContext.DescriptorInfo.RouteParameterDescriptors)
                {
                    var routeValue = serverCallContext.HttpContext.Request.RouteValues[parameterDescriptor.Key];
                    if (routeValue != null)
                    {
                        ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, parameterDescriptor.Value, routeValue);
                    }
                }

                foreach (var item in serverCallContext.HttpContext.Request.Query)
                {
                    if (CanBindQueryStringVariable(serverCallContext, item.Key))
                    {
                        var pathDescriptors = GetPathDescriptors(serverCallContext, requestMessage, item.Key);

                        if (pathDescriptors != null)
                        {
                            object value = item.Value.Count == 1 ? (object)item.Value[0] : item.Value;
                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, pathDescriptors, value);
                        }
                    }
                }

                GrpcServerLog.ReceivedMessage(serverCallContext.Logger);
                return((TRequest)requestMessage);
            }
            catch (Exception ex)
            {
                GrpcServerLog.ErrorReadingMessage(serverCallContext.Logger, ex);
                throw;
            }
        }
Example #18
0
    private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, HttpRule httpRule, MethodDescriptor methodDescriptor, string pattern, string verb)
    {
        var apiDescription = new ApiDescription();

        apiDescription.HttpMethod       = verb;
        apiDescription.ActionDescriptor = new ActionDescriptor
        {
            RouteValues = new Dictionary <string, string?>
            {
                // Swagger uses this to group endpoints together.
                // Group methods together using the service name.
                ["controller"] = methodDescriptor.Service.FullName
            },
            EndpointMetadata = routeEndpoint.Metadata.ToList()
        };
        apiDescription.RelativePath = pattern.TrimStart('/');
        apiDescription.SupportedRequestFormats.Add(new ApiRequestFormat {
            MediaType = "application/json"
        });
        apiDescription.SupportedResponseTypes.Add(new ApiResponseType
        {
            ApiResponseFormats = { new ApiResponseFormat {
                                       MediaType = "application/json"
                                   } },
            ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(methodDescriptor.OutputType.ClrType)),
            StatusCode    = 200
        });
        var explorerSettings = routeEndpoint.Metadata.GetMetadata <ApiExplorerSettingsAttribute>();

        if (explorerSettings != null)
        {
            apiDescription.GroupName = explorerSettings.GroupName;
        }

        var methodMetadata  = routeEndpoint.Metadata.GetMetadata <GrpcMethodMetadata>() !;
        var routeParameters = ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(routeEndpoint.RoutePattern, methodDescriptor.InputType);

        foreach (var routeParameter in routeParameters)
        {
            var field         = routeParameter.Value.Last();
            var parameterName = ServiceDescriptorHelpers.FormatUnderscoreName(field.Name, pascalCase: true, preservePeriod: false);
            var propertyInfo  = field.ContainingType.ClrType.GetProperty(parameterName);

            // 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));

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

        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.Descriptor.ClrType, bodyDescriptor.PropertyInfo.DeclaringType !)
                : ModelMetadataIdentity.ForType(bodyDescriptor.Descriptor.ClrType);

            // 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;

            apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
            {
                Name                = "Input",
                ModelMetadata       = new GrpcModelMetadata(identity),
                Source              = BindingSource.Body,
                ParameterDescriptor = parameterDescriptor !
            });
Example #19
0
    public static async ValueTask <TRequest> ReadMessage <TRequest>(JsonTranscodingServerCallContext serverCallContext, JsonSerializerOptions serializerOptions) where TRequest : class
    {
        try
        {
            GrpcServerLog.ReadingMessage(serverCallContext.Logger);

            IMessage requestMessage;
            if (serverCallContext.DescriptorInfo.BodyDescriptor != null)
            {
                Type   type;
                object bodyContent;

                if (serverCallContext.DescriptorInfo.BodyDescriptor.FullName == HttpBody.Descriptor.FullName)
                {
                    type = typeof(HttpBody);

                    bodyContent = await ReadHttpBodyAsync(serverCallContext);
                }
                else
                {
                    if (!serverCallContext.IsJsonRequestContent)
                    {
                        GrpcServerLog.UnsupportedRequestContentType(serverCallContext.Logger, serverCallContext.HttpContext.Request.ContentType);
                        throw new InvalidOperationException($"Unable to read the request as JSON because the request content type '{serverCallContext.HttpContext.Request.ContentType}' is not a known JSON content type.");
                    }

                    var(stream, usesTranscodingStream) = GetStream(serverCallContext.HttpContext.Request.Body, serverCallContext.RequestEncoding);

                    try
                    {
                        if (serverCallContext.DescriptorInfo.BodyDescriptorRepeated)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();

                            // TODO: JsonSerializer currently doesn't support deserializing values onto an existing object or collection.
                            // Either update this to use new functionality in JsonSerializer or improve work-around perf.
                            type = JsonConverterHelper.GetFieldType(serverCallContext.DescriptorInfo.BodyFieldDescriptors.Last());
                            type = type.GetGenericArguments()[0];
                            type = typeof(List <>).MakeGenericType(type);

                            GrpcServerLog.DeserializingMessage(serverCallContext.Logger, type);

                            bodyContent = (await JsonSerializer.DeserializeAsync(stream, type, serializerOptions)) !;

                            if (bodyContent == null)
                            {
                                throw new InvalidOperationException($"Unable to deserialize null to {type.Name}.");
                            }
                        }
                        else
                        {
                            type = serverCallContext.DescriptorInfo.BodyDescriptor.ClrType;

                            GrpcServerLog.DeserializingMessage(serverCallContext.Logger, type);
                            bodyContent = (IMessage)(await JsonSerializer.DeserializeAsync(stream, serverCallContext.DescriptorInfo.BodyDescriptor.ClrType, serializerOptions)) !;
                        }
                    }
                    finally
                    {
                        if (usesTranscodingStream)
                        {
                            await stream.DisposeAsync();
                        }
                    }
                }

                if (serverCallContext.DescriptorInfo.BodyFieldDescriptors != null)
                {
                    requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                    ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, serverCallContext.DescriptorInfo.BodyFieldDescriptors, bodyContent); // TODO - check nullability
                }
                else
                {
                    if (bodyContent == null)
                    {
                        throw new InvalidOperationException($"Unable to deserialize null to {type.Name}.");
                    }

                    requestMessage = (IMessage)bodyContent;
                }
            }
            else
            {
                requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
            }

            foreach (var parameterDescriptor in serverCallContext.DescriptorInfo.RouteParameterDescriptors)
            {
                var routeValue = serverCallContext.HttpContext.Request.RouteValues[parameterDescriptor.Key];
                if (routeValue != null)
                {
                    ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, parameterDescriptor.Value, routeValue);
                }
            }

            foreach (var item in serverCallContext.HttpContext.Request.Query)
            {
                if (CanBindQueryStringVariable(serverCallContext, item.Key))
                {
                    var pathDescriptors = GetPathDescriptors(serverCallContext, requestMessage, item.Key);

                    if (pathDescriptors != null)
                    {
                        var value = item.Value.Count == 1 ? (object?)item.Value[0] : item.Value;
                        ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, pathDescriptors, value);
                    }
                }
            }

            GrpcServerLog.ReceivedMessage(serverCallContext.Logger);
            return((TRequest)requestMessage);
        }
        catch (JsonException ex)
        {
            GrpcServerLog.ErrorReadingMessage(serverCallContext.Logger, ex);
            throw new RpcException(new Status(StatusCode.InvalidArgument, "Request JSON payload is not correctly formatted.", ex));
        }
        catch (Exception ex)
        {
            GrpcServerLog.ErrorReadingMessage(serverCallContext.Logger, ex);
            throw new RpcException(new Status(StatusCode.InvalidArgument, ex.Message, ex));
        }
    }