Como usar System.Net.HttpClient para postar um tipo complexo?

102

Eu tenho um tipo complexo personalizado com o qual desejo trabalhar usando a API da Web.

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

E aqui está meu método de controlador de API da web. Eu quero postar este objeto assim:

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

E agora eu gostaria de usar System.Net.HttpClientpara fazer a chamada ao método. No entanto, não tenho certeza de que tipo de objeto passar para o PostAsyncmétodo e como construí-lo. Aqui está um exemplo de código do cliente.

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

Como faço para criar o HttpContentobjeto de uma maneira que a API da web o entenda?

indot_brad
fonte
Você tentou enviar uma versão serializada XML de seu objeto para o terminal de serviço?
Joshua Drake

Respostas:

132

O genérico HttpRequestMessage<T>foi removido . Este :

new HttpRequestMessage<Widget>(widget)

vai não funcionam mais .

Em vez disso, a partir desta postagem , a equipe ASP.NET incluiu algumas novas chamadas para oferecer suporte a essa funcionalidade:

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

Então, o novo código ( de Dunston ) se torna:

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
fonte
1
Sim, mas e se você não tiver acesso à classe Widget?
contactmatt
13
As novas 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 KeyValuePair`?
Jaans
1
@Jaans Flurl.Http fornece um caminho simples / curto via PostUrlEncodedAsync.
Todd Menier
16
Observe que você precisa adicionar uma referência a System.Net.Http.Formatting para poder usar PostAsJsonAsyncouPostAsXmlAsync
Pete
6
Para usar o PostAsJsonAcync, adicione o pacote NuGet Microsoft.AspNet.WebApi.Client !!
Dennis
99

Você deve usar o SendAsyncmétodo em vez disso, este é um método genérico, que serializa a entrada para o serviço

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() );

Se você não quiser criar a classe concreta, você pode fazer isso com a FormUrlEncodedContentclasse

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

Observação: você precisa tornar seu id um int ( int?) anulável

Dunston
fonte
1
Será chamado a partir de um projeto externo, onde não terei uma referência à montagem que contém o objeto Widget. Tentei criar um objeto digitado anonimamente que contém as propriedades corretas, serializando-o usando esse método e passando-o dessa forma, mas recebo um 500 Erro Interno do Servidor. Nunca atinge o método do controlador de API da web.
indot_brad
Oh - então você precisa postar xml ou json para o serviço webapi, e ele vai desserializar - faz o mesmo, SendAsync, serializando o objeto para o serviço
dunston
1
Acabei de fazer uma atualização - testei o código, mas com um código mais simples, mas devo funcionar
Dunston
8
Estou recebendo "O tipo não genérico 'System.Net.Http.HttpRequestMessage' não pode ser usado com argumentos de tipo". isso ainda é válido?
user10479
5
Sim, a primeira solução não funciona mais: aspnetwebstack.codeplex.com/discussions/350492
Giovanni B
74

Observe que, se você estiver usando uma Biblioteca de Classes Portátil, HttpClient não terá o método PostAsJsonAsync . Para postar um conteúdo como JSON usando uma Biblioteca de Classes Portátil, você terá que fazer o seguinte:

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
fonte
Quando argsAsJson vem de um objeto serializado, e esse objeto tem uma propriedade ie. Conteúdo = "domínio \ usuário", então o \ será codificado duas vezes. Uma vez quando serializado para argsAsJson e segunda vez quando PostAsync posta contentPost. Como evitar codificação dupla?
Krzysztof Morcinek de
3
Excelente @fabiano! Isso realmente funcionou. Esses dois argumentos extras são necessários neste tipo de projetos.
Peter Klein
Muito bom @PeterKlein! Não consegui encontrar essas informações na documentação da Microsoft na web, portanto, isso pode ajudar outras pessoas com o mesmo problema. Meu projeto simplesmente não envia dados sem esse truque.
Fabiano
1
Observe que também pode ser necessário adicionar "application / json" ao cabeçalho Aceitar da solicitação, por stackoverflow.com/a/40375351/3063273
Matt Thomas
4

Se você quiser os tipos de métodos de conveniência mencionados em outras respostas, mas precisam de portabilidade (ou mesmo se não), você pode querer verificar Flurl [divulgação: eu sou o autor]. Ele envolve o HttpClientJson.NET e adiciona um pouco de açúcar fluente e outras guloseimas, incluindo alguns auxiliares de teste integrados .

Postar como JSON:

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

ou codificado por URL:

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

Ambos os exemplos acima retornam um HttpResponseMessage, mas Flurl inclui métodos de extensão para retornar outras coisas se você quiser ir direto ao ponto:

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

Flurl está disponível no NuGet:

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

Depois de investigar várias alternativas, descobri outra abordagem, adequada para a versão API 2.0.

(VB.NET é o meu favorito, então ...)

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

Boa sorte! Para mim deu certo (no final!).

Atenciosamente, Peter

user2366741
fonte
1
Isso COM as sugestões dadas acima por @Fabiano fazem as coisas acontecerem.
Peter Klein de
2
VB.NET não é o favorito de ninguém :)
Lazy Coder
1

Eu acho que você pode fazer isso:

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
fonte
1

No caso de alguém como eu não ter entendido muito bem o que todos acima estão falando, dou um exemplo fácil que está funcionando para mim. Se você tem uma api web cujo url é " http://somesite.com/verifyAddress ", é um método de postagem e você precisa passar um objeto de endereço. Você deseja chamar esta API em seu código. Aqui está o que você pode fazer.

    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
fonte
0

Este é o código que acabei usando, com base nas outras respostas aqui. Isso é para um HttpPost que recebe e responde com tipos complexos:

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
fonte
-1

Faça uma chamada de serviço 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);
} 

E método de serviço como este:

public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}

PutAsJsonAsync cuida da serialização e desserialização na rede

Nitin Nayyar
fonte
Isso enviará uma mensagem HTTP PUT, não um POST conforme solicitado. Como já foi dito PostAsJsonAsyncirá enviar os dados requeridos, como um POST em JSON.
Zhaph - Ben Duguid