ObjectContent не работает, но StringContent работает - PullRequest
0 голосов
/ 13 ноября 2018

Я пытаюсь написать перегрузку для HttpClient.PutAsJsonAsync<T>, которая позволяет устанавливать заголовки при каждом запросе.Глядя на исходный код этого метода, он создает внутреннее ObjectContent<T>, которое я попытался эмулировать ниже.

Однако мой целевой сервер всегда получает нулевое тело при связывании (используя [FromBody] Form form).

Если я изменю запрос на StringContent и вручную сериализую тело, запрос будет выполнен успешно!В чем может быть разница между этими двумя реализациями?

Еще более странно, если я позвоню await content.ReadAsStringAsync() до SendAsync, тогда это также сработает!

Мои мыслиэто может быть либо:

  • JSON сериализуется по-разному (но я нигде не настраиваю глобалы Json.NET). Очевидной проблемой может быть другая капитализация, но, насколько я знаю,JSON.net нечувствителен к регистру при синтаксическом анализе, и мой сервер будет использовать JSON.net
  • Я не жду чего-то, и содержимое отправляется до полной сериализации.Я не могу обнаружить никаких неожиданных задач в моем коде, хотя, и точка входа является стандартной async Main()

Моя среда:

  • Клиент простойnetcoreapp2.1 exe, работающий в microsoft/dotnet:2.1-sdk-alpine образе докера.
  • Сервер - это .NET Framework MVC5 WebApi, со многими похожими API, которые другие клиенты могут нормально вызывать

Кодниже приведена сокращенная версия того, что вызывает мой клиент.

public static class UserUpdater
{
    public static async Task UpdateAsync(List<string> users)
    {
        using (var client = new HttpClient())
        {
            var form = new { Users = users };
            var headers = new Dictionary<string, string>
            {
                ["Authorization"] = "Bearer " // Real JWT here
            };

            var response = await client.PutAsJsonAsync(new Uri("http://localhost/api/app/v1/gateway-users"), form, headers);
            if (!response.IsSuccessStatusCode)
            {
                throw new InvalidOperationException("Response status code does not indicate success: " +
                                                    response.StatusCode + "\n\n" +
                                                    await response.Content.ReadAsStringAsync());
            }
        }
    }

    public static async Task<HttpResponseMessage> PutAsJsonAsync<T>(this HttpClient client, Uri requestUri, T value,
        IEnumerable<KeyValuePair<string, string>> headers, CancellationToken cancellationToken = default)
    {
        var content = new ObjectContent<T>(value, new JsonMediaTypeFormatter(), (MediaTypeHeaderValue)null);
        ////var content = new StringContent(JsonConvert.SerializeObject(value), Encoding.UTF8, "application/json"); // This works fine
        var message = MakeRequestMessage(HttpMethod.Put, requestUri, headers, content);
        return await client.SendAsync(message, cancellationToken);
    }

    private static HttpRequestMessage MakeRequestMessage(
        [NotNull] HttpMethod method,
        [NotNull] Uri requestUri,
        [CanBeNull] IEnumerable<KeyValuePair<string, string>> headers,
        [CanBeNull] HttpContent content = null)
    {
        var message = new HttpRequestMessage(method, requestUri);

        if (headers != null)
        {
            foreach (var header in headers)
            {
                message.Headers.Add(header.Key, header.Value);
            }
        }

        if (content != null)
        {
            message.Content = content;
        }

        return message;
    }

Если это помогает, то это сторона сервера (MVC5 WebApi):

    [Route("")]
    [ValidateModel]
    public IHttpActionResult Put([Required] [FromBody] GatewayUserCollectionForm form)
    {
        Ensure.NotNull(form, nameof(form)); // This throws since form is null

        this.manager.UpdateGatewayUsers(form.Users.ToList());
        return this.Ok();
    }

    public class GatewayUserCollectionForm
    {
        [Required]
        public List<string> Users { get; set; }
    }
...