¿Cómo extraer el valor del encabezado personalizado en el controlador de mensajes de la API web?


150

Actualmente tengo un controlador de mensajes en mi servicio de API web que anula 'SendAsync' de la siguiente manera:

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
  //implementation
}

Dentro de este código, necesito inspeccionar un valor de encabezado de solicitud agregado personalizado llamado MyCustomID. El problema es cuando hago lo siguiente:

if (request.Headers.Contains("MyCustomID"))  //OK
    var id = request.Headers["MyCustomID"];  //build error - not OK

... recibo el siguiente mensaje de error:

No se puede aplicar la indexación con [] a una expresión de tipo 'System.Net.Http.Headers.HttpRequestHeaders'

¿Cómo puedo acceder a un único encabezado de solicitud personalizada a través de la instancia HttpRequestMessage( Documentación de MSDN ) que se pasa a este método anulado?


¿Qué pasa si estás usando request.Headers.Get("MyCustomID");?
udidu

2
No hay ningún Get' on the tipo HttpRequestHeaders. Aparece el mensaje: "No se puede resolver el símbolo 'Obtener'".
atconway

Respuestas:


252

Intenta algo como esto:

IEnumerable<string> headerValues = request.Headers.GetValues("MyCustomID");
var id = headerValues.FirstOrDefault();

También hay un método TryGetValues ​​en los encabezados que puede usar si no siempre se garantiza que tenga acceso al encabezado.


26
La comprobación nula para GetValues ​​no sirve ningún valor ya que nunca devolverá nulo. Si el encabezado no existe, obtendrá una InvalidOperationException. Debe usar TryGetHeaders si es posible que el encabezado no exista en la solicitud y verificar si hay una respuesta falsa O intentar / atrapar la llamada GetValues ​​(no recomendado).
Drew Marsh

44
@Drew request.Headers.Single (h => h.Key == "Autorización"); ¡Mucho menos código haciendo lo mismo!
Elisabeth

17
¿Por qué no solo?var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Gaui

3
@SaeedNeamati porque los valores de encabezado no son uno a uno. Puede tener Some-Header: oney luego Some-Header: twoen la misma solicitud. Algunos idiomas descartan en silencio "uno", pero eso es incorrecto. Está en el RFC pero soy demasiado vago para encontrarlo ahora.
Cory Mawhorter

1
El punto de Saeed es válido, la usabilidad es importante y el caso de uso más común aquí es recuperar un valor para un encabezado de solicitud. Todavía puede tener una operación GetValues ​​para recuperar múltiples valores para un encabezado de solicitud (que las personas usarán), pero el 99% del tiempo querrán recuperar un valor para un encabezado de solicitud específico, y eso debería ser uno transatlántico.
Justin

39

La línea de abajo throws exceptionsi la clave no existe.

IEnumerable<string> headerValues = request.Headers.GetValues("MyCustomID");

Solución alterna :

Incluir System.Linq;

IEnumerable<string> headerValues;
var userId = string.Empty;

     if (request.Headers.TryGetValues("MyCustomID", out headerValues))
     {
         userId = headerValues.FirstOrDefault();
     }           

17

Para ampliar la respuesta de Youssef, escribí este método basado en las preocupaciones de Drew sobre el encabezado no existente, porque me encontré con esta situación durante las pruebas unitarias.

private T GetFirstHeaderValueOrDefault<T>(string headerKey, 
   Func<HttpRequestMessage, string> defaultValue, 
   Func<string,T> valueTransform)
    {
        IEnumerable<string> headerValues;
        HttpRequestMessage message = Request ?? new HttpRequestMessage();
        if (!message.Headers.TryGetValues(headerKey, out headerValues))
            return valueTransform(defaultValue(message));
        string firstHeaderValue = headerValues.FirstOrDefault() ?? defaultValue(message);
        return valueTransform(firstHeaderValue);
    }

Aquí hay un ejemplo de uso:

GetFirstHeaderValueOrDefault("X-MyGuid", h => Guid.NewGuid().ToString(), Guid.Parse);

También eche un vistazo a la respuesta de @ doguhan-uluca para una solución más general.


1
Funcy Actionson construcciones genéricas de firma de delegado integradas en .NET 3.5 y superiores. Me encantaría discutir preguntas específicas sobre el método, pero recomendaría aprender sobre ellas primero.
neontapir

1
@neontapir (y otros), el segundo parámetro se usa para proporcionar un valor predeterminado si no se encuentra la clave. El tercer parámetro se utiliza para 'transformar' el valor de retorno para que sea del tipo deseado, que también especifica el tipo que se devolverá. Según el ejemplo, si no se encuentra 'X-MyGuid', el parámetro 2 lambda básicamente proporciona un guid predeterminado como una cadena (como se habría recuperado del encabezado) y el tercer parámetro Guid.Parse traducirá el valor de cadena encontrado o predeterminado en un GUID.
Mikee

@neontapir ¿de dónde viene la solicitud en esta función? (y si es nula la forma en que un nuevo HttpRequestMessage () tienen ninguna cabecera no tiene sentido simplemente devolver el valor por defecto si la solicitud es nulo??
Mendel

Han pasado dos años, pero si recuerdo, se inicia una nueva HttpRequestMessagecon una colección de encabezados vacía, que no es nula. Esta función termina devolviendo el valor predeterminado si la solicitud es nula.
neontapir

@mendel, neontapir He intentado usar el fragmento anterior y creo que la "Solicitud" en la línea 2 del cuerpo del método debe ser un campo privado en la clase que contiene el método o pasar como parámetro (de tipo HttpRequestMessage) a el método
Sudhanshu Mishra

12

Cree un nuevo método: ' Devuelve un valor de encabezado HTTP individual ' y llame a este método con un valor clave cada vez que necesite acceder a varios valores clave desde HttpRequestMessage.

public static string GetHeader(this HttpRequestMessage request, string key)
        {
            IEnumerable<string> keys = null;
            if (!request.Headers.TryGetValues(key, out keys))
                return null;

            return keys.First();
        }

¿Qué sucede si MyCustomID no es parte de la solicitud? Devuelve una excepción nula.
Prasad Kanaparthi

10

Para ampliar aún más la solución de @ neontapir, aquí hay una solución más genérica que puede aplicarse a HttpRequestMessage o HttpResponseMessage por igual y no requiere expresiones o funciones codificadas a mano.

using System.Net.Http;
using System.Collections.Generic;
using System.Linq;

public static class HttpResponseMessageExtensions
{
    public static T GetFirstHeaderValueOrDefault<T>(
        this HttpResponseMessage response,
        string headerKey)
    {
        var toReturn = default(T);

        IEnumerable<string> headerValues;

        if (response.Content.Headers.TryGetValues(headerKey, out headerValues))
        {
            var valueString = headerValues.FirstOrDefault();
            if (valueString != null)
            {
                return (T)Convert.ChangeType(valueString, typeof(T));
            }
        }

        return toReturn;
    }
}

Uso de la muestra:

var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");

Se ve muy bien, pero GetFirstHeaderValueOrDefaulttiene dos parámetros, por lo que se queja de la falta de parámetros cuando se llama como muestra de uso var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");¿Me estoy perdiendo algo?
Jeb50

Se agregó la nueva clase estática, se reemplazó la Respuesta a solicitud. Llamado desde el controlador API, como se var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault<int>("productID");obtuvo No hay ningún argumento dado que corresponda al parámetro formal requerido 'headerKey' de 'HttpRequestMessageExtension.GetFirstHeaderValueOrDefault <T> (HttpRequestMessage, string)'
Jeb50

@ Jeb50 ¿estás declarando using HttpResponseMessageExtensionssobre el archivo que estás intentando usar esta extensión?
Doguhan Uluca

4

Para ASP.Net Core hay una solución fácil si desea usar el parámetro directamente en el método del controlador: use la anotación [FromHeader].

        public JsonResult SendAsync([FromHeader] string myParam)
        {
        if(myParam == null)  //Param not set in request header
        {
           return null;
        }
        return doSomething();
    }   

Información adicional: en mi caso, "myParam" tenía que ser una cadena, int siempre era 0.


4

Para ASP.NET, puede obtener el encabezado directamente del parámetro en el método del controlador utilizando esta biblioteca / paquete simple . Proporciona un [FromHeader]atributo como el que tiene en ASP.NET Core :). Por ejemplo:

    ...
    using RazHeaderAttribute.Attributes;

    [Route("api/{controller}")]
    public class RandomController : ApiController 
    {
        ...
        // GET api/random
        [HttpGet]
        public IEnumerable<string> Get([FromHeader("pages")] int page, [FromHeader] string rows)
        {
            // Print in the debug window to be sure our bound stuff are passed :)
            Debug.WriteLine($"Rows {rows}, Page {page}");
            ...
        }
    }

4

Solución de una línea

var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();

¿Qué sucede si MyCustomID no es parte de la solicitud? Devuelve una excepción nula.
Prasad Kanaparthi

1
@PrasadKanaparthi, entonces debería usar otra respuesta como stackoverflow.com/a/25640256/4275342 . Usted ve que no hay ningún cheque nulo, entonces, ¿qué requestes null? También es posible. ¿O qué MyCustomIDpasa si una cadena vacía o no es igual a foo? Depende del contexto, por lo que esta respuesta solo describe la forma y toda la validación y lógica de negocios que necesita agregar por su cuenta
Roman Marusyk

¿No está de acuerdo con que el código funciona y puede devolver el valor del encabezado?
Roman Marusyk

1
Funciona bien ... si "MyCustomID" es parte de la solicitud de solicitud. Sí, toda la validación debe ser atendida
Prasad Kanaparthi

4
request.Headers.FirstOrDefault( x => x.Key == "MyCustomID" ).Value.FirstOrDefault()

variante moderna :)


¿Qué sucede si MyCustomID no es parte de la solicitud? Devuelve una excepción nula.
Prasad Kanaparthi
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.