Як використовувати System.Net.HttpClient для публікації складного типу?


102

У мене є спеціальний комплексний тип, з яким я хочу працювати з використанням Web API.

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

І ось мій метод контролера веб-API. Я хочу розмістити цей об’єкт так:

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

І тепер я хотів би скористатися System.Net.HttpClientдля виклику методу. Однак я не впевнений, який тип об’єкта перейти до PostAsyncметоду та як його побудувати. Ось приклад коду клієнта.

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

Як створити HttpContentоб'єкт таким чином, щоб веб-API його зрозумів?


Чи намагалися ви надіслати серійну версію вашого об'єкта XML до кінцевої точки служби?
Джошуа Дрейк

Відповіді:


132

Родове HttpRequestMessage<T>був видалений . Це:

new HttpRequestMessage<Widget>(widget)

буде більше не працювати .

Натомість з цієї публікації команда ASP.NET включила кілька нових дзвінків для підтримки цієї функціональності:

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

Отже, новий код ( від dunston ) стає:

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
Так, але що робити, якщо у вас немає доступу до класу віджетів?
contactmatt

13
Нові 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`?
Яанс

1
@Jaans Flurl.Http забезпечує простий / короткий шлях через PostUrlEncodedAsync.
Тодд Меньє

16
Зауважте, що вам потрібно додати посилання на System.Net.Http.Formatting, щоб мати можливість використовувати PostAsJsonAsyncабоPostAsXmlAsync
Піт

6
Щоб використовувати PostAsJsonAcync, додайте пакет NuGet Microsoft.AspNet.WebApi.Client !!
Денніс

99

Ви повинні використовувати SendAsyncметод замість цього, це загальний метод, який серіалізує вхід до служби

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

Якщо ви не хочете створювати конкретний клас, ви можете скласти його з FormUrlEncodedContentкласом

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

Примітка: вам потрібно зробити свій ідентифікатор нульовим int ( int?)


1
Це буде викликано із зовнішнього проекту, де я не матиму посилання на збірку, що містить об’єкт Widget. Я спробував створити анонімно набраний об’єкт, який містить правильні властивості, серіалізував його за допомогою цього методу та передав його таким чином, але я отримую 500 внутрішніх помилок сервера. Він ніколи не потрапляє в метод веб-контролера api.
indot_brad

О, тоді вам потрібно опублікувати xml або json на сервісі webapi, і це знецінить його - він робить те саме, SendAsync, серіалізує об’єкт для сервісу
dunston,

1
Щойно зробили оновлення - у мене тестерують код, але з деяким простішим кодом, але я повинен працювати
Данстон

8
Я отримую "Не загальний тип 'System.Net.Http.HttpRequestMessage' не можна використовувати з аргументами типу". це все-таки діє?
користувач10479

5
Так, перше рішення більше не працює: aspnetwebstack.codeplex.com/discussions/350492
Giovanni B

74

Зауважте, що якщо ви використовуєте бібліотеку портативних класів, HttpClient не матиме метод PostAsJsonAsync . Щоб опублікувати вміст як JSON за допомогою бібліотеки портативних класів, вам доведеться це зробити:

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

Коли argsAsJson походить від серіалізованого об'єкта, і цей об'єкт має властивість, тобто. Content = "домен \ користувач", тоді \ буде кодовано двічі. Один раз при серіалізації в argsAsJson і вдруге, коли PostAsync публікує contentPost. Як уникнути подвійного кодування?
Кшиштоф Морчінек

3
Відмінно @fabiano! Це справді зробило трюк. Ці два додаткові аргументи необхідні для проектів такого типу.
Пітер Кляйн

Дуже добре @PeterKlein! Я не зміг знайти цю інформацію в документації Microsoft через Інтернет, тому це може допомогти іншим із тією ж проблемою. Мій проект просто не надсилає дані без цього фокусу.
Фабіано

1
Зауважте, що вам, можливо, доведеться додати "application / json" у заголовок Accept запиту, за stackoverflow.com/a/40375351/3063273
Matt Thomas

4

Якщо ви хочете, щоб типи методів зручності були згадані в інших відповідях, але вам потрібна портативність (або навіть якщо ви цього не зробите), ви можете перевірити Flurl [розкриття: Я автор]. Він (тонко) обгортає HttpClientі Json.NET і додає трохи вільного цукру та інших смаколиків, включаючи деякі помічники для тестування .

Опублікувати як JSON:

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

або URL-адреса:

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

Обидва приклади вище повертають HttpResponseMessage, але Flurl включає методи розширення для повернення інших речей, якщо ви просто хочете вирізати погоню:

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

Flurl доступний на NuGet:

PM> Install-Package Flurl.Http

1

Дослідивши безліч альтернатив, я натрапив на інший підхід, придатний для версії API 2.0.

(VB.NET - мій улюблений, так ...)

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

Удачі! Для мене це спрацювало (врешті-решт!).

З повагою, Пітер


1
Це з пропозиціями, поданими вище від @Fabiano, змушує щось статися.
Пітер Кляйн

2
VB.NET - ніхто не улюблений :)
Ледачий кодер

1

Я думаю, ви можете це зробити:

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

У випадку, якщо хтось, як я, насправді не зрозумів, про що говорять всі вище, я даю простий приклад, який працює для мене. Якщо у вас є веб-api, URL якого " http://somesite.com/verifyAddress ", це метод публікації, і вам потрібно передати йому об'єкт адреси. Ви хочете зателефонувати цій api у своєму коді. Ось що ви можете зробити.

    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

Це код, з яким я вирішив, грунтуючись на інших відповідях тут. Це для HttpPost, який приймає та реагує на складні типи:

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

Зробіть службовий дзвінок так:

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

І такий спосіб обслуговування:

public HttpResponseMessage PutSaveActivationCode(ActivationCodes objAC)
{
}

PutAsJsonAsync піклується про серіалізацію та десеріалізацію по мережі


Це надішле повідомлення HTTP PUT, а не POST, як вимагається. Як вже говорили інші, PostAsJsonAsyncвони надсилатимуть потрібні дані як пошту в JSON.
Джаф - Бен Дюгід
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.