Comment passer un objet à HttpClient.PostAsync et sérialiser en tant que corps JSON?

94

J'utilise System.Net.Http, j'ai trouvé plusieurs exemples sur le web. J'ai réussi à créer ce code pour faire une POSTdemande:

public static string POST(string resource, string token)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(baseUri);
        client.DefaultRequestHeaders.Add("token", token);

        var content = new FormUrlEncodedContent(new[]
        {
             new KeyValuePair<string, string>("", "")
        });

        var result = client.PostAsync("", content).Result;
        string resultContent = result.Content.ReadAsStringAsync().Result;
        return resultContent;
    }
 }

tout fonctionne bien. Mais supposons que je veuille passer un troisième paramètre à la POSTméthode, un paramètre appelé data. Le paramètre de données est un objet comme celui-ci:

object data = new
{
    name = "Foo",
    category = "article"
};

comment puis-je faire cela sans créer le KeyValuePair? Mon php RestAPIattend une entrée json, donc le FormUrlEncodedContentdevrait envoyer rawcorrectement le json. Mais comment puis-je faire cela Microsoft.Net.Http? Merci.

IlDrugo
la source
Si je comprends votre question, vous souhaitez envoyer du contenu JSON au lieu d'un contenu codé par formulaire (et par extension, vous voulez que votre type anonyme soit sérialisé en tant que JSON dans ce contenu)?
CodingGorilla
@CodingGorilla yes est un type anonyme.
IlDrugo
3
Pour les futurs lecteurs, n'utilisez pas de usingpour le HttpClient. aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong
maxshuty
2
Note de Microsoft pourquoi usingne devrait pas être utilisé: HttpClient is intended to be instantiated once and reused throughout the life of an application. The following conditions can result in SocketException errors: Creating a new HttpClient instance per request. Server under heavy load. Creating a new HttpClient instance per request can exhaust the available sockets. docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/…
Ogglas

Réponses:

155

La réponse directe à votre question est: Non. La signature de la PostAsyncméthode est la suivante:

Tâche publique PostAsync (Uri requestUri, contenu HttpContent)

Ainsi, bien que vous puissiez passer un objectà, PostAsyncil doit être de type HttpContentet votre type anonyme ne répond pas à ces critères.

Cependant, il existe des moyens d'accomplir ce que vous voulez accomplir. Tout d'abord, vous devrez sérialiser votre type anonyme en JSON, l'outil le plus courant pour cela est Json.NET . Et le code pour cela est assez trivial:

var myContent = JsonConvert.SerializeObject(data);

Ensuite, vous devrez construire un objet de contenu pour envoyer ces données, j'utiliserai un ByteArrayContentobjet, mais vous pouvez utiliser ou créer un type différent si vous le souhaitez.

var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);

Ensuite, vous souhaitez définir le type de contenu pour indiquer à l'API qu'il s'agit de JSON.

byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

Ensuite, vous pouvez envoyer votre demande très similaire à votre exemple précédent avec le contenu du formulaire:

var result = client.PostAsync("", byteContent).Result

Sur une note secondaire, appeler la .Resultpropriété comme vous le faites ici peut avoir des effets secondaires négatifs tels que le verrouillage mort, vous devez donc être prudent avec cela.

CodageGorille
la source
D'accord, c'est très clair. Merci pour cette réponse. Juste une question: quand un POST, PUT, DELETEest effectué, généralement le retour API TRUE, j'ai déclaré la méthode comme string, mais quand je le fais: return result;j'obtiens:, Can't Convert HttpResponseMessage in stringdois-je changer la déclaration de méthode? J'ai besoin de la réponse de la chaîne car je devrai la désérialiser après dans une autre méthode de classe.
IlDrugo
2
Si vous devez désérialiser le corps de la réponse, renvoyer la chaîne comme vous l'avez fait dans votre question (en utilisant result.Content.ReadAsStringAsync()) est probablement très bien. En fonction de la structure de votre application, il peut être préférable de renvoyer l' Contentobjet directement si vous devez inspecter les en-têtes pour déterminer le type de contenu (par exemple, XML ou JSON). Mais si vous savez que cela retournera toujours JSON (ou un autre format), renvoyer simplement le corps de la réponse sous forme de chaîne devrait être correct.
CodingGorilla
Désolé de demander, mais si devez-vous le faire si les données sont de type StringContent?
MyDaftQuestions
1
@MyDaftQuestions Je ne sais pas exactement ce que vous demandez, mais vous pouvez passer un StringContentdirectement à PostAsynccar il dérive HttpContent.
CodingGorilla
@CodingGorilla, qui était ce que je demandais. Merci :)
MyDaftQuestions
67

Vous devez transmettre vos données dans le corps de la requête sous forme de chaîne brute plutôt que FormUrlEncodedContent. Une façon de le faire est de le sérialiser dans une chaîne JSON:

var json = JsonConvert.SerializeObject(data); // or JsonSerializer.Serialize if using System.Text.Json

Il ne vous reste plus qu'à transmettre la chaîne à la méthode post.

var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); // use MediaTypeNames.Application.Json in Core 3.0+ and Standard 2.1+

var client = new HttpClient();
var response = await client.PostAsync(uri, stringContent);
elolos
la source
Qu'est-ce que c'est stringContent? Dans mon cas, la stringContentvaleur est "\"\"". Cette valeur est-elle correcte?
R15 du
est-il possible d'obtenir le résultat de la chaîne dans vb à partir de votre code c #? J'ai découvert que c'est assez similaire ....
gumuruh
42

Une solution simple consiste à utiliser Microsoft ASP.NET Web API 2.2 Clientdepuis NuGet .

Ensuite, vous pouvez simplement le faire et cela sérialisera l'objet en JSON et définira l'en- Content-Typetête sur application/json; charset=utf-8:

var data = new
{
    name = "Foo",
    category = "article"
};

var client = new HttpClient();
client.BaseAddress = new Uri(baseUri);
client.DefaultRequestHeaders.Add("token", token);
var response = await client.PostAsJsonAsync("", data);
trydis
la source
2
définitivement PostAsJsonAsync est là pour utiliser
Kat Lim Ruiz
26

Il existe maintenant un moyen plus simple avec .NET Standardou .NET Core:

var client = new HttpClient();
var response = await client.PostAsync(uri, myRequestObject, new JsonMediaTypeFormatter());

REMARQUE: pour utiliser la JsonMediaTypeFormatterclasse, vous devrez installer le Microsoft.AspNet.WebApi.Clientpackage NuGet, qui peut être installé directement, ou via un autre tel que Microsoft.AspNetCore.App.

En utilisant cette signature de HttpClient.PostAsync, vous pouvez passer n'importe quel objet et le JsonMediaTypeFormatters'occupera automatiquement de la sérialisation, etc.

Avec la réponse, vous pouvez utiliser HttpContent.ReadAsAsync<T>pour désérialiser le contenu de la réponse au type que vous attendez:

var responseObject = await response.Content.ReadAsAsync<MyResponseType>();
Ken Lyon
la source
1
quelle version de .net utilise-t-il? Ma version ne trouve pas «Formatage» dans l'espace de noms System.Net.Http
TemporaryFix
1
@Programmatic Vous devez utiliser .NET Standardou .NET Core, comme je l'ai mentionné. Peut-être que vous utilisez .NET Framework? Dans mon projet, le JsonMediaTypeFormatter est chargé à partir d'ici: C: \ Program Files \ dotnet \ sdk \ NuGetFallbackFolder \ microsoft.aspnet.webapi.client \ 5.2.6 \ lib \ netstandard2.0 \ System.Net.Http.Formatting. dll
Ken Lyon
1
@Programmatic Si vous utilisez déjà l'un de ces types de projet, il se peut que vous deviez ajouter un package NuGet supplémentaire. J'oublie exactement lesquels ont été inclus automatiquement pour moi. Dans mon cas, il a été inclus dans le cadre du package NuGet Microsoft.AspNetCore.App.
Ken Lyon
1
J'utilisais .NET Core, mais je ne pense pas que ma solution était configurée pour utiliser la dernière version du langage c #. J'ai mis à jour et cela a fonctionné. Merci
TemporaryFix
1
@Programmatic De rien. Je suis heureux que vous ayez réussi à le faire! J'ai ajouté une note à ma réponse sur le package NuGet.
Ken Lyon