Wie verwende ich System.Net.HttpClient, um einen komplexen Typ zu veröffentlichen?

102

Ich habe einen benutzerdefinierten komplexen Typ, mit dem ich mithilfe der Web-API arbeiten möchte.

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

Und hier ist meine Web-API-Controller-Methode. Ich möchte dieses Objekt so posten:

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

Und jetzt möchte ich System.Net.HttpClientdie Methode aufrufen. Ich bin mir jedoch nicht sicher, welche Art von Objekt an die PostAsyncMethode übergeben werden soll und wie sie erstellt werden soll. Hier ist ein Beispiel für einen Client-Code.

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

Wie erstelle ich das HttpContentObjekt so, dass die Web-API es versteht?

indot_brad
quelle
Haben Sie versucht, eine XML-serialisierte Version Ihres Objekts an den Service-Endpunkt zu senden?
Joshua Drake

Antworten:

132

Das Generikum HttpRequestMessage<T>wurde entfernt . Dies :

new HttpRequestMessage<Widget>(widget)

wird nicht mehr funktionieren .

Stattdessen hat das ASP.NET-Team in diesem Beitrag einige neue Aufrufe aufgenommen , um diese Funktionalität zu unterstützen:

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

Der neue Code ( von Dunston ) lautet also :

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
quelle
1
Ja, aber was ist, wenn Sie keinen Zugriff auf die Widget-Klasse haben?
Kontaktmatt
13
Die neuen 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 KeyValuePair-Listen?
Jaans
1
@Jaans Flurl.Http bietet einen einfachen / kurzen Weg über PostUrlEncodedAsync.
Todd Menier
16
Beachten Sie, dass Sie einen Verweis auf System.Net.Http.Formatting hinzufügen müssen, um PostAsJsonAsyncoderPostAsXmlAsync
Pete
6
Um PostAsJsonAcync zu verwenden, fügen Sie das NuGet-Paket Microsoft.AspNet.WebApi.Client hinzu !!
Dennis
99

Sie sollten SendAsyncstattdessen die Methode verwenden. Dies ist eine generische Methode, die die Eingabe für den Dienst serialisiert

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

Wenn Sie die konkrete Klasse nicht erstellen möchten, können Sie sie mit der FormUrlEncodedContentKlasse erstellen

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

Hinweis: Sie müssen Ihre ID auf nullable int ( int?) setzen.

Dunston
quelle
1
Dies wird von einem externen Projekt aufgerufen, in dem ich keinen Verweis auf die Assembly habe, die das Widget-Objekt enthält. Ich habe versucht, ein anonym typisiertes Objekt mit den richtigen Eigenschaften zu erstellen, es mit dieser Methode zu serialisieren und auf diese Weise zu übergeben, aber es wird ein 500 Internal Server Error angezeigt. Es trifft nie die Web-API-Controller-Methode.
indot_brad
Oh - dann müssen Sie XML oder JSON an den Webapi-Dienst senden, und es wird es deserialisieren - es macht dasselbe, SendAsync, serialisiert das Objekt für den Dienst
Dunston
1
Habe gerade ein Update gemacht - ich habe den Code getestet, aber mit etwas einfacherem Code, aber ich sollte arbeiten
Dunston
8
Ich erhalte die Meldung "Der nicht generische Typ 'System.Net.Http.HttpRequestMessage' kann nicht mit Typargumenten verwendet werden". ist das noch gültig?
user10479
5
Ja, die erste Lösung funktioniert nicht mehr: aspnetwebstack.codeplex.com/discussions/350492
Giovanni B
74

Beachten Sie, dass HttpClient bei Verwendung einer Portable Class Library nicht über die PostAsJsonAsync-Methode verfügt . Um einen Inhalt als JSON mithilfe einer Portable Class Library zu veröffentlichen, müssen Sie Folgendes tun:

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
quelle
Wenn argsAsJson von einem serialisierten Objekt stammt und dieses Objekt eine Eigenschaft hat, d. H. Content = "domain \ user", dann wird das \ zweimal codiert. Einmal bei der Serialisierung in argsAsJson und zum zweiten Mal bei der Veröffentlichung von contentPost durch PostAsync. Wie vermeide ich Doppelkodierung?
Krzysztof Morcinek
3
Ausgezeichnetes @fabiano! Das hat wirklich den Trick gemacht. Diese beiden zusätzlichen Argumente sind für diese Art von Projekten erforderlich.
Peter Klein
Sehr gut @PeterKlein! Ich konnte diese Informationen nicht in der Microsoft-Dokumentation über das Internet finden, sodass dies anderen bei demselben Problem helfen kann. Mein Projekt sendet ohne diesen Trick einfach keine Daten.
Fabiano
1
Beachten Sie, dass Sie möglicherweise auch "application / json" in den Accept-Header der Anforderung einfügen müssen
Matt Thomas
4

Wenn Sie die in anderen Antworten genannten Arten von Convenience-Methoden möchten, aber Portabilität benötigen (oder auch nicht), sollten Sie sich Flurl ansehen [Offenlegung: Ich bin der Autor]. Es (dünn) wickelt HttpClientund Json.NET und fügt einige fließend Zucker und andere Leckereien, darunter auch einige backene in Helfer zu testen .

Post als JSON:

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

oder URL-codiert:

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

Beide obigen Beispiele geben ein zurück HttpResponseMessage, aber Flurl enthält Erweiterungsmethoden zum Zurückgeben anderer Dinge, wenn Sie nur auf den Punkt kommen möchten:

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

Flurl ist auf NuGet verfügbar:

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

Nachdem ich viele Alternativen untersucht habe, bin ich auf einen anderen Ansatz gestoßen, der für die API 2.0-Version geeignet ist.

(VB.NET ist mein Favorit, 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

Viel Glück! Für mich hat das geklappt (am Ende!).

Grüße, Peter

user2366741
quelle
1
Dies mit den oben von @Fabiano gegebenen Vorschlägen macht Dinge möglich.
Peter Klein
2
VB.NET ist niemandes Favorit :)
Lazy Coder
1

Ich denke, Sie können dies tun:

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

Für den Fall, dass jemand wie ich nicht wirklich verstanden hat, worüber alle oben genannten sprechen, gebe ich ein einfaches Beispiel, das für mich funktioniert. Wenn Sie eine Web-API mit der URL " http://somesite.com/verifyAddress " haben, handelt es sich um eine Post-Methode, für die Sie ein Adressobjekt übergeben müssen. Sie möchten diese API in Ihrem Code aufrufen. Hier was du machen kannst.

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

Dies ist der Code, mit dem ich abgeschlossen habe, basierend auf den anderen Antworten hier. Dies ist für einen HttpPost, der komplexe Typen empfängt und mit diesen antwortet:

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

Machen Sie einen Serviceabruf wie folgt:

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

Und Service-Methode wie folgt:

public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}

PutAsJsonAsync kümmert sich um die Serialisierung und Deserialisierung über das Netzwerk

Nitin Nayyar
quelle
Dadurch wird eine HTTP-PUT-Nachricht gesendet, nicht wie angefordert ein POST. Wie andere gesagt haben, PostAsJsonAsyncwerden die erforderlichen Daten als POST in JSON gesendet.
Zhaph - Ben Duguid