Как извлечь пользовательское значение заголовка в обработчик сообщений Web API?

150

В настоящее время в моей службе Web API есть обработчик сообщений, который переопределяет SendAsync следующим образом:

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

В этом коде мне нужно проверить пользовательское добавленное значение заголовка запроса с именем MyCustomID. Проблема в том, когда я делаю следующее:

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

... я получаю следующее сообщение об ошибке:

Невозможно применить индексирование с помощью [] к выражению типа 'System.Net.Http.Headers.HttpRequestHeaders'

Как получить доступ к одному пользовательскому заголовку запроса через экземпляр HttpRequestMessage( документация MSDN ), переданный в этот переопределенный метод?

atconway
источник
что произойдет, если вы используете request.Headers.Get("MyCustomID");?
Удиду
2
Get' on the Типа HttpRequestHeaders не существует . Появится сообщение: «Не удается разрешить символ« Получить »».
atconway

Ответы:

253

Попробуйте что-то вроде этого:

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

В заголовках также есть метод TryGetValues, который можно использовать, если не всегда гарантирован доступ к заголовку.

Юсеф Муссауи
источник
26
Нулевая проверка для GetValues ​​не служит никаким значением, поскольку она никогда не вернет ноль. Если заголовок не существует, вы получите InvalidOperationException. Вам необходимо использовать TryGetHeaders, если возможно, что заголовок может отсутствовать в запросе и проверить на ложный ответ ИЛИ попробовать / перехватить вызов GetValues ​​(не рекомендуется).
Дрю Марш
4
@Drew request.Headers.Single (h => h.Key == "Авторизация"); Гораздо меньше кода делает то же самое!
Элизабет
17
Почему бы простоvar id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Гауи
3
@SaeedNeamati, потому что значения заголовков не однозначные. Вы можете иметь Some-Header: oneи тогда Some-Header: twoв том же запросе. Некоторые языки молча отбрасывают «один», но это неверно. Это в RFC, но мне лень его сейчас найти.
Кори Моухортер
1
Точка Саида верна, удобство и простота использования важны, и наиболее распространенный вариант использования здесь - получить одно значение для заголовка запроса. У вас все еще может быть операция GetValues ​​для извлечения нескольких значений для заголовка запроса (которые будут использовать пользователи), но в 99% случаев они захотят просто получить одно значение для определенного заголовка запроса, и это должно быть одно вкладыш.
Джастин
39

Строка ниже, throws exceptionесли ключ не существует.

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

Работа вокруг:

Включить System.Linq;

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

     if (request.Headers.TryGetValues("MyCustomID", out headerValues))
     {
         userId = headerValues.FirstOrDefault();
     }           
SharpCoder
источник
17

Чтобы расширить ответ Юсефа, я написал этот метод на основе опасений Дрю о том, что заголовок не существует, потому что я столкнулся с этой ситуацией во время модульного тестирования.

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

Вот пример использования:

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

Также взгляните на ответ @ doguhan-uluca для более общего решения.

neontapir
источник
1
Funcи Actionявляются общими конструкциями подписи делегата, встроенными в .NET 3.5 и выше. Я был бы рад обсудить конкретные вопросы о методе, но я бы рекомендовал узнать о них в первую очередь.
Неонтапир
1
@neontapir (и другие), второй параметр используется для предоставления значения по умолчанию, если ключ не найден. Третий параметр используется для «преобразования» возвращаемого значения в желаемый тип, который также указывает тип, который должен быть возвращен. Согласно примеру, если 'X-MyGuid' не найден, лямбда-параметр 2 в основном предоставляет guid по умолчанию в виде строки (как это было бы получено из заголовка), а третий параметр Guid.Parse переведет найденное или строковое значение по умолчанию. в GUID.
Майки
@neontapir откуда запрос в этой функции? (и если он равен null, как у нового HttpRequestMessage () будут заголовки? разве не имеет смысла просто возвращать значение по умолчанию, если Request равен null?
mendel
Прошло два года, но, если я помню, новый HttpRequestMessageинициализируется с пустой коллекцией заголовков, которая не является нулевой. Эта функция возвращает значение по умолчанию, если запрос равен нулю.
Неонтапир
@mendel, neontapir Я попытался использовать приведенный выше фрагмент, и я считаю, что «Запрос» в строке 2 тела метода должен быть либо частным полем в классе, содержащем метод, либо передаваться в качестве параметра (типа HttpRequestMessage) в метод
Судханшу Мишра
12

Создайте новый метод - « Возвращает отдельное значение заголовка HTTP » и вызывайте этот метод со значением ключа каждый раз, когда вам нужно получить доступ к нескольким значениям ключа из 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
источник
Что, если MyCustomID не является частью запроса .. он возвращает нулевое исключение.
Прасад Канапарти
10

Чтобы дополнительно расширить решение @ neontapir, вот более общее решение, которое может в равной степени применяться к HttpRequestMessage или HttpResponseMessage и не требует выражений или функций, закодированных вручную.

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

Пример использования:

var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");
Догухан Улука
источник
Выглядит отлично, но GetFirstHeaderValueOrDefaultимеет два параметра, поэтому он жалуется на отсутствие параметра при вызове в качестве примера использования. var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");Я что-то упустил?
Jeb50
Добавлен новый статический класс, заменен Response for Request. Вызывается из контроллера API, так как var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault<int>("productID");есть Там не аргумент , учитывая , что соответствует требуемому формальному параметру «headerKey» из «HttpRequestMessageExtension.GetFirstHeaderValueOrDefault <T> (HttpRequestMessage, строка)»
Jeb50
@ Jeb50 вы декларируете using HttpResponseMessageExtensionsфайл, который пытаетесь использовать это расширение?
Догухан Улука
4

Для ASP.Net Core существует простое решение, если вы хотите использовать параметр непосредственно в методе контроллера: используйте аннотацию [FromHeader].

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

Дополнительная информация: в моем случае «myParam» должен был быть строкой, int всегда был 0.

Райнер
источник
4

Для ASP.NET вы можете получить заголовок непосредственно из параметра в методе контроллера, используя эту простую библиотеку / пакет . Он предоставляет [FromHeader]атрибут, как у вас в ASP.NET Core :). Например:

    ...
    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
источник
4

Одноканальное решение

var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Роман Марусик
источник
Что, если MyCustomID не является частью запроса .. он возвращает нулевое исключение.
Прасад Канапарти
1
@PrasadKanaparthi, тогда вы должны использовать другой ответ, например stackoverflow.com/a/25640256/4275342 . Вы видите, что нет никакой нулевой проверки, так что же requestэто null? Это также возможно. Или что, если MyCustomIDэто пустая строка или не равно foo? Это зависит от контекста, поэтому этот ответ просто описывает путь и всю валидацию и бизнес-логику, которые вы должны добавить самостоятельно
Роман Марусик
Разве вы не согласны с тем, что код работает и может возвращать значение заголовка?
Роман Марусик
1
Работает нормально .. если "MyCustomID" является частью запроса запроса. Да, все проверки нужно позаботиться
Прасад Канапарти
4
request.Headers.FirstOrDefault( x => x.Key == "MyCustomID" ).Value.FirstOrDefault()

современный вариант :)

Константин Салаватов
источник
Что, если MyCustomID не является частью запроса .. он возвращает нулевое исключение.
Прасад Канапарти