Wie extrahiere ich einen benutzerdefinierten Header-Wert im Web-API-Nachrichtenhandler?

150

Ich habe derzeit einen Nachrichtenhandler in meinem Web-API-Dienst, der 'SendAsync' wie folgt überschreibt:

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

Innerhalb dieses Codes muss ich einen benutzerdefinierten hinzugefügten Anforderungsheaderwert mit dem Namen überprüfen MyCustomID. Das Problem ist, wenn ich Folgendes mache:

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

... Ich erhalte folgende Fehlermeldung:

Indizierung mit [] kann nicht auf einen Ausdruck vom Typ 'System.Net.Http.Headers.HttpRequestHeaders' angewendet werden.

Wie kann ich über die an diese überschriebene Methode übergebene Instanz ( MSDN Documentation ) auf einen einzelnen benutzerdefinierten Anforderungsheader zugreifen ?HttpRequestMessage

atconway
quelle
Was passiert, wenn Sie verwenden request.Headers.Get("MyCustomID");?
Udidu
2
Es gibt keinen Get' on the HttpRequestHeaders-Typ. Die Meldung "Symbol 'Get' kann nicht aufgelöst werden" wird ausgegeben.
Atconway

Antworten:

252

Versuchen Sie so etwas:

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

Es gibt auch eine TryGetValues-Methode für Header, die Sie verwenden können, wenn nicht immer garantiert ist, dass Sie Zugriff auf den Header haben.

Youssef Moussaoui
quelle
26
Die Nullprüfung für GetValues ​​liefert keinen Wert, da sie niemals null zurückgibt. Wenn der Header nicht vorhanden ist, erhalten Sie eine InvalidOperationException. Sie müssen TryGetHeaders verwenden, wenn es möglich ist, dass der Header in der Anforderung nicht vorhanden ist, und nach einer falschen Antwort suchen ODER den GetValues-Aufruf versuchen / abfangen (nicht empfohlen).
Drew Marsh
4
@Drew request.Headers.Single (h => h.Key == "Authorization"); Viel weniger Code macht das Gleiche!
Elisabeth
17
Warum nicht einfachvar id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Gaui
3
@SaeedNeamati, da die Header-Werte nicht eins zu eins sind. Sie können Some-Header: oneund dann Some-Header: twoin der gleichen Anfrage haben. Einige Sprachen verwerfen stillschweigend "eins", aber das ist falsch. Es ist im RFC, aber ich bin zu faul, um es jetzt zu finden.
Cory Mawhorter
1
Der Punkt von Saeed ist gültig, die Benutzerfreundlichkeit ist wichtig und der häufigste Anwendungsfall besteht darin, einen Wert für einen Anforderungsheader abzurufen. Sie können weiterhin eine GetValues-Operation zum Abrufen mehrerer Werte für einen Anforderungsheader verwenden (die nur von Personen verwendet werden), aber in 99% der Fälle möchten sie nur einen Wert für einen bestimmten Anforderungsheader abrufen, und dies sollte einer sein Liner.
Justin
39

Die folgende Zeile, throws exceptionwenn der Schlüssel nicht vorhanden ist.

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

Umgehen:

Include System.Linq;

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

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

Um Youssefs Antwort zu erweitern, schrieb ich diese Methode basierend auf Drews Bedenken bezüglich des nicht vorhandenen Headers, da ich während des Komponententests auf diese Situation stieß.

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

Hier ist ein Beispiel für die Verwendung:

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

Schauen Sie sich auch die Antwort von @ doguhan-uluca an, um eine allgemeinere Lösung zu finden.

Neontapir
quelle
1
Funcund Actionsind generische Delegatensignaturkonstrukte, die in .NET 3.5 und höher integriert sind. Ich würde gerne spezifische Fragen zur Methode besprechen, aber ich würde empfehlen, zuerst etwas darüber zu lernen.
Neontapir
1
@neontapir (und andere) Der zweite Parameter wird verwendet, um einen Standardwert bereitzustellen, wenn der Schlüssel nicht gefunden wird. Der dritte Parameter wird verwendet, um den Rückgabewert vom gewünschten Typ zu transformieren, der auch den zurückzugebenden Typ angibt. Wenn im Beispiel 'X-MyGuid' nicht gefunden wird, liefert Parameter 2 Lambda grundsätzlich eine Standard-Guid als Zeichenfolge (wie sie aus dem Header abgerufen worden wäre), und der dritte Guid.Parse-Parameter übersetzt den gefundenen oder Standard-Zeichenfolgenwert in eine GUID.
Mikee
@neontapir Woher kommt die Anfrage in dieser Funktion? (und wenn es null ist, wie wird eine neue HttpRequestMessage () irgendwelche Header haben? Ist es nicht sinnvoll, nur den Standardwert zurückzugeben, wenn Request null ist?
Mendel
Es ist zwei Jahre her, aber wenn ich mich HttpRequestMessagerecht erinnere, wird eine neue mit einer leeren Headers-Sammlung initialisiert, die nicht null ist. Diese Funktion gibt am Ende den Standardwert zurück, wenn die Anforderung null ist.
Neontapir
@mendel, neontapir Ich habe versucht, das obige Snippet zu verwenden, und ich glaube, dass die "Anforderung" in Zeile 2 des Methodenkörpers entweder ein privates Feld in der Klasse sein sollte, die die Methode enthält, oder als Parameter (vom Typ HttpRequestMessage) an übergeben werden sollte die Methode
Sudhanshu Mishra
12

Erstellen Sie eine neue Methode - ' Gibt einen einzelnen HTTP-Header-Wert zurück ' und rufen Sie diese Methode jedes Mal mit einem Schlüsselwert auf, wenn Sie über HttpRequestMessage auf mehrere Schlüsselwerte zugreifen müssen.

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
quelle
Was ist, wenn MyCustomID nicht Teil der Anforderung ist? Es gibt eine Null-Ausnahme zurück.
Prasad Kanaparthi
10

Um die Lösung von @ neontapir weiter zu erweitern, finden Sie hier eine allgemeinere Lösung, die für HttpRequestMessage oder HttpResponseMessage gleichermaßen gelten kann und keine handcodierten Ausdrücke oder Funktionen erfordert.

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

Beispielnutzung:

var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");
Doguhan Uluca
quelle
Sieht gut aus, hat aber GetFirstHeaderValueOrDefaultzwei Parameter und beschwert sich über fehlende Parameter beim Aufruf als Beispielverwendung. Fehlt var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");mir etwas?
Jeb50
Die neue statische Klasse wurde hinzugefügt und die Antwort auf Anforderung ersetzt. Genannt von API - Controller, als var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault<int>("productID");hätte es kein Argument angegeben ist , dass entspricht den erforderlichen formalen Parameter ‚headerKey‘ von ‚HttpRequestMessageExtension.GetFirstHeaderValueOrDefault <T> (HttpRequestMessage, string)‘
Jeb50
@ Jeb50 erklären Sie using HttpResponseMessageExtensionsin der Datei, dass Sie versuchen, diese Erweiterung zu verwenden?
Doguhan Uluca
4

Für ASP.Net Core gibt es eine einfache Lösung, wenn Sie den Parameter direkt in der Controller-Methode verwenden möchten: Verwenden Sie die Annotation [FromHeader].

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

Zusätzliche Informationen: In meinem Fall musste "myParam" eine Zeichenfolge sein, int war immer 0.

Reiner
quelle
4

Für ASP.NET können Sie den Header mithilfe dieser einfachen Bibliothek / dieses Pakets direkt aus dem Parameter in der Controller-Methode abrufen . Es bietet ein [FromHeader]Attribut wie in ASP.NET Core :). Beispielsweise:

    ...
    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
quelle
4

Einzeilige Lösung

var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
Roman Marusyk
quelle
Was ist, wenn MyCustomID nicht Teil der Anforderung ist? Es gibt eine Null-Ausnahme zurück.
Prasad Kanaparthi
1
@PrasadKanaparthi dann sollten Sie eine andere Antwort wie stackoverflow.com/a/25640256/4275342 verwenden . Sie sehen, dass es keine Nullprüfung gibt. Was ist requestdas null? Es ist auch möglich. Oder was ist, wenn MyCustomIDeine leere Zeichenfolge gleich ist oder nicht foo? Es hängt vom Kontext ab, daher beschreibt diese Antwort nur den Weg und alle Validierungs- und Geschäftslogiken, die Sie selbst hinzufügen müssen
Roman Marusyk,
Stimmen Sie nicht zu, dass der Code funktioniert und einen Header-Wert zurückgeben kann?
Roman Marusyk
1
Es funktioniert gut .. wenn "MyCustomID" Teil der Anfrage der Anfrage ist. Ja, alle Validierungen müssen
erledigt
4
request.Headers.FirstOrDefault( x => x.Key == "MyCustomID" ).Value.FirstOrDefault()

moderne Variante :)

Konstantin Salavatov
quelle
Was ist, wenn MyCustomID nicht Teil der Anforderung ist? Es gibt eine Null-Ausnahme zurück.
Prasad Kanaparthi