Я пытаюсь написать перегрузку для 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; }
}