Jak używać System.Net.HttpClient do publikowania typu złożonego?

102

Mam niestandardowy typ złożony, z którym chcę pracować przy użyciu interfejsu API sieci Web.

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

A oto moja metoda kontrolera internetowego interfejsu API. Chcę opublikować ten obiekt w ten sposób:

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

A teraz chciałbym użyć System.Net.HttpClientdo wywołania metody. Jednak nie jestem pewien, jaki typ obiektu należy przekazać do PostAsyncmetody i jak go skonstruować. Oto przykładowy kod klienta.

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

Jak utworzyć HttpContentobiekt w taki sposób, aby internetowy interfejs API go zrozumiał?

indot_brad
źródło
Czy próbowałeś przesłać zserializowaną wersję XML obiektu do punktu końcowego usługi?
Joshua Drake

Odpowiedzi:

132

Generyczny HttpRequestMessage<T>został usunięty . To:

new HttpRequestMessage<Widget>(widget)

nie będzie już działać .

Zamiast tego w tym poście zespół ASP.NET zawarł kilka nowych wezwań do obsługi tej funkcji:

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

Tak więc nowy kod ( z dunston ) staje się:

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
źródło
1
Tak, ale co jeśli nie masz dostępu do klasy Widget?
contactmatt
13
Nowe 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 listy KeyValuePair?
Jaans
1
@Jaans Flurl.Http zapewnia prostą / krótką drogę przez PostUrlEncodedAsync.
Todd Menier
16
Zauważ, że musisz dodać odwołanie do System.Net.Http.Formatting, aby móc używać PostAsJsonAsynclubPostAsXmlAsync
Pete
6
Aby użyć PostAsJsonAcync, dodaj pakiet NuGet Microsoft.AspNet.WebApi.Client !!
Dennis
99

Zamiast tego należy użyć SendAsyncmetody, jest to metoda ogólna, która serializuje dane wejściowe do usługi

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

Jeśli nie chcesz tworzyć konkretnej klasy, możesz to zrobić z FormUrlEncodedContentklasą

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

Uwaga: musisz ustawić swój identyfikator na wartość null int ( int?)

Dunston
źródło
1
Zostanie to wywołane z zewnętrznego projektu, w którym nie będę mieć odniesienia do zestawu zawierającego obiekt Widget. Próbowałem utworzyć obiekt wpisany anonimowo, który zawiera poprawne właściwości, serializować go za pomocą tej metody i przekazać w ten sposób, ale otrzymuję wewnętrzny błąd serwera 500. Nigdy nie trafia w metodę kontrolera internetowego interfejsu API.
indot_brad
Aha - wtedy musisz wysłać xml, lub json do usługi webapi, a ona ją deserializuje - robi to samo, SendAsync, serializuje obiekt dla usługi
dunston
1
Właśnie dokonałem aktualizacji - przetestowałem kod, ale z prostszym kodem, ale powinienem działać
dunston
8
Otrzymuję komunikat „Nieogólny typ„ System.Net.Http.HttpRequestMessage ”nie może być używany z argumentami typu”. czy to jest nadal aktualne?
user10479
5
Tak, pierwsze rozwiązanie już nie działa: aspnetwebstack.codeplex.com/discussions/350492
Giovanni B
74

Zwróć uwagę, że jeśli używasz przenośnej biblioteki klas, HttpClient nie będzie mieć metody PostAsJsonAsync . Aby opublikować zawartość jako JSON przy użyciu przenośnej biblioteki klas, musisz zrobić to:

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
źródło
Gdy argsAsJson pochodzi z serializowanego obiektu, a ten obiekt ma właściwość tj. Treść = "domena \ użytkownik", wtedy \ zostanie zakodowany dwukrotnie. Raz podczas serializacji do argsAsJson i drugi raz, gdy PostAsync publikuje contentPost. Jak uniknąć podwójnego kodowania?
Krzysztof Morcinek
3
Doskonały @fabiano! To naprawdę zadziałało. Te dwa dodatkowe argumenty są niezbędne w tego typu projektach.
Peter Klein,
Bardzo dobrze @PeterKlein! Nie mogłem znaleźć tych informacji w dokumentacji firmy Microsoft w Internecie, więc może to pomóc innym w rozwiązaniu tego samego problemu. Mój projekt po prostu nie wysyła danych bez tej sztuczki.
Fabiano
1
Zwróć uwagę, że może być konieczne dodanie „application / json” do nagłówka Accept żądania, na stackoverflow.com/a/40375351/3063273
Matt Thomas
4

Jeśli szukasz wygodnych metod wymienionych w innych odpowiedziach, ale potrzebujesz przenośności (a nawet jeśli nie), możesz sprawdzić Flurl [ujawnienie: jestem autorem]. To (cienko) owija HttpClienti Json.NET i dodaje trochę cukru i innych gadżetów, w tym kilka pomocników testowych .

Opublikuj jako JSON:

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

lub zakodowane w adresie URL:

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

Oba powyższe przykłady zwracają HttpResponseMessage, ale Flurl zawiera metody rozszerzające do zwracania innych rzeczy, jeśli chcesz tylko przejść do sedna:

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

Flurl jest dostępny w NuGet:

PM> Install-Package Flurl.Http
Todd Menier
źródło
1

Po zbadaniu wielu alternatyw natknąłem się na inne podejście, odpowiednie dla wersji API 2.0.

(VB.NET jest moim ulubionym, więc ...)

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

Powodzenia! U mnie to się udało (w końcu!).

Pozdrawiam, Peter

user2366741
źródło
1
Dzięki sugestiom podanym powyżej przez @Fabiano wszystko się dzieje.
Peter Klein,
2
VB.NET nie jest ulubionym :)
Lazy Coder
1

Myślę, że możesz to zrobić:

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
źródło
1

W przypadku, gdy ktoś taki jak ja nie bardzo rozumiał, o czym mówię powyżej, podaję prosty przykład, który działa dla mnie. Jeśli masz interfejs API sieciowy, którego adres URL to „ http://somesite.com/verifyAddress ”, jest to metoda wysyłania wiadomości i wymaga przekazania jej obiektu adresu. Chcesz wywołać ten interfejs API w swoim kodzie. Oto co możesz zrobić.

    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
źródło
0

To jest kod, z którym się skończyłem, oparty na innych odpowiedziach tutaj. To jest dla HttpPost, który odbiera i odpowiada za pomocą typów złożonych:

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
źródło
-1

Zadzwoń do serwisu w następujący sposób:

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

I metoda serwisowa, taka jak ta:

public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}

PutAsJsonAsync zajmuje się serializacją i deserializacją w sieci

Nitin Nayyar
źródło
Spowoduje to wysłanie komunikatu HTTP PUT, a nie żądania POST. Jak powiedzieli inni PostAsJsonAsync, wyśle ​​wymagane dane jako POST w formacie JSON.
Zhaph - Ben Duguid