¿Cómo usar System.Net.HttpClient para publicar un tipo complejo?

102

Tengo un tipo complejo personalizado con el que quiero trabajar usando Web API.

public class Widget
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Y aquí está mi método de controlador de API web. Quiero publicar este objeto así:

public class TestController : ApiController
{
    // POST /api/test
    public HttpResponseMessage<Widget> Post(Widget widget)
    {
        widget.ID = 1; // hardcoded for now. TODO: Save to db and return newly created ID

        var response = new HttpResponseMessage<Widget>(widget, HttpStatusCode.Created);
        response.Headers.Location = new Uri(Request.RequestUri, "/api/test/" + widget.ID.ToString());
        return response;
    }
}

Y ahora me gustaría usar System.Net.HttpClientpara hacer la llamada al método. Sin embargo, no estoy seguro de qué tipo de objeto pasar al PostAsyncmétodo y cómo construirlo. Aquí hay un código de cliente de muestra.

var client = new HttpClient();
HttpContent content = new StringContent("???"); // how do I construct the Widget to post?
client.PostAsync("http://localhost:44268/api/test", content).ContinueWith(
    (postTask) =>
    {
        postTask.Result.EnsureSuccessStatusCode();
    });

¿Cómo creo el HttpContentobjeto de manera que la API web lo entienda?

indot_brad
fuente
¿Ha intentado enviar una versión serializada XML de su objeto al punto final del servicio?
Joshua Drake

Respuestas:

132

Se HttpRequestMessage<T>ha eliminado el genérico . Esta :

new HttpRequestMessage<Widget>(widget)

se ya no funcionan .

En cambio, a partir de esta publicación , el equipo de ASP.NET ha incluido algunas llamadas nuevas para admitir esta funcionalidad:

HttpClient.PostAsJsonAsync<T>(T value) sends application/json
HttpClient.PostAsXmlAsync<T>(T value) sends application/xml

Entonces, el nuevo código ( de Dunston ) se convierte en:

Widget widget = new Widget()
widget.Name = "test"
widget.Price = 1;

HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:44268");
client.PostAsJsonAsync("api/test", widget)
    .ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode() );
Joshua Ball
fuente
1
Sí, pero ¿qué pasa si no tiene acceso a la clase Widget?
contactmatt
13
¿Las nuevas HttpClient.PostAsXXXAsync<T>( T value ) methods are great, but what about one for application/x-www-form-urlencoded format? Is there a simple / short way for that or do we still need to create elaborate listas de KeyValuePair`?
Jaans
1
@Jaans Flurl.Http proporciona una forma simple / corta a través de PostUrlEncodedAsync.
Todd Menier
16
Tenga en cuenta que debe agregar una referencia a System.Net.Http.Formatting para poder usar PostAsJsonAsyncoPostAsXmlAsync
Pete
6
Para usar PostAsJsonAcync, agregue el paquete NuGet Microsoft.AspNet.WebApi.Client !!
Dennis
99

Debería usar el SendAsyncmétodo en su lugar, este es un método genérico, que serializa la entrada al servicio

Widget widget = new Widget()
widget.Name = "test"
widget.Price = 1;

HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:44268/api/test");
client.SendAsync(new HttpRequestMessage<Widget>(widget))
    .ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode() );

Si no desea crear la clase concreta, puede hacerlo con la FormUrlEncodedContentclase

var client = new HttpClient();

// This is the postdata
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("Name", "test"));
postData.Add(new KeyValuePair<string, string>("Price ", "100"));

HttpContent content = new FormUrlEncodedContent(postData); 

client.PostAsync("http://localhost:44268/api/test", content).ContinueWith(
    (postTask) =>
    {
        postTask.Result.EnsureSuccessStatusCode();
    });

Nota: debe convertir su identificación en un int ( int?) que acepta valores NULL

Dunston
fuente
1
Esto se llamará desde un proyecto externo, donde no tendré una referencia al ensamblado que contiene el objeto Widget. Intenté crear un objeto escrito de forma anónima que contiene las propiedades correctas, serializándolo usando este método y pasándolo de esa manera, pero obtengo un Error de servidor interno 500. Nunca llega al método del controlador web api.
indot_brad
Oh, entonces necesita publicar xml o json en el servicio webapi, y lo deserializará; hace lo mismo, SendAsync, está serializando el objeto para el servicio
Dunston
1
Acabo de hacer una actualización - he probado el código, pero con un código más simple, pero debería funcionar
Dunston
8
Recibo "El tipo no genérico 'System.Net.Http.HttpRequestMessage' no se puede usar con argumentos de tipo". ¿esto sigue siendo válido?
user10479
5
Sí, la primera solución ya no funciona: aspnetwebstack.codeplex.com/discussions/350492
Giovanni B
74

Tenga en cuenta que si está utilizando una biblioteca de clases portátil, HttpClient no tendrá el método PostAsJsonAsync . Para publicar un contenido como JSON usando una biblioteca de clases portátil, tendrá que hacer esto:

HttpClient client = new HttpClient();
HttpContent contentPost = new StringContent(argsAsJson, Encoding.UTF8, 
"application/json");

await client.PostAsync(new Uri(wsUrl), contentPost).ContinueWith(
(postTask) => postTask.Result.EnsureSuccessStatusCode());
Fabiano
fuente
Cuando argsAsJson proviene de un objeto serializado, y este objeto tiene una propiedad, es decir. Content = "dominio \ usuario", entonces \ se codificará dos veces. Una vez cuando se serializa en argsAsJson y la segunda vez cuando PostAsync publica contentPost. ¿Cómo evitar la doble codificación?
Krzysztof Morcinek
3
Excelente @fabiano! Esto realmente funcionó. Esos dos argumentos extra son necesarios en este tipo de proyectos.
Peter Klein
¡Muy bien @PeterKlein! No pude encontrar esta información en la documentación de Microsoft en la web, por lo que esto puede ayudar a otros con el mismo problema. Mi proyecto simplemente no envía datos sin este truco.
Fabiano
1
Tenga en cuenta que es posible que también deba agregar "application / json" en el encabezado Accept de la solicitud, según stackoverflow.com/a/40375351/3063273
Matt Thomas
4

Si desea los tipos de métodos de conveniencia mencionados en otras respuestas pero necesita portabilidad (o incluso si no la necesita), es posible que desee consultar Flurl [divulgación: soy el autor]. Envuelve (finamente) HttpClienty Json.NET y agrega algo de azúcar fluido y otras ventajas, incluidos algunos ayudantes de prueba incorporados .

Publicar como JSON:

var resp = await "http://localhost:44268/api/test".PostJsonAsync(widget);

o codificado en URL:

var resp = await "http://localhost:44268/api/test".PostUrlEncodedAsync(widget);

Ambos ejemplos anteriores devuelven un HttpResponseMessage, pero Flurl incluye métodos de extensión para devolver otras cosas si solo desea ir al grano:

T poco = await url.PostJsonAsync(data).ReceiveJson<T>();
dynamic d = await url.PostUrlEncodedAsync(data).ReceiveJson();
string s = await url.PostUrlEncodedAsync(data).ReceiveString();

Flurl está disponible en NuGet:

PM> Install-Package Flurl.Http
Todd Menier
fuente
1

Después de investigar muchas alternativas, me encontré con otro enfoque, adecuado para la versión API 2.0.

(VB.NET es mi favorito, muuuy ...)

Public Async Function APIPut_Response(ID as Integer, MyWidget as Widget) as Task(Of HttpResponseMessage)
    Dim DesiredContent as HttpContent = New StringContent(JsonConvert.SerializeObject(MyWidget))
    Return Await APIClient.PutAsync(String.Format("api/widget/{0}", ID), DesiredContent)
End Function

¡Buena suerte! Para mí esto funcionó (¡al final!).

Saludos, Peter

usuario2366741
fuente
1
Esto CON las sugerencias dadas arriba por @Fabiano hacen que las cosas sucedan.
Peter Klein
2
VB.NET no es el favorito de nadie :)
Lazy Coder
1

Creo que puedes hacer esto:

var client = new HttpClient();
HttpContent content = new Widget();
client.PostAsync<Widget>("http://localhost:44268/api/test", content, new FormUrlEncodedMediaTypeFormatter())
    .ContinueWith((postTask) => { postTask.Result.EnsureSuccessStatusCode(); });
Marius Stănescu
fuente
1

En caso de que alguien como yo no entienda realmente de qué están hablando todos los anteriores, doy un ejemplo sencillo que me está funcionando. Si tiene una API web cuya URL es " http://somesite.com/verifyAddress ", es un método de publicación y necesita que le pase un objeto de dirección. Quieres llamar a esta api en tu código. Aquí lo que puedes hacer.

    public Address verifyAddress(Address address)
    {
        this.client = new HttpClient();
        client.BaseAddress = new Uri("http://somesite.com/");
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        var urlParm = URL + "verifyAddress";
        response = client.PostAsJsonAsync(urlParm,address).Result;
        var dataObjects = response.IsSuccessStatusCode ? response.Content.ReadAsAsync<Address>().Result : null;
        return dataObjects;
    }
user3293338
fuente
0

Este es el código con el que terminé, basado en las otras respuestas aquí. Esto es para un HttpPost que recibe y responde con tipos complejos:

Task<HttpResponseMessage> response = httpClient.PostAsJsonAsync(
                       strMyHttpPostURL,
                       new MyComplexObject { Param1 = param1, Param2 = param2}).ContinueWith((postTask) => postTask.Result.EnsureSuccessStatusCode());
                    //debug:
                    //String s = response.Result.Content.ReadAsStringAsync().Result;
                    MyOtherComplexType moct = (MyOtherComplexType)JsonConvert.DeserializeObject(response.Result.Content.ReadAsStringAsync().Result, typeof(MyOtherComplexType));
Lodlaiden
fuente
-1

Realice una llamada de servicio como esta:

public async void SaveActivationCode(ActivationCodes objAC)
{
    var client = new HttpClient();
    client.BaseAddress = new Uri(baseAddress);
    HttpResponseMessage response = await client.PutAsJsonAsync(serviceAddress + "/SaveActivationCode" + "?apiKey=445-65-1216", objAC);
} 

Y método de servicio como este:

public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}

PutAsJsonAsync se encarga de la serialización y deserialización en la red

Nitin Nayyar
fuente
Esto enviará un mensaje HTTP PUT, no un POST según lo solicitado. Como han dicho otros PostAsJsonAsync, enviará los datos requeridos, como un POST en JSON.
Zhaph - Ben Duguid