Як встановити заголовок Content-Type для запиту HttpClient?


739

Я намагаюся встановити Content-Typeзаголовок HttpClientоб’єкта, як того вимагає API, який я дзвоню.

Я спробував налаштувати Content-Typeподібне нижче:

using (var httpClient = new HttpClient())
{
    httpClient.BaseAddress = new Uri("http://example.com/");
    httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
    httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json");
    // ...
}

Це дозволяє мені додати Acceptзаголовок, але коли я намагаюся додати, Content-Typeвін видає такий виняток:

Неправильно використане ім'я заголовка. Переконайтеся, що заголовки запитів використовуються із HttpRequestMessageзаголовками відповідей HttpResponseMessageта заголовками вмісту з HttpContentоб’єктами.

Як я можу встановити Content-Typeзаголовок у HttpClientзапиті?


Ви можете прослідкувати, як це робить HttpWebRequest в .NET Core (він використовує HttpClient внутрішньо), див. Github.com/dotnet/corefx/blob/master/src/System.Net.Requests/… метод "SendRequest"
jiping-s

Відповіді:


928

Тип вмісту - це заголовок вмісту, а не запиту, тому це не вдається. AddWithoutValidationяк запропонував Роберт Леві, може працювати, але ви також можете встановити тип вмісту, створюючи сам контент запиту (зауважте, що фрагмент коду додає "application / json" у двох місцях - для заголовків Accept і Content-Type):

HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://example.com/");
client.DefaultRequestHeaders
      .Accept
      .Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT header

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "relativeAddress");
request.Content = new StringContent("{\"name\":\"John Doe\",\"age\":33}",
                                    Encoding.UTF8, 
                                    "application/json");//CONTENT-TYPE header

client.SendAsync(request)
      .ContinueWith(responseTask =>
      {
          Console.WriteLine("Response: {0}", responseTask.Result);
      });

32
Крім того, Response.Content.Headersбуде працювати більшу частину часу.
Джон Гітцен

4
@AshishJain Більшість відповідей, про які я бачив, що стосуються Response.Content.Headersвеб-API ASP.Net, також не працювали, але ви можете легко встановити його, HttpContext.Current.Response.ContentTypeякщо вам потрібно.
jerhewet

6
@jerhewet я використовував таким чином, який працював на мене. var content = новий StringContent (data, Encoding.UTF8, "application / json");
Ashish-BeJovial

22
Content-Type - це властивість повідомлення HTTP з корисним навантаженням; це не має нічого спільного з запитом проти відповіді.
Джуліан Решке

6
Цікаво. Я спробував створити новий StringContent з трьома параметрами, і він не спрацював. Потім я вручну: request.Content.Headers.Remove ("Content-Type"), а потім: request.Content.Headers.Add ("Content-Type", "application / query + json"), і це спрацювало. Незвичайно.
Білл Ноель

163

Для тих, хто не бачив Джонса, коментуючи рішення Карлоса ...

req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

Завантажуючи PDF-файл, це мало значення. З телефону він намагався завантажити HTML-код. Після перетворення розширення файл зазвичай кодувався.
Маттео Дефанті

Мені довелося кинути .ToString () наприкінці, але так, це працювало для реалізації послуги WCF.
Джон Мейєр

2
Я врешті-решт з’ясую, що таке тип об’єкта "req" ... методом проб та помилок ........ АЛЕ було б чудово це показати. Спасибі за вашу увагу.
granadaCoder

4
Точно так люди знають, що за допомогою MediaTypeHeaderValue поверне помилку при спробі встановити схему, як-от так; response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml; charset=utf-8");
МБак

3
Джон прокоментував рішення Карло, сказав: Response.Content.Headers, але ви використовуєте req.Content.Headers? тобто Запит проти відповіді?
joedotnot

52

Якщо ви не заперечуєте проти невеликої залежності бібліотеки, Flurl.Http [розкриття: я автор] робить це uber-просто. Його PostJsonAsyncметод забезпечує як серіалізацію вмісту, так і встановлення content-typeзаголовка, і ReceiveJsonдесеріалізує відповідь. Якщо acceptзаголовок необхідний, вам потрібно буде встановити це самостійно, але Flurl пропонує досить чистий спосіб зробити це також:

using Flurl.Http;

var result = await "http://example.com/"
    .WithHeader("Accept", "application/json")
    .PostJsonAsync(new { ... })
    .ReceiveJson<TResult>();

Flurl використовує HttpClient і Json.NET під кришкою, і це PCL, тому він буде працювати на різних платформах.

PM> Install-Package Flurl.Http

Як надіслати, якщо вміст застосовано application / x-www-form-urlencod?
Владо Панджич

2
Користувався ним. Досягнуто за <1 хвилину те, що затягувало мене довго. Дякуємо, що зберігаєте цю бібліотеку безкоштовно.
Najeeb

35

спробуйте використовувати TryAddWithoutValidation

  var client = new HttpClient();
  client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");

4
не працює, дає мені неправильне ім'я заголовка. Переконайтеся, що заголовки запитів використовуються з HttpRequestMessage, заголовки відповідей з HttpResponseMessage та заголовки вмісту з об’єктами HttpContent. '
Мартін Летц

3
Ті з вас, хто повідомляє про "працюючий" або "не працює", HttpClient є дуже неоднозначним об'єктом в наші дні. Повідомте про повне ім’я (пробіл) та збірку .dll, з якої він походить.
granadaCoder

Misused header nameпомилка підтверджується Dotnet ядром 2.2. Я повинен був використовувати @ відповідь carlosfigueira в stackoverflow.com/a/10679340/2084315 .
ps2goat

він працює для повноцінних .net робіт (4.7).
ZakiMa

28

.Net намагається змусити вас підкорятися певні стандарти, а саме , що Content-Typeзаголовок може бути визначений тільки за запитами , які мають зміст (наприклад POST, PUTі т.д.). Тому, як вказували інші, кращим способом встановлення Content-Typeзаголовка є HttpContent.Headers.ContentTypeвластивість.

Зважаючи на це, певні API (наприклад, LiquidFiles Api станом на 2016-12-19 рр.) Потребує встановлення Content-Typeзаголовка для GETзапиту. .Net не дозволить встановити цей заголовок на сам запит - навіть використовуючи TryAddWithoutValidation. Крім того, ви не можете вказати Contentзапит, навіть якщо він нульової довжини. Єдиний спосіб, яким я міг би обійти це, - вдатися до роздумів. Код (у випадку, якщо хтось інший потребує) є

var field = typeof(System.Net.Http.Headers.HttpRequestHeaders)
    .GetField("invalidHeaders", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static) 
  ?? typeof(System.Net.Http.Headers.HttpRequestHeaders) 
    .GetField("s_invalidHeaders", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
if (field != null)
{
  var invalidFields = (HashSet<string>)field.GetValue(null);
  invalidFields.Remove("Content-Type");
}
_client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "text/xml");

Редагувати:

Як зазначається в коментарях, це поле має різні назви в різних версіях DLL. У вихідному коді на GitHub це поле зараз названо s_invalidHeaders. Приклад змінено для врахування цього за пропозицією @David Thompson.


1
Це поле тепер s_invalidHeaders, тому використання наступного забезпечує сумісність: var field = typeof (System.Net.Http.Headers.HttpRequestHeaders) .GetField ("invalidHeaders", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Stags) ?? typeof (System.Net.Http.Headers.HttpRequestHeaders) .GetField ("s_invalidHeaders", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
Девід Томпсон

2
Дякую, дякую, дякую! Іноді моє кріплення висить відкритим, і, коли я потрапляю на програму Microsoft API, виходить дролл. Мені вдалося його почистити, побачивши ваш дуже простий пост. Не надто погано ..
Джерард ONeill

1
Мене бентежить те, як цей код може викликати описані вами катастрофічні помилки. Чи можете ви надати більш детальну інформацію про ваш випадок використання та помилки, які ви отримуєте?
erdomke

2
Ого. Ще більше диво, що методи Asp.net WebApi GET вимагають чіткого введення типу Content-Type = (
AlfeG

2
Холлі Молі, я не можу повірити, що мені доведеться вдатися до цього. З якого часу .NET Framework Devs йому потрібно тримати руку, що я можу додати до розділу заголовка Http? Огидні.
mmix

17

Деякі додаткові відомості про .NET Core (прочитавши повідомлення Erdomke про налаштування приватного поля для подання типу вмісту на запит, який не містить вмісту) ...

Після налагодження коду я не можу побачити приватне поле, яке слід задати за допомогою відображення - тому я подумав спробувати відновити проблему.

Я спробував такий код за допомогою .Net 4.6:

HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, @"myUrl");
httpRequest.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json");

HttpClient client = new HttpClient();
Task<HttpResponseMessage> response =  client.SendAsync(httpRequest);  //I know I should have used async/await here!
var result = response.Result;

І, як очікувалося, я отримую сукупний виняток із вмістом "Cannot send a content-body with this verb-type."

Однак якщо я те ж саме роблю з .NET Core (1.1) - я не отримую винятку. На мій запит із задоволенням відповів мій серверний додаток, і тип вмісту був підібраний.

Я був приємно здивований з цього приводу, і я сподіваюся, що це комусь допоможе!


1
Дякую, Джей - Не використовую ядро, і буде використовувати відповідь Ердомке. Я вдячний знанням того, що всі розумні шляхи були випробувані :).
Джерард ONeill

1
не працює .net 4 ({"Неможливо надіслати контент-тіло за допомогою цього типу дієслова."})
Tarek El-Mallah

3
@ TarekEl-Mallah Так - будь ласка, прочитайте коментарі у моїй відповіді. Вся суть мого допису полягала в тому, щоб проілюструвати, що він не працює в .NET 4, але він працює в .NET core (вони не те саме). Вам доведеться побачити відповідь Ердомеке на питання ОП, щоб мати можливість зламати його для роботи в .NET 4.
Jay

16

Дзвінок AddWithoutValidationзамість Add(див. Посилання MSDN ).

Як варіант, я здогадуюсь, що API, який ви використовуєте, вимагає цього лише для POST або PUT-запитів (не звичайних GET-запитів). У такому випадку, коли ви телефонуєте HttpClient.PostAsyncта передаєте номер HttpContent, встановіть це у Headersвластивості цього HttpContentоб'єкта.


не працює, дає мені неправильне ім'я заголовка. Переконайтеся, що заголовки запитів використовуються з HttpRequestMessage, заголовки відповідей з HttpResponseMessage та заголовки вмісту з об’єктами HttpContent. '
Мартін Летц

3
AddWithoutValidation не існує
KansaiRobot

14

Для тих, хто хвилювався charset

У мене був дуже особливий випадок, що постачальник послуг не прийняв схему, і вони відмовляються змінювати підструктуру, щоб дозволити це ... На жаль, HttpClient встановлював заголовок автоматично через StringContent, і незалежно від того, чи буде ви передано null або Encoding.UTF8, він завжди буде встановлювати шаблони ...

Сьогодні я був на краю, щоб змінити підсистему; переходячи від HttpClient до чогось іншого, що щось мені прийшло в голову ..., чому б не використати роздуми, щоб очистити "шафу"? ... І перш ніж я навіть спробую це, я подумав про спосіб, "можливо, я можу змінити його після ініціалізації", і це спрацювало.

Ось як можна встановити точний заголовок "application / json" без "; charset = utf-8".

var jsonRequest = JsonSerializeObject(req, options); // Custom function that parse object to string
var stringContent = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
stringContent.Headers.ContentType.CharSet = null;
return stringContent;

Примітка . nullЗначення в наступному не працює, і додає "; charset = utf-8"

return new StringContent(jsonRequest, null, "application/json");

EDIT

@DesertFoxAZ припускає, що також можна використовувати наступний код і працює добре. (не перевіряв сам)

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

1
stringContent.Headers.ContentType = новий MediaTypeHeaderValue ("додаток / json"); також працює
DesertFoxAZ

4
var content = new JsonContent();
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("charset", "utf-8"));
content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("IEEE754Compatible", "true"));

Це все, що вам потрібно.

Використовуючи Newtonsoft.Json, якщо вам потрібен вміст у вигляді рядка json

public class JsonContent : HttpContent
   {
    private readonly MemoryStream _stream = new MemoryStream();
    ~JsonContent()
    {
        _stream.Dispose();
    }

    public JsonContent(object value)
    {
        Headers.ContentType = new MediaTypeHeaderValue("application/json");
        using (var contexStream = new MemoryStream())
        using (var jw = new JsonTextWriter(new StreamWriter(contexStream)) { Formatting = Formatting.Indented })
        {
            var serializer = new JsonSerializer();
            serializer.Serialize(jw, value);
            jw.Flush();
            contexStream.Position = 0;
            contexStream.WriteTo(_stream);
        }
        _stream.Position = 0;

    }

    private JsonContent(string content)
    {
        Headers.ContentType = new MediaTypeHeaderValue("application/json");
        using (var contexStream = new MemoryStream())
        using (var sw = new StreamWriter(contexStream))
        {
            sw.Write(content);
            sw.Flush();
            contexStream.Position = 0;
            contexStream.WriteTo(_stream);
        }
        _stream.Position = 0;
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        return _stream.CopyToAsync(stream);
    }

    protected override bool TryComputeLength(out long length)
    {
        length = _stream.Length;
        return true;
    }

    public static HttpContent FromFile(string filepath)
    {
        var content = File.ReadAllText(filepath);
        return new JsonContent(content);
    }
    public string ToJsonString()
    {
        return Encoding.ASCII.GetString(_stream.GetBuffer(), 0, _stream.GetBuffer().Length).Trim();
    }
}

1
Чи можете ви дати невелике пояснення того, що це робить?
Алехандро

2
Перший рядок не відповідає CS0144: "Неможливо створити екземпляр абстрактного класу чи інтерфейсу" HttpContent ""
Рандалл Флагг,

1
а потімHttpMessageHandler handler = new WebRequestHandler(); HttpResponseMessage result; using (var client = (new HttpClient(handler, true))) { result = client.PostAsync(fullUri, content).Result; }
art24war

2

Гаразд, це не HTTPClient, але якщо ти можеш ним користуватися, WebClient досить простий:

using (var client = new System.Net.WebClient())
 {
    client.Headers.Add("Accept", "application/json");
    client.Headers.Add("Content-Type", "application/json; charset=utf-8");
    client.DownloadString(...);
 }

1

Ви можете скористатися цим, це буде працювати!

HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get,"URL");
msg.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json");

HttpResponseMessage response = await _httpClient.SendAsync(msg);
response.EnsureSuccessStatusCode();

string json = await response.Content.ReadAsStringAsync();

0

Я вважаю найпростішим і легким зрозуміти наступним чином:

async Task SendPostRequest()
{
    HttpClient client = new HttpClient();
    var requestContent = new StringContent(<content>);
    requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
    var response = await client.PostAsync(<url>, requestContent);
    var responseString = await response.Content.ReadAsStringAsync();
}
...

SendPostRequest().Wait();

0

Вам потрібно зробити так:

    HttpContent httpContent = new StringContent(@"{ the json string }");
    httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));                
    HttpResponseMessage message = client.PostAsync(@"{url}", httpContent).Result;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.