Як встановити заголовок 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.