Como extrair o valor do cabeçalho personalizado no manipulador de mensagens da API da Web?

150

Atualmente, tenho um manipulador de mensagens no meu serviço de API da Web que substitui 'SendAsync' da seguinte maneira:

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

Nesse código, preciso inspecionar um valor de cabeçalho de solicitação adicionado personalizado chamado MyCustomID. O problema é quando eu faço o seguinte:

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

...Eu recebi a seguinte mensagem de erro:

Não é possível aplicar a indexação com [] a uma expressão do tipo 'System.Net.Http.Headers.HttpRequestHeaders'

Como acessar um único cabeçalho de solicitação personalizada por meio da instância HttpRequestMessage( Documentação do MSDN ) passada para esse método substituído?

atconway
fonte
o que acontece se você estiver usando request.Headers.Get("MyCustomID");?
udidu
2
Não há Get' on the tipo HttpRequestHeaders. A mensagem: "Não é possível resolver o símbolo 'Get'" é produzida.
Atletway

Respostas:

252

Tente algo como isto:

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

Há também um método TryGetValues ​​nos cabeçalhos que você pode usar se nem sempre garantir o acesso ao cabeçalho.

Youssef Moussaoui
fonte
26
A verificação nula para GetValues ​​não serve para nenhum valor, pois nunca retornará nulo. Se o cabeçalho não existir, você receberá uma InvalidOperationException. Você precisa usar o TryGetHeaders se for possível que o cabeçalho não exista na solicitação e verificar se há uma resposta falsa OU tentar capturar a chamada GetValues ​​(não recomendado).
Drew Marsh
4
@Drew request.Headers.Single (h => h.Key == "Autorização"); Muito menos código fazendo o mesmo!
Elisabeth
17
Por que não apenasvar id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Gaui
3
@SaeedNeamati porque os valores do cabeçalho não são individuais. Você pode ter Some-Header: onee, Some-Header: twoem seguida, na mesma solicitação. Alguns idiomas descartam silenciosamente "um", mas isso está incorreto. Está no RFC, mas estou com preguiça de encontrá-lo agora.
Cory Mawhorter
1
O ponto de Saeed é válido, a usabilidade é importante e o caso de uso mais comum aqui é recuperar um valor para um cabeçalho de solicitação. Você ainda pode ter uma operação GetValues ​​para recuperar vários valores para um cabeçalho de solicitação (que as pessoas raramente usarão), mas 99% das vezes eles desejam recuperar apenas um valor para um cabeçalho de solicitação específico, e esse deve ser um forro.
Justin
39

A linha abaixo throws exceptionse a chave não existir.

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

Gambiarra :

Inclua System.Linq;

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

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

Para expandir a resposta de Youssef, escrevi esse método com base nas preocupações de Drew sobre o cabeçalho não existente, porque me deparei com essa situação durante o teste de unidade.

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

Aqui está um exemplo de uso:

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

Veja também a resposta de @ doguhan-uluca para uma solução mais geral.

neontapir
fonte
1
Funce Actionsão construções genéricas de assinatura de delegado criadas no .NET 3.5 e superior. Ficaria feliz em discutir perguntas específicas sobre o método, mas recomendo aprender sobre elas primeiro.
neontapir
1
@neontapir (e outros), o segundo parâmetro é usado para fornecer um valor padrão se a chave não for encontrada. O terceiro parâmetro é usado para 'transformar' o valor de retorno para o tipo desejado, que também especifica o tipo a ser retornado. Por exemplo, se 'X-MyGuid' não for encontrado, o parâmetro 2 lambda basicamente fornecerá um guid padrão como uma string (como teria sido recuperada do Header) e o terceiro parâmetro Guid.Parse converterá o valor da string encontrado ou padrão em um GUID.
Mikee
@neontapir, de onde vem Request nesta função? (e se é nulo como é que um novo HttpRequestMessage () tem nenhum cabeçalhos não faz sentido apenas devolver o valor padrão se o pedido é nulo?
Mendel
Faz dois anos, mas se bem me lembro, um novo HttpRequestMessageé inicializado com uma coleção de cabeçalhos vazia, que não é nula. Essa função acaba retornando o valor padrão se a solicitação for nula.
neontapir
@mendel, neontapir Eu tentei usar o snippet acima e acredito que o "Request" na linha 2 do corpo do método deve ser um campo privado na classe que contém o método ou ser passado como parâmetro (do tipo HttpRequestMessage) para o método
Sudhanshu Mishra 06/07/2015
12

Crie um novo método - ' Retorna um valor de cabeçalho HTTP individual ' e chame esse método com valor-chave sempre que precisar acessar vários valores-chave em 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();
        }
SRI
fonte
E se MyCustomID não fizer parte da solicitação .. ele retornará uma exceção nula.
Prasad Kanaparthi 03/04
10

Para expandir ainda mais a solução da @ neontapir, aqui está uma solução mais genérica que pode ser aplicada ao HttpRequestMessage ou HttpResponseMessage igualmente e não requer expressões ou funções codificadas manualmente.

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 da amostra:

var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");
Doguhan Uluca
fonte
Parece ótimo, mas GetFirstHeaderValueOrDefaulttem dois parâmetros, por isso reclama sobre a falta de parâmetros ao chamar como uso de amostra var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");Estou perdendo alguma coisa?
Jeb50
Adicionada a nova classe estática, substituída Resposta por solicitação. Chamado a partir do controlador da API, como var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault<int>("productID");obtido Não há argumento fornecido que corresponda ao parâmetro formal obrigatório 'headerKey' de 'HttpRequestMessageExtension.GetFirstHeaderValueOrDefault <T> (HttpRequestMessage, string)'
Jeb50
@ Jeb50 você está declarando using HttpResponseMessageExtensionsno arquivo que está tentando usar esta extensão?
você precisa saber é o seguinte
4

Para o ASP.Net Core, existe uma solução fácil, se você quiser usar o parâmetro diretamente no método do controlador: Use a anotação [FromHeader].

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

Informações adicionais: No meu caso, o "myParam" tinha que ser uma string, int sempre foi 0.

Reiner
fonte
4

Para o ASP.NET, você pode obter o cabeçalho diretamente do parâmetro no método controller usando esta simples biblioteca / pacote . Ele fornece um [FromHeader]atributo exatamente como o ASP.NET Core :). Por exemplo:

    ...
    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}");
            ...
        }
    }
lawrenceagbani
fonte
4

Solução de uma linha

var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Roman Marusyk
fonte
E se MyCustomID não fizer parte da solicitação .. ele retornará uma exceção nula.
Prasad Kanaparthi 03/04
1
@PrasadKanaparthi, você deve usar outra resposta como stackoverflow.com/a/25640256/4275342 . Você vê que não há nenhuma verificação nula, então, o que requesté null? Também é possível. Ou o que MyCustomIDacontece se uma string vazia ou não for igual a foo? Depende do contexto, portanto, esta resposta descreve apenas o caminho e toda a validação e lógica de negócios que você precisa adicionar por conta própria
Roman Marusyk
Você não concorda que o código está funcionando e pode retornar o valor do cabeçalho?
Roman Marusyk em
1
Funciona bem. Se "MyCustomID" fizer parte da solicitação de solicitação. Sim, toda validação precisa ser cuidada
Prasad Kanaparthi 04/04
4
request.Headers.FirstOrDefault( x => x.Key == "MyCustomID" ).Value.FirstOrDefault()

variante moderna :)

Konstantin Salavatov
fonte
E se MyCustomID não fizer parte da solicitação .. ele retornará uma exceção nula.
Prasad Kanaparthi 03/04