¿Cómo se configura el encabezado Content-Type para una solicitud HttpClient?

739

Estoy tratando de establecer el Content-Typeencabezado de un HttpClientobjeto como lo requiere una API a la que estoy llamando.

Intenté configurar lo Content-Typesiguiente a continuación:

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

Me permite agregar el Acceptencabezado pero cuando trato de agregarlo Content-Typearroja la siguiente excepción:

Nombre de encabezado mal utilizado. Asegúrese de utilizar encabezados de solicitud con HttpRequestMessageencabezados de respuesta HttpResponseMessagey encabezados de contenido con HttpContentobjetos.

¿Cómo puedo configurar el Content-Typeencabezado en una HttpClientsolicitud?

mynameiscoffey
fuente
Puede seguir cómo HttpWebRequest en .NET Core lo hace (usa HttpClient internamente), consulte github.com/dotnet/corefx/blob/master/src/System.Net.Requests/… Método " SendRequest "
jiping-s

Respuestas:

929

El tipo de contenido es un encabezado del contenido, no de la solicitud, por lo que esto está fallando. AddWithoutValidationsegún lo sugerido por Robert Levy puede funcionar, pero también puede establecer el tipo de contenido al crear el contenido de la solicitud en sí (tenga en cuenta que el fragmento de código agrega "application / json" en dos lugares: para los encabezados Aceptar y Tipo de contenido):

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
fuente
32
Alternativamente, Response.Content.Headersfuncionará la mayor parte del tiempo.
John Gietzen
44
@AshishJain La mayoría de las respuestas SO que he visto involucradas Response.Content.Headerspara la API web ASP.Net tampoco han funcionado, pero puede configurarlas fácilmente HttpContext.Current.Response.ContentTypesi lo necesita.
jerhewet
66
@jerhewet que utilicé de la siguiente manera, que funcionó para mí. var content = new StringContent (data, Encoding.UTF8, "application / json");
Ashish-BeJovial
22
Content-Type es una propiedad de un mensaje HTTP con carga útil; No tiene nada que ver con la solicitud frente a la respuesta.
Julian Reschke
66
Interesante. Intenté crear un nuevo StringContent con los tres parámetros y no funcionó. Luego, manualmente: request.Content.Headers.Remove ("Content-Type") y luego: request.Content.Headers.Add ("Content-Type", "application / query + json") y funcionó. Impar.
Bill Noel
163

Para aquellos que no vieron el comentario de Johns a la solución de Carlos ...

req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
archgl
fuente
Hizo la diferencia al descargar un pdf. Desde el teléfono intentó descargar un HTML. Después de convertir la extensión, el archivo se codificó normalmente.
Matteo Defanti
Tuve que tirar .ToString () al final, pero sí, esto funcionó para una implementación del servicio WCF.
John Meyer
2
Eventualmente descubriré qué tipo de objeto es "req" ... por prueba y error ........ PERO sería genial mostrar eso. Gracias por su consideración.
granadaCoder
44
Para que la gente sepa, el uso de MediaTypeHeaderValue devolverá un error si intenta establecer el juego de caracteres, así; response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/xml; charset=utf-8");
MBak
3
El comentario de Johns a la solución de Carlo dijo Response.Content.Headers, pero ¿está utilizando req.Content.Headers? es decir, solicitud vs respuesta?
joedotnot
52

Si no le importa una pequeña dependencia de la biblioteca, Flurl.Http [revelación: soy el autor] lo hace súper simple. Su PostJsonAsyncmétodo se encarga tanto de serializar el contenido como de configurar el content-typeencabezado, y ReceiveJsondeserializa la respuesta. Si acceptse requiere el encabezado, deberá configurarlo usted mismo, pero Flurl proporciona una forma bastante limpia de hacerlo también:

using Flurl.Http;

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

Flurl usa HttpClient y Json.NET bajo el capó, y es una PCL, por lo que funcionará en una variedad de plataformas.

PM> Install-Package Flurl.Http
Todd Menier
fuente
¿Cómo enviar si el contenido es application / x-www-form-urlencoded?
Vlado Pandžić
2
Lo usé Logré en <1 minuto lo que me estaba llevando mucho tiempo descubrir. Gracias por mantener esta biblioteca gratis.
Najeeb
35

intenta usar TryAddWithoutValidation

  var client = new HttpClient();
  client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
SharpCoder
fuente
44
no funciona me da un 'Nombre de encabezado mal utilizado. Asegúrese de que los encabezados de solicitud se utilicen con HttpRequestMessage, los encabezados de respuesta con HttpResponseMessage y los encabezados de contenido con objetos HttpContent.
Martin Lietz
3
Aquellos de ustedes que informan "trabajando" o "no trabajando", HttpClient es un objeto muy ambiguo en estos días. Informe el nombre completo (espacio) y el ensamblado .dll del que proviene.
granadaCoder
el Misused header nameerror se confirma con dotnet core 2.2. Tuve que usar la respuesta de @ carlosfigueira stackoverflow.com/a/10679340/2084315 .
ps2goat
funciona para trabajos completos de .net (4.7).
ZakiMa
28

.NET intenta obligarlo a obedecer ciertas normas, a saber, que la Content-Typecabecera sólo se puede especificar en las solicitudes que tienen contenido (por ejemplo POST, PUT, etc.). Por lo tanto, como otros han indicado, la forma preferida de establecer el Content-Typeencabezado es a través de la HttpContent.Headers.ContentTypepropiedad.

Dicho esto, ciertas API (como la API LiquidFiles , a partir del 2016-12-19) requieren configurar el Content-Typeencabezado para una GETsolicitud. .Net no permitirá configurar este encabezado en la solicitud en sí, incluso utilizando TryAddWithoutValidation. Además, no puede especificar un Contentpara la solicitud, incluso si es de longitud cero. La única forma en que podía evitar esto era recurrir a la reflexión. El código (en caso de que alguien más lo necesite) es

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

Editar:

Como se señaló en los comentarios, este campo tiene diferentes nombres en diferentes versiones de la dll. En el código fuente de GitHub , el campo se llama actualmente s_invalidHeaders. El ejemplo se ha modificado para dar cuenta de esto según la sugerencia de @David Thompson.

erdomke
fuente
1
Este campo ahora es s_invalidHeaders, por lo que usar lo siguiente garantiza la compatibilidad: 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
¡Gracias, gracias, gracias! A veces, mi montaje se cuelga y sale baba cuando me topa con un error de API de Microsoft. Pude limpiarlo después de ver tu publicación muy sencilla. No demasiado mal ..
Gerard O'Neill
1
Estoy confundido sobre cómo este código podría causar los errores catastróficos que usted describe. ¿Puede proporcionar más detalles sobre su caso de uso y los errores que está recibiendo?
erdomke
2
Guau. Aún más sorprendente, que los métodos GET de Asp.net WebApi requieren que Content-Type se especifique explícitamente = (
AlfeG
2
Holly Molly, no puedo creer que tenga que recurrir a esto. ¿Desde cuándo los desarrolladores de .NET Framework necesitan sostener mi mano en lo que puedo agregar a la sección de encabezado Http? Abominable.
mmix
17

Alguna información adicional sobre .NET Core (después de leer la publicación de erdomke sobre la configuración de un campo privado para suministrar el tipo de contenido en una solicitud que no tiene contenido) ...

Después de depurar mi código, no puedo ver el campo privado para establecer mediante reflexión, así que pensé en intentar recrear el problema.

He intentado el siguiente código usando .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;

Y, como se esperaba, obtengo una excepción agregada con el contenido "Cannot send a content-body with this verb-type."

Sin embargo, si hago lo mismo con .NET Core (1.1), no obtengo una excepción. Mi solicitud fue respondida felizmente por mi aplicación de servidor, y el tipo de contenido fue recogido.

Me sorprendió gratamente eso, ¡y espero que ayude a alguien!

Arrendajo
fuente
1
Gracias, Jay. No usa core, y usaré la respuesta de erdomke. Agradezco saber que se han probado todos los caminos razonables :).
Gerard ONeill
1
no funciona .net 4 ({"No se puede enviar un cuerpo de contenido con este tipo de verbo".)
Tarek El-Mallah
3
@ TarekEl-Mallah Sí, por favor lea los comentarios en mi respuesta. El objetivo de mi publicación fue ilustrar que no funciona en .NET 4, pero sí funciona en .NET core (no son lo mismo). Tendrá que ver la respuesta de erdomeke a la pregunta del OP para poder hackearla para que funcione en .NET 4.
Jay
16

Llame en AddWithoutValidationlugar de Add(vea este enlace de MSDN ).

Alternativamente, supongo que la API que está utilizando realmente solo requiere esto para las solicitudes POST o PUT (no las solicitudes GET ordinarias). En ese caso, cuando llame HttpClient.PostAsyncy pase un HttpContent, establezca esto en la Headerspropiedad de ese HttpContentobjeto.

Robert Levy
fuente
no funciona me da un 'Nombre de encabezado mal utilizado. Asegúrese de que los encabezados de solicitud se utilicen con HttpRequestMessage, los encabezados de respuesta con HttpResponseMessage y los encabezados de contenido con objetos HttpContent.
Martin Lietz
3
AddWithoutValidation no existe
KansaiRobot
14

Para aquellos que tuvieron problemas con charset

Tuve un caso muy especial de que el proveedor de servicios no aceptó el juego de caracteres, y se niegan a cambiar la subestructura para permitirlo ... Desafortunadamente, HttpClient estaba configurando el encabezado automáticamente a través de StringContent, y no importa si pasa nulo o Encoding.UTF8, siempre establecerá el juego de caracteres ...

Hoy estaba al borde para cambiar el subsistema; pasando de HttpClient a cualquier otra cosa, ese algo vino a mi mente ..., ¿por qué no usar la reflexión para vaciar el "juego de caracteres"? ... Y antes de intentarlo, pensé en una forma, "tal vez pueda cambiarlo después de la inicialización", y eso funcionó.

Así es como puede configurar el encabezado exacto "application / json" sin "; 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;

Nota: El nullvalor siguiente no funcionará, y agregue "; charset = utf-8"

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

EDITAR

@DesertFoxAZ sugiere que también se puede usar el siguiente código y funciona bien. (no lo probé yo mismo)

stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
hombre muerto
fuente
1
stringContent.Headers.ContentType = new MediaTypeHeaderValue ("aplicación / json"); también funciona
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"));

Es todo lo que necesitas.

Con el uso de Newtonsoft.Json, si necesita un contenido como cadena 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
fuente
1
¿Puedes dar una pequeña explicación de lo que hace?
Alejandro
2
La primera línea falla con CS0144: "No se puede crear una instancia de la clase abstracta o interfaz 'HttpContent'"
Randall Flagg
1
y luegoHttpMessageHandler handler = new WebRequestHandler(); HttpResponseMessage result; using (var client = (new HttpClient(handler, true))) { result = client.PostAsync(fullUri, content).Result; }
art24war el
2

Ok, no es HTTPClient pero si puedes usarlo, WebClient es bastante fácil:

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
fuente
1

¡Puedes usar esto, será trabajo!

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
fuente
0

Lo encuentro más simple y fácil de entender de la siguiente manera:

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
fuente
0

Necesitas hacerlo así:

    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;
usuario890332
fuente