Як отримати значення спеціального заголовка в обробці повідомлень 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 Documentation ), переданий у цей перекритий метод?


що станеться, якщо ви використовуєте request.Headers.Get("MyCustomID");?
udidu

2
Немає Get' on the типу HttpRequestHeaders`. Повідомлення: "Не вдається вирішити символ" Отримати "".
atconway

Відповіді:


252

Спробуйте щось подібне:

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

Існує також метод TryGetValues ​​в заголовках, який ви можете використовувати, якщо не завжди гарантується доступ до заголовка.


26
Перевірка нуля для GetValues ​​не обслуговує жодного значення, оскільки він ніколи не поверне null. Якщо заголовок не існує, ви отримаєте 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, але я занадто ледачий, щоб знайти його зараз.
Cory Mawhorter

1
Точка Saeed є дійсною, зручність використання важлива, і найпоширенішим випадком використання тут є отримання одного значення для заголовка запиту. Ви все ще можете виконати операцію 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();
     }           

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 для більш загального рішення.


1
Funcі Actionє загальними конструкціями підписів делегата, вбудованими в .NET 3.5 і вище. Я би радий обговорити конкретні питання щодо методу, але рекомендую ознайомитися з ними першими.
neontapir

1
@neontapir (та інші) другий параметр використовується для надання значення за замовчуванням, якщо ключ не знайдено. Третій параметр використовується для "перетворення" значення повернення в потрібний тип, який також визначає тип, який потрібно повернути. На прикладі, якщо "X-MyGuid" не знайдено, параметр 2 лямбда в основному надає орієнтир за замовчуванням як рядок (як це було б отримано із заголовка), а третій параметр Guid.Parse переведе знайдене або значення рядка за замовчуванням в GUID.
Майк

@neontapir звідки надходить Запит у цій функції? (і якщо це null, як у нового HttpRequestMessage () будуть заголовки? чи не має сенсу просто повертати значення за замовчуванням, якщо Request - це нульове?
mendel

Минуло два роки, але якщо я пригадую, нове HttpRequestMessageініціалізується із порожньою колекцією заголовків, яка не є нульовою. Ця функція повертається до значення за замовчуванням, якщо запит є нульовим.
neontapir

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

Що робити, якщо 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

Додано новий статичний клас, замінено Відповідь на запит. Викликано з контролера API, як var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault<int>("productID");отримано Немає аргументу, який відповідає необхідному формальному параметру 'headerKey' розділу 'HttpRequestMessageExtension.GetFirstHeaderValueOrDefault <T> (HttpRequestMessage, string)'
Jeb50

@ Jeb50 Ви заявляєте про using HttpResponseMessageExtensionsфайл, який ви намагаєтесь використовувати це розширення?
Doguhan Uluca

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

4

Рішення однієї лінії

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

Що робити, якщо MyCustomID не є частиною запиту .. він повертає нульовий виняток.
Прасад Канапартті

1
@PrasadKanaparthi, тоді ви повинні використовувати іншу відповідь, наприклад stackoverflow.com/a/25640256/4275342 . Ви бачите, що немає жодної нульової перевірки, тож, що requestтаке null? Це також можливо. Або що робити, якщо MyCustomIDпорожній рядок чи не дорівнює foo? Це залежить від контексту, тому ця відповідь просто описує спосіб, і всю перевірку та логіку бізнесу, яку потрібно додати самостійно
Роман Марусик

Ви не згодні з тим, що код працює і може повернути значення заголовка?
Роман Марусик

1
Це добре працює, якщо "MyCustomID" є частиною запиту запиту. Так, про всі перевірки потрібно подбати
Prasad Kanaparthi

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.