¿Cómo paso un objeto a HttpClient.PostAsync y lo serializo como un cuerpo JSON?

94

Estoy usando System.Net.Http, encontré varios ejemplos en la web. Logré crear este código para hacer una POSTsolicitud:

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

todo funciona bien. Pero supongamos que quiero pasar un tercer parámetro al POSTmétodo, un parámetro llamado data. El parámetro de datos es un objeto como este:

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

¿Cómo puedo hacer eso sin crear el KeyValuePair? Mi php RestAPIespera una entrada json, por lo que FormUrlEncodedContentdebería enviar el rawjson correctamente. ¿Pero cómo puedo hacer esto con Microsoft.Net.Http? Gracias.

IlDrugo
fuente
Si entiendo su pregunta, ¿desea enviar contenido JSON en lugar de contenido codificado correctamente (y, por extensión, desea que su tipo anónimo se serialice como JSON en ese contenido)?
CodingGorilla
@CodingGorilla sí es un tipo anónimo.
IlDrugo
3
Como nota al margen para los futuros lectores, no utilice a usingpara HttpClient. aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
maxshuty
2
Nota de Microsoft por usingqué no se debe utilizar: 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

Respuestas:

155

La respuesta directa a su pregunta es: No. La firma del PostAsyncmétodo es la siguiente:

Public Task PostAsync (Uri requestUri, contenido de HttpContent)

Entonces, si bien puede pasar un objecta PostAsync, debe ser de tipo HttpContenty su tipo anónimo no cumple con ese criterio.

Sin embargo, hay formas de lograr lo que desea lograr. Primero, necesitará serializar su tipo anónimo a JSON, la herramienta más común para esto es Json.NET . Y el código para esto es bastante trivial:

var myContent = JsonConvert.SerializeObject(data);

A continuación, deberá construir un objeto de contenido para enviar estos datos, usaré un ByteArrayContentobjeto, pero puede usar o crear un tipo diferente si lo desea.

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

A continuación, desea configurar el tipo de contenido para que la API sepa que se trata de JSON.

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

Luego puede enviar su solicitud de manera muy similar a su ejemplo anterior con el contenido del formulario:

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

En una nota al margen, llamar a la .Resultpropiedad como lo está haciendo aquí puede tener algunos efectos secundarios negativos , como bloqueo muerto, por lo que debe tener cuidado con esto.

CodificaciónGorilla
fuente
Está bien, está muy claro. Gracias por esta respuesta. Solo una pregunta: cuando POST, PUT, DELETEse realiza un, generalmente el retorno de la API TRUE, declaro el método como string, pero cuando lo hago: return result;obtengo:, Can't Convert HttpResponseMessage in string¿debo cambiar la declaración del método? Necesito la respuesta de cadena porque necesitaré deserializarla después en otro método de clase.
IlDrugo
2
Si necesita deserializar el cuerpo de la respuesta, entonces devolver la cadena como lo hizo en su pregunta (usando result.Content.ReadAsStringAsync()) probablemente esté bien. Dependiendo de la estructura de su aplicación, podría ser mejor devolver el Contentobjeto directamente si necesita inspeccionar los encabezados para determinar cuál es el tipo de contenido (por ejemplo, XML o JSON). Pero si sabe que siempre devolverá JSON (o algún otro formato), simplemente devolver el cuerpo de la respuesta como una cadena debería estar bien.
CodingGorilla
Lamento preguntar, pero ¿necesita hacer esto si los datos son de tipo StringContent?
MyDaftQuestions
1
@MyDaftQuestions No estoy exactamente seguro de lo que está preguntando, pero puede pasar un StringContentdirectamente a PostAsyncya que se deriva de HttpContent.
CodingGorilla
@CodingGorilla, eso era lo que estaba preguntando. Gracias :)
MyDaftQuestions
67

Debe pasar sus datos en el cuerpo de la solicitud como una cadena sin formato en lugar de FormUrlEncodedContent. Una forma de hacerlo es serializarlo en una cadena JSON:

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

Ahora todo lo que necesita hacer es pasar la cadena al método de publicación.

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
fuente
¿Qué es stringContent? En mi caso el stringContentvalor es "\"\"". ¿Es este el valor correcto?
R15
¿Es posible obtener el resultado de la cadena en vb desde su código c #? descubrí que es bastante similar ...
gumuruh
42

Una solución simple es usar Microsoft ASP.NET Web API 2.2 Clientdesde NuGet .

Luego, simplemente puede hacer esto y serializará el objeto en JSON y establecerá el Content-Typeencabezado en 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
fuente
2
definitivamente PostAsJsonAsync está ahí para usar
Kat Lim Ruiz
26

Ahora hay una forma más sencilla con .NET Standardo .NET Core:

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

NOTA: Para usar la JsonMediaTypeFormatterclase, deberá instalar el Microsoft.AspNet.WebApi.Clientpaquete NuGet, que se puede instalar directamente o mediante otro como Microsoft.AspNetCore.App.

Con esta firma de HttpClient.PostAsync, puede pasar cualquier objeto y JsonMediaTypeFormatterautomáticamente se encargará de la serialización, etc.

Con la respuesta, puede usar HttpContent.ReadAsAsync<T>para deserializar el contenido de la respuesta al tipo que espera:

var responseObject = await response.Content.ReadAsAsync<MyResponseType>();
Ken Lyon
fuente
1
¿Qué versión de .net está usando? Mi versión no puede encontrar "Formateo" en el espacio de nombres System.Net.Http
TemporaryFix
1
@Programmatic Necesitas usar .NET Standardo .NET Core, como mencioné. ¿Quizás estás usando .NET Framework? En mi proyecto, JsonMediaTypeFormatter se carga desde aquí: C: \ Archivos de programa \ dotnet \ sdk \ NuGetFallbackFolder \ microsoft.aspnet.webapi.client \ 5.2.6 \ lib \ netstandard2.0 \ System.Net.Http.Formatting. dll
Ken Lyon
1
@Programmatic Si ya está utilizando uno de esos tipos de proyectos, es posible que necesite agregar un paquete NuGet adicional. Olvidé exactamente cuáles se incluyeron automáticamente para mí. En mi caso, se incluyó como parte del paquete Microsoft.AspNetCore.App NuGet.
Ken Lyon
1
Estaba usando .NET Core, pero no creo que mi solución estuviera configurada para usar la última versión del lenguaje c #. Actualicé y funcionó. Gracias
TemporaryFix
1
@Programmatic De nada. ¡Me alegra saber que lo hiciste funcionar! Agregué una nota a mi respuesta sobre el paquete NuGet.
Ken Lyon