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?

indot_brad
la source
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() );
Joshua Ball
la source
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?)

Dunston
la source
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());
Fabiano
la source
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
Todd Menier
la source
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

user2366741
la source
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(); });
Marius Stănescu
la source
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;
    }
user3293338
la source
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));
Lodlaiden
la source
-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

Nitin Nayyar
la source
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