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?


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

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


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

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

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


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

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

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

-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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.