Como faço para passar um objeto para HttpClient.PostAsync e serializar como um corpo JSON?

95

Estou usando System.Net.Http, encontrei vários exemplos na web. Consegui criar este código para fazer um POSTpedido:

public static string POST(string resource, string token)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(baseUri);
        client.DefaultRequestHeaders.Add("token", token);

        var content = new FormUrlEncodedContent(new[]
        {
             new KeyValuePair<string, string>("", "")
        });

        var result = client.PostAsync("", content).Result;
        string resultContent = result.Content.ReadAsStringAsync().Result;
        return resultContent;
    }
 }

tudo funcionando bem. Mas suponha que eu queira passar um terceiro parâmetro para o POSTmétodo, um parâmetro chamado data. O parâmetro de dados é um objeto como este:

object data = new
{
    name = "Foo",
    category = "article"
};

como posso fazer isso sem criar o KeyValuePair? Meu php RestAPIespera uma entrada json, então FormUrlEncodedContentdeve enviar o rawjson corretamente. Mas como posso fazer isso Microsoft.Net.Http? Obrigado.

IlDrugo
fonte
Se entendi sua pergunta, você deseja enviar conteúdo JSON em vez de conteúdo codificado de forma correta (e, por extensão, deseja que seu tipo anônimo seja serializado como JSON nesse conteúdo)?
CodingGorilla
@CodingGorilla sim é um tipo anônimo.
IlDrugo
3
Como nota lateral para futuros leitores, não use um usingpara o HttpClient. aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
maxshuty
2
Observação da Microsoft por usingque não deve ser usado: HttpClient is intended to be instantiated once and reused throughout the life of an application. The following conditions can result in SocketException errors: Creating a new HttpClient instance per request. Server under heavy load. Creating a new HttpClient instance per request can exhaust the available sockets. docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/…
Ogglas

Respostas:

160

A resposta direta à sua pergunta é: Não. A assinatura do PostAsyncmétodo é a seguinte:

Public Task PostAsync (Uri requestUri, conteúdo HttpContent)

Portanto, embora você possa passar um objectpara, PostAsyncele deve ser do tipo HttpContente seu tipo anônimo não atende a esses critérios.

No entanto, existem maneiras de realizar o que você deseja realizar. Primeiro, você precisará serializar seu tipo anônimo para JSON, a ferramenta mais comum para isso é Json.NET . E o código para isso é bastante trivial:

var myContent = JsonConvert.SerializeObject(data);

Em seguida, você precisará construir um objeto de conteúdo para enviar esses dados. Vou usar um ByteArrayContentobjeto, mas você pode usar ou criar um tipo diferente, se quiser.

var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);

Em seguida, você deseja definir o tipo de conteúdo para permitir que a API saiba que é JSON.

byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

Em seguida, você pode enviar sua solicitação muito semelhante ao exemplo anterior com o conteúdo do formulário:

var result = client.PostAsync("", byteContent).Result

Por outro lado, chamar a .Resultpropriedade como você está fazendo aqui pode ter alguns efeitos colaterais ruins , como bloqueio morto, então você deve ter cuidado com isso.

CodingGorilla
fonte
Ok, está muito claro. Obrigado por esta resposta. Só uma pergunta: quando um POST, PUT, DELETEé executado, geralmente o retorno da API TRUE, declaro o método como string, mas quando faço: return result;Eu obtenho Can't Convert HttpResponseMessage in string:, devo alterar a declaração do método? Eu preciso da resposta da string porque vou precisar desserializar depois em outro método de classe.
IlDrugo
2
Se você precisar desserializar o corpo da resposta, result.Content.ReadAsStringAsync()provavelmente será adequado retornar a string da maneira que você fez em sua pergunta (usando ). Dependendo da estrutura do seu aplicativo, pode ser melhor retornar o Contentobjeto diretamente se você precisar inspecionar os cabeçalhos para determinar qual é o tipo de conteúdo (por exemplo, XML ou JSON). Mas se você sabe que ele sempre retornará JSON (ou algum outro formato), basta retornar o corpo da resposta como uma string.
CodingGorilla
Desculpe perguntar, mas se você precisa fazer isso se os dados forem do tipo StringContent?
MyDaftQuestions
1
@MyDaftQuestions Não tenho certeza do que você está perguntando, mas você pode passar um StringContentdiretamente para, PostAsyncuma vez que deriva de HttpContent.
CodingGorilla
@CodingGorilla, era o que eu estava perguntando. Obrigado :)
MyDaftQuestions
69

Você precisa passar seus dados no corpo da solicitação como uma string bruta em vez de FormUrlEncodedContent. Uma maneira de fazer isso é serializá-lo em uma string JSON:

var json = JsonConvert.SerializeObject(data); // or JsonSerializer.Serialize if using System.Text.Json

Agora tudo que você precisa fazer é passar a string para o método post.

var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); // use MediaTypeNames.Application.Json in Core 3.0+ and Standard 2.1+

var client = new HttpClient();
var response = await client.PostAsync(uri, stringContent);
Elolos
fonte
O que é stringContent? No meu caso, o stringContentvalor é "\"\"". Este é o valor correto?
R15
é possível obter o resultado da string em vb do seu código c #? descobri que é bastante semelhante ...
gumuruh
45

Uma solução simples é usar Microsoft ASP.NET Web API 2.2 Clienta partir NuGet .

Em seguida, você pode simplesmente fazer isso e serializar o objeto para JSON e definir o Content-Typecabeçalho para application/json; charset=utf-8:

var data = new
{
    name = "Foo",
    category = "article"
};

var client = new HttpClient();
client.BaseAddress = new Uri(baseUri);
client.DefaultRequestHeaders.Add("token", token);
var response = await client.PostAsJsonAsync("", data);
Trydis
fonte
3
definitivamente PostAsJsonAsync está lá para usar
Kat Lim Ruiz
27

Agora existe uma maneira mais simples com .NET Standardou .NET Core:

var client = new HttpClient();
var response = await client.PostAsync(uri, myRequestObject, new JsonMediaTypeFormatter());

NOTA: Para usar a JsonMediaTypeFormatterclasse, você precisará instalar o Microsoft.AspNet.WebApi.Clientpacote NuGet, que pode ser instalado diretamente ou por meio de outro, como Microsoft.AspNetCore.App.

Usando essa assinatura de HttpClient.PostAsync, você pode passar qualquer objeto e o JsonMediaTypeFormatterfará automaticamente a serialização etc.

Com a resposta, você pode usar HttpContent.ReadAsAsync<T>para desserializar o conteúdo da resposta para o tipo que você está esperando:

var responseObject = await response.Content.ReadAsAsync<MyResponseType>();
Ken Lyon
fonte
1
que versão do .net está usando? Minha versão não consegue encontrar "Formatting" no namespace System.Net.Http
TemporaryFix
1
@Programático Você precisa estar usando .NET Standardou .NET Core, como mencionei. Talvez você esteja usando .NET Framework? Em meu projeto, o JsonMediaTypeFormatter está sendo carregado a partir daqui: C: \ Arquivos de programas \ dotnet \ sdk \ NuGetFallbackFolder \ microsoft.aspnet.webapi.client \ 5.2.6 \ lib \ netstandard2.0 \ System.Net.Http.Formatting. dll
Ken Lyon
1
@Programático Se você já estiver usando um desses tipos de projeto, pode ser necessário adicionar um pacote NuGet extra. Eu esqueço exatamente quais foram incluídos automaticamente para mim. No meu caso, ele foi incluído como parte do pacote NuGet Microsoft.AspNetCore.App.
Ken Lyon
1
Eu estava usando o .NET Core, mas não acho que minha solução foi configurada para usar a versão mais recente da linguagem c #. Eu atualizei e funcionou. Obrigado
TemporaryFix
1
@Programático de nada. Fico feliz em saber que você está funcionando! Eu adicionei uma observação à minha resposta sobre o pacote NuGet.
Ken Lyon