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

741

Я пытаюсь установить 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запросе?

mynameiscoffey
источник
Вы можете проследить , как HttpWebRequest в .NET Ядра делает это (он использует HttpClient внутренне), см github.com/dotnet/corefx/blob/master/src/System.Net.Requests/... метод «SendRequest»
Цзипина-s

Ответы:

930

Тип содержимого - это заголовок содержимого, а не запроса, поэтому это не удается. 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);
      });
carlosfigueira
источник
32
В качестве альтернативы, Response.Content.Headersбудет работать большую часть времени.
Джон Гитцен
4
@AshishJain Большинство ответов SO, которые я видел Response.Content.Headersв отношении ASP.Net Web API, также не сработали, но вы можете легко установить его, используя, HttpContext.Current.Response.ContentTypeесли вам нужно.
Jerhewet
6
@jerhewet я использовал следующим образом, который работал для меня. var content = new StringContent (data, Encoding.UTF8, "application / json");
Ашиш-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");
archgl
источник
Это имело значение, загружая PDF. С телефона он пытался скачать HTML. После преобразования расширения файл был нормально закодирован.
Маттео Дефанти
Мне пришлось бросить .ToString () в конце, но да, это работало для реализации службы WCF.
Джон Мейер
2
В конце концов я выясню, что такое тип объекта "req" ... методом проб и ошибок ........ НО было бы здорово показать это. Спасибо за ваше внимание.
granadaCoder
4
Просто, как люди знают, использование MediaTypeHeaderValue вернет ошибку при попытке установить кодировку, вот так; response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml; charset=utf-8");
MBak
3
Джонс в комментарии к решению Карло сказал Response.Content.Headers, но вы используете req.Content.Headers? т.е. запрос против ответа?
joedotnot
53

Если вы не возражаете против небольшой зависимости от библиотеки, Flurl.Http [раскрытие: я автор] делает это очень просто. Его 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-urlencoded?
Владо Панджич,
2
Использовал это. Достигнуто в течение <1 минуты, что заняло у меня много времени, чтобы выяснить. Спасибо за сохранение этой библиотеки бесплатно.
Наджиб
35

попробуйте использовать TryAddWithoutValidation

  var client = new HttpClient();
  client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
SharpCoder
источник
4
не работает дает мне 'Неправильное имя заголовка. Убедитесь, что заголовки запросов используются с HttpRequestMessage, заголовки ответов с HttpResponseMessage и заголовки содержимого с объектами HttpContent. '
Мартин Лиц
3
Те из вас, кто сообщает, что «работает» или «не работает», HttpClient в наши дни является весьма неоднозначным объектом. Пожалуйста, сообщите полное имя (пробел) и сборку .dll, из которой он исходит.
granadaCoder
Misused header nameошибка подтверждается Dotnet ядром 2.2. Мне пришлось использовать ответ @ carlosfigueira stackoverflow.com/a/10679340/2084315 .
ps2goat
это работает для полной работы .net (4.7).
Закима
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.

erdomke
источник
1
Это поле теперь s_invalidHeaders, поэтому использование следующего обеспечивает совместимость: 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);
Дэвид Томпсон
2
Спасибо Спасибо спасибо! Иногда мое монтирование остается открытым, и когда я сталкиваюсь с ошибкой Microsoft API, появляется слюна. Я смог очистить его после просмотра вашего очень простого поста. Не так уж плохо ..
Жерар ONeill
1
Я запутался в том, как этот код может вызвать катастрофические ошибки, которые вы описываете. Можете ли вы предоставить более подробную информацию о вашем случае использования и об ошибках, которые вы получаете?
эрдомке
2
Ух ты. Еще более удивительно, что методы GET Asp.net WebApi требуют, чтобы Content-Type был явно указан = (
AlfeG
2
Холли Молли, я не могу поверить, что должна прибегнуть к этому. С каких это пор разработчикам .NET Framework нужно держать меня за руку, что я могу добавить в раздел заголовка 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 ({"Невозможно отправить тело контента с этим типом глагола."})
Тарек Эль-Малла,
3
@ TarekEl-Mallah Да - пожалуйста, прочитайте комментарии в моем ответе. Весь смысл моего поста состоял в том, чтобы проиллюстрировать, что он не работает в .NET 4, но работает в ядре .NET (это не одно и то же). Вам нужно будет увидеть ответ erdomeke на вопрос ОП, чтобы иметь возможность взломать его, чтобы работать в .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 значение в дальнейшем не будет работать, и Append «; кодировка = UTF-8»

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

РЕДАКТИРОВАТЬ

@DesertFoxAZ предлагает использовать следующий код и работает нормально. (сам не проверял)

stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
deadManN
источник
1
stringContent.Headers.ContentType = new MediaTypeHeaderValue ("application / 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();
    }
}
art24war
источник
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();
Kumaran
источник
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;
user890332
источник