Comment utiliser System.Net.HttpClient pour publier un type complexe?


102

J'ai un type complexe personnalisé avec lequel je souhaite travailler à l'aide de l'API Web.

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

Et voici ma méthode de contrôleur API Web. Je veux publier cet objet comme ceci:

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

Et maintenant, j'aimerais utiliser System.Net.HttpClientpour faire l'appel à la méthode. Cependant, je ne sais pas quel type d'objet passer dans la PostAsyncméthode et comment le construire. Voici un exemple de code client.

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

Comment créer l' HttpContentobjet de manière à ce que l'API Web le comprenne?


Avez-vous essayé de soumettre une version sérialisée XML de votre objet au point de terminaison de service?
Joshua Drake

Réponses:


132

Le générique HttpRequestMessage<T>a été supprimé . Ce :

new HttpRequestMessage<Widget>(widget)

sera pas de travail plus .

Au lieu de cela, à partir de cet article , l'équipe ASP.NET a inclus de nouveaux appels pour prendre en charge cette fonctionnalité:

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

Ainsi, le nouveau code ( de Dunston ) devient:

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
Oui, mais que faire si vous n'avez pas accès à la classe Widget?
contactmatt

13
Les nouvelles 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 listes KeyValuePair`?
Jaans

1
@Jaans Flurl.Http fournit un moyen simple / court via PostUrlEncodedAsync.
Todd Menier

16
Notez que vous devez ajouter une référence à System.Net.Http.Formatting pour pouvoir utiliser PostAsJsonAsyncouPostAsXmlAsync
Pete

6
Pour utiliser PostAsJsonAcync, ajoutez le package NuGet Microsoft.AspNet.WebApi.Client !!
Dennis

99

Vous devriez utiliser la SendAsyncméthode à la place, c'est une méthode générique, qui sérialise l'entrée du service

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 vous ne voulez pas créer la classe concrète, vous pouvez la créer avec la 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();
    });

Remarque: vous devez faire de votre identifiant un int nullable ( int?)


1
Cela sera appelé à partir d'un projet externe, où je n'aurai pas de référence à l'assemblage qui contient l'objet Widget. J'ai essayé de créer un objet typé de manière anonyme qui contient les propriétés correctes, de le sérialiser à l'aide de cette méthode et de le transmettre de cette façon, mais j'obtiens une erreur de serveur interne 500. Il n'atteint jamais la méthode du contrôleur de l'API Web.
indot_brad

Oh - alors vous devez publier xml ou json sur le service webapi, et il le désérialisera - il fait la même chose, SendAsync, sérialise l'objet pour le service
dunston

1
Je viens de faire une mise à jour - j'ai testé le code, mais avec un code plus simple, mais je devrais travailler
Dunston

8
J'obtiens "Le type non générique 'System.Net.Http.HttpRequestMessage' ne peut pas être utilisé avec des arguments de type". est-ce toujours valable?
user10479

5
Ouais la première solution ne fonctionne plus: aspnetwebstack.codeplex.com/discussions/350492
Giovanni B

74

Notez que si vous utilisez une bibliothèque de classes portable, HttpClient n'aura pas de méthode PostAsJsonAsync . Pour publier un contenu au format JSON à l'aide d'une bibliothèque de classes portable, vous devrez procéder comme suit:

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

Lorsque argsAsJson provient d'un objet sérialisé, et que cet objet a une propriété ie. Content = "domaine \ utilisateur", alors le \ sera encodé deux fois. Une fois lorsqu'il est sérialisé sur argsAsJson et une deuxième fois lorsque PostAsync publie contentPost. Comment éviter le double encodage?
Krzysztof Morcinek

3
Excellent @fabiano! Cela a vraiment fait l'affaire. Ces deux arguments supplémentaires sont nécessaires dans ce type de projets.
Peter Klein

Très bien @PeterKlein! Je n'ai pas pu trouver ces informations dans la documentation de Microsoft sur le Web, cela peut donc aider d'autres personnes à résoudre le même problème. Mon projet n'envoie tout simplement pas de données sans cette astuce.
Fabiano

1
Notez que vous devrez peut-être également ajouter "application / json" dans l'en-tête Accept de la requête, par stackoverflow.com/a/40375351/3063273
Matt Thomas

4

Si vous voulez les types de méthodes de commodité mentionnés dans d'autres réponses mais que vous avez besoin de portabilité (ou même si vous ne le faites pas), vous voudrez peut-être vérifier Flurl [divulgation: je suis l'auteur]. Il enveloppe (finement) HttpClientet Json.NET et ajoute du sucre courant et d'autres goodies, y compris des assistants de test intégrés .

Publier en JSON:

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

ou encodé en URL:

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

Les deux exemples ci-dessus renvoient un HttpResponseMessage, mais Flurl inclut des méthodes d'extension pour renvoyer d'autres choses si vous voulez juste aller droit au but:

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 sur NuGet:

PM> Install-Package Flurl.Http

1

Après avoir étudié de nombreuses alternatives, je suis tombé sur une autre approche, adaptée à la version API 2.0.

(VB.NET est mon préféré, sooo ...)

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

Bonne chance! Pour moi, cela a fonctionné (à la fin!).

Cordialement, Peter


1
Ceci AVEC les suggestions données ci-dessus par @Fabiano fait bouger les choses.
Peter Klein

2
VB.NET n'est pas un favori :)
Lazy Coder

1

Je pense que vous pouvez faire ceci:

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

Au cas où quelqu'un comme moi ne comprendrait pas vraiment de quoi parlent tout ce qui précède, je donne un exemple simple qui fonctionne pour moi. Si vous avez une API Web dont l'url est " http://somesite.com/verifyAddress ", il s'agit d'une méthode de publication et vous devez lui transmettre un objet d'adresse. Vous souhaitez appeler cette API dans votre code. Voici ce que vous pouvez faire.

    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

C'est le code avec lequel je me suis retrouvé, basé sur les autres réponses ici. Ceci est pour un HttpPost qui reçoit et répond avec des types complexes:

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

Faites un appel de service comme celui-ci:

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

Et méthode de service comme celle-ci:

public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}

PutAsJsonAsync s'occupe de la sérialisation et de la désérialisation sur le réseau


Cela enverra un message HTTP PUT, pas un POST comme demandé. Comme d'autres l'ont dit PostAsJsonAsync, enverra les données requises, en tant que POST dans JSON.
Zhaph - Ben Duguid
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.