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); } } } } }
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; } } }
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); }
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"); } }
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; })); }
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); }
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)); } }
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); } }
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); }
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); }
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(); }
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); }
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; } }
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 ! });
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)); } }