Comment définissez-vous l'en-tête Content-Type pour une demande HttpClient?

740

J'essaie de définir l'en- Content-Typetête d'un HttpClientobjet comme requis par une API que j'appelle.

J'ai essayé de définir Content-Typecomme ci-dessous:

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");
    // ...
}

Cela me permet d'ajouter l'en- Accepttête mais lorsque j'essaye de l'ajouter, Content-Typeil lance l'exception suivante:

Nom d'en-tête mal utilisé. Assurez-vous que les en-têtes de demande sont utilisés avec HttpRequestMessage, les en-têtes de réponse avec HttpResponseMessageet les en-têtes de contenu avec les HttpContentobjets.

Comment puis-je définir l'en- Content-Typetête dans une HttpClientdemande?

mynameiscoffey
la source
Vous pouvez suivre la façon dont HttpWebRequest dans .NET Core le fait (il utilise HttpClient en interne), voir github.com/dotnet/corefx/blob/master/src/System.Net.Requests/… "SendRequest" méthode
jiping-s

Réponses:

930

Le type de contenu est un en-tête du contenu, pas de la demande, c'est pourquoi cela échoue. AddWithoutValidationcomme suggéré par Robert Levy peut fonctionner, mais vous pouvez également définir le type de contenu lors de la création du contenu de la demande lui-même (notez que l'extrait de code ajoute "application / json" à deux endroits - pour les en-têtes Accept et 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
la source
32
Alternativement, Response.Content.Headersfonctionnera la plupart du temps.
John Gietzen
4
@AshishJain La plupart des réponses SO que j'ai vues impliquer Response.Content.Headerspour l'API Web ASP.Net n'ont pas fonctionné non plus, mais vous pouvez facilement le configurer en utilisant HttpContext.Current.Response.ContentTypesi vous en avez besoin.
jerhewet
6
@ jerhewet j'ai utilisé de la manière suivante qui a fonctionné pour moi. var content = new StringContent (data, Encoding.UTF8, "application / json");
Ashish-BeJovial
22
Content-Type est une propriété d'un message HTTP avec charge utile; cela n'a rien à voir avec la demande vs la réponse.
Julian Reschke
6
Intéressant. J'ai essayé de créer un nouveau StringContent avec les trois paramètres et cela n'a pas fonctionné. J'ai ensuite manuellement: request.Content.Headers.Remove ("Content-Type") puis: request.Content.Headers.Add ("Content-Type", "application / query + json") et cela a fonctionné. Impair.
Bill Noel du
163

Pour ceux qui n'ont pas vu le commentaire de Johns sur la solution carlos ...

req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
archgl
la source
Cela a fait la différence de télécharger un pdf. Depuis le téléphone, il a essayé de télécharger un code HTML. Après avoir converti l'extension, le fichier était normalement encodé.
Matteo Defanti
J'ai dû lancer .ToString () à la fin, mais oui, cela a fonctionné pour une implémentation de service WCF.
John Meyer
2
Je finirai par comprendre quel type d'objet "req" est ... par essais et erreurs ....... MAIS ce serait formidable de le montrer. Merci pour votre considération.
granadaCoder
4
Pour que les gens le sachent, l'utilisation de MediaTypeHeaderValue renverra une erreur si vous essayez de définir le jeu de caractères, comme ceci; response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml; charset=utf-8");
MBak
3
Johns a commenté la solution de Carlo, a déclaré Response.Content.Headers, mais vous utilisez req.Content.Headers? ie demande vs réponse?
joedotnot
52

Si cela ne vous dérange pas une petite dépendance de bibliothèque, Flurl.Http [divulgation: je suis l'auteur] rend cela ultra simple. Sa PostJsonAsyncméthode prend en charge à la fois la sérialisation du contenu et la définition de l'en- content-typetête, et ReceiveJsondésérialise la réponse. Si l'en- accepttête est requis, vous devrez le définir vous-même, mais Flurl fournit également un moyen assez propre de le faire:

using Flurl.Http;

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

Flurl utilise HttpClient et Json.NET sous le capot, et c'est un PCL, donc cela fonctionnera sur une variété de plates-formes.

PM> Install-Package Flurl.Http
Todd Menier
la source
Comment envoyer si le contenu est codé application / x-www-form-urlencod?
Vlado Pandžić
2
Je l'ai utilisé. Atteint en <1 minute ce qui me prenait beaucoup de temps à comprendre. Merci d'avoir gardé cette bibliothèque gratuite.
Najeeb
35

essayez d'utiliser TryAddWithoutValidation

  var client = new HttpClient();
  client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
SharpCoder
la source
4
ne fonctionne pas me donne un nom d'en-tête mal utilisé. Assurez-vous que les en-têtes de demande sont utilisés avec HttpRequestMessage, les en-têtes de réponse avec HttpResponseMessage et les en-têtes de contenu avec les objets HttpContent. '
Martin Lietz
3
Ceux d'entre vous qui déclarent "travailler" ou "ne pas travailler", HttpClient est un objet très ambigu de nos jours. Veuillez signaler le nom complet (espace) et l'assembly .dll dont il provient.
granadaCoder
l' Misused header nameerreur est confirmée avec dotnet core 2.2. J'ai dû utiliser la réponse stackoverflow.com/a/10679340/2084315 de @ carlosfigueira .
ps2goat
cela fonctionne pour les travaux .net complets (4.7).
ZakiMa
28

.Net essaie de vous forcer à obéir à certaines normes, à savoir que l' en- Content-Typetête ne peut être spécifié sur les demandes qui ont un contenu (par exemple POST, PUTetc.). Par conséquent, comme d'autres l'ont indiqué, la meilleure façon de définir l'en- Content-Typetête consiste à utiliser la HttpContent.Headers.ContentTypepropriété.

Cela dit, certaines API (telles que l' API LiquidFiles , à partir du 2016-12-19) nécessitent de définir l'en- Content-Typetête d'une GETdemande. .Net ne permettra pas de définir cet en-tête sur la demande elle-même - même en utilisant TryAddWithoutValidation. De plus, vous ne pouvez pas spécifier un Contentpour la demande - même s'il est de longueur nulle. La seule façon dont je pouvais sembler contourner cela était de recourir à la réflexion. Le code (au cas où quelqu'un d'autre en aurait besoin) est

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

Éditer:

Comme indiqué dans les commentaires, ce champ a des noms différents dans différentes versions de la DLL. Dans le code source sur GitHub , le champ est actuellement nommé s_invalidHeaders. L'exemple a été modifié pour tenir compte de cela selon la suggestion de @David Thompson.

erdomke
la source
1
Ce champ est maintenant s_invalidHeaders, donc l'utilisation des éléments suivants garantit la compatibilité: 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);
David Thompson
2
Merci merci merci! Parfois, ma monture se bloque et bave sort quand je frappe sur un échec de l'API Microsoft .. J'ai pu le nettoyer après avoir vu votre message très simple. Pas trop mal ..
Gerard ONeill
1
Je suis confus sur la façon dont ce code provoquerait les erreurs catastrophiques que vous décrivez. Pouvez-vous fournir plus de détails sur votre cas d'utilisation et les erreurs que vous recevez?
erdomke
2
Sensationnel. Encore plus wow, les méthodes Asp.net WebApi GET nécessitent que Content-Type soit explicitement spécifié = (
AlfeG
2
Holly molly, je ne peux pas croire que je dois y recourir. Depuis quand le framework .NET est-il nécessaire de tenir ma main dans ce que je peux ajouter à la section d'en-tête Http? Abominable.
mmix
17

Quelques informations supplémentaires sur .NET Core (après avoir lu le post d'erdomke sur la définition d'un champ privé pour fournir le type de contenu sur une demande qui n'a pas de contenu) ...

Après avoir débogué mon code, je ne vois pas le champ privé à définir via la réflexion - j'ai donc pensé essayer de recréer le problème.

J'ai essayé le code suivant en utilisant .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;

Et, comme prévu, j'obtiens une exception globale avec le contenu "Cannot send a content-body with this verb-type."

Cependant, si je fais la même chose avec .NET Core (1.1) - je n'ai pas d'exception.Ma demande a été très heureusement répondu par mon application serveur, et le type de contenu a été repris.

J'en ai été agréablement surpris et j'espère que cela aide quelqu'un!

Geai
la source
1
Merci, Jay - N'utilise pas le noyau et utilisera la réponse d'erdomke. J'apprécie de savoir que toutes les voies raisonnables ont été essayées :).
Gerard ONeill
1
ne fonctionne pas .net 4 ({"Impossible d'envoyer un corps de contenu avec ce type de verbe."})
Tarek El-Mallah
3
@ TarekEl-Mallah Oui - veuillez lire les commentaires dans ma réponse. Le but de mon article était d'illustrer que cela ne fonctionne pas dans .NET 4, mais cela fonctionne dans .NET core (ce n'est pas la même chose). Vous devrez voir la réponse d'Erdomeke à la question du PO pour pouvoir le pirater pour qu'il fonctionne dans .NET 4.
Jay
16

Appelez AddWithoutValidationau lieu de Add(voir ce lien MSDN ).

Alternativement, je suppose que l'API que vous utilisez ne l'exige vraiment que pour les requêtes POST ou PUT (pas les requêtes GET ordinaires). Dans ce cas, lorsque vous appelez HttpClient.PostAsyncet transmettez un HttpContent, définissez-le sur la Headerspropriété de cet HttpContentobjet.

Robert Levy
la source
ne fonctionne pas me donne un nom d'en-tête mal utilisé. Assurez-vous que les en-têtes de demande sont utilisés avec HttpRequestMessage, les en-têtes de réponse avec HttpResponseMessage et les en-têtes de contenu avec les objets HttpContent. '
Martin Lietz
3
AddWithoutValidation n'existe pas
KansaiRobot
14

Pour ceux qui ont des problèmes avec charset

J'ai eu un cas très spécial que le fournisseur de services n'a pas accepté charset, et ils refusent de changer la sous-structure pour le permettre ... Malheureusement, HttpClient définissait automatiquement l'en-tête via StringContent, et peu importe si vous passez null ou Encoding.UTF8, il définira toujours le jeu de caractères ...

Aujourd'hui, j'étais sur le point de changer le sous-système; passer de HttpClient à autre chose, que quelque chose m'est venu à l'esprit ..., pourquoi ne pas utiliser la réflexion pour vider le "charset"? ... Et avant même de l'essayer, j'ai pensé à un moyen, "peut-être que je peux le changer après l'initialisation", et cela a fonctionné.

Voici comment définir l'en-tête "application / json" exact sans "; 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;

Remarque: La nullvaleur de ce qui suit ne fonctionnera pas et ajoutera "; charset = utf-8"

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

ÉDITER

@DesertFoxAZ suggère que le code suivant peut également être utilisé et fonctionne correctement. (je ne l'ai pas testé moi-même)

stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
deadManN
la source
1
stringContent.Headers.ContentType = new MediaTypeHeaderValue ("application / json"); fonctionne également
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"));

C'est tout ce dont vous avez besoin.

En utilisant Newtonsoft.Json, si vous avez besoin d'un contenu en tant que chaîne 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
la source
1
Pouvez-vous donner une petite explication de ce qu'il fait?
Alejandro
2
La première ligne échoue avec CS0144: "Impossible de créer une instance de la classe abstraite ou de l'interface 'HttpContent'"
Randall Flagg
1
puisHttpMessageHandler handler = new WebRequestHandler(); HttpResponseMessage result; using (var client = (new HttpClient(handler, true))) { result = client.PostAsync(fullUri, content).Result; }
art24war
2

Ok, ce n'est pas HTTPClient mais si vous pouvez l'utiliser, WebClient est assez simple:

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(...);
 }
Ziba Leah
la source
1

Vous pouvez l'utiliser, ce sera du travail!

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
la source
0

Je le trouve le plus simple et le plus facile à comprendre de la manière suivante:

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();
Anshuman Goel
la source
0

Vous devez le faire comme ceci:

    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
la source