Wie füge ich jedem WCF-Aufruf einen benutzerdefinierten HTTP-Header hinzu?

165

Ich habe einen WCF-Dienst, der in einem Windows-Dienst gehostet wird. Clients, die diesen Dienst verwenden, müssen jedes Mal, wenn sie Dienstmethoden aufrufen, einen Bezeichner übergeben (da dieser Bezeichner für die Funktionsweise der aufgerufenen Methode wichtig ist). Ich dachte, es ist eine gute Idee, diese Kennung irgendwie in die WCF-Header-Informationen einzufügen.

Wenn es eine gute Idee ist, wie kann ich den Bezeichner automatisch zu den Header-Informationen hinzufügen. Mit anderen Worten, wenn der Benutzer die WCF-Methode aufruft, muss der Bezeichner automatisch zum Header hinzugefügt werden.

UPDATE: Clients, die den WCF-Dienst verwenden, sind sowohl Windows-Anwendungen als auch Windows Mobile-Anwendungen (unter Verwendung von Compact Framework).

mrtaikandi
quelle
Haben Sie dies letztendlich für das Compact Framework zum Laufen gebracht?
Vaccano

Antworten:

189

Dies hat den Vorteil, dass es auf jeden Anruf angewendet wird.

Erstellen Sie eine Klasse, die IClientMessageInspector implementiert . Fügen Sie in der BeforeSendRequest-Methode Ihren benutzerdefinierten Header zur ausgehenden Nachricht hinzu. Es könnte ungefähr so ​​aussehen:

public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
    HttpRequestMessageProperty httpRequestMessage;
    object httpRequestMessageObject;
    if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
    {
        httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
        if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
        {
            httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
        }
    }
    else
    {
        httpRequestMessage = new HttpRequestMessageProperty();
        httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
        request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
    }
    return null;
}

Erstellen Sie dann ein Endpunktverhalten, das den Nachrichteninspektor auf die Client-Laufzeit anwendet. Sie können das Verhalten über ein Attribut oder über die Konfiguration mithilfe eines Verhaltenserweiterungselements anwenden.

Hier ist ein großartiges Beispiel dafür, wie Sie allen Anforderungsnachrichten einen HTTP-Benutzeragenten-Header hinzufügen. Ich benutze dies bei einigen meiner Kunden. Sie können dies auch auf der Serviceseite tun, indem Sie den IDispatchMessageInspector implementieren .

Hast du das gedacht?

Update: Ich habe diese Liste der WCF-Funktionen gefunden, die vom kompakten Framework unterstützt werden. Ich glaube Nachricht Inspektoren klassifizierten als ‚Kanal Extensibility‘, die nach diesem Beitrag werden durch den kompakten Rahmen unterstützt.

Mark gut
quelle
Vielen Dank. Dies ist genau das, was ich wollte, aber funktioniert es in einem kompakten Rahmen?
Mrtaikandi
2
@ Mark, das ist eine wirklich gute Antwort. Vielen Dank. Ich habe dies über net.tcp versucht, verwende aber die Headers-Auflistung direkt (die HTTP-Header haben nicht funktioniert). Ich erhalte einen Header mit meinem Token (Name) beim ServiceHost AfterReceiveRequest-Ereignis, aber nicht den Wert (es scheint nicht einmal eine Eigenschaft für einen Wert zu geben?). Fehlt mir etwas? Ich hätte ein Name / Wert-Paar erwartet, wie beim Erstellen des Headers, nach dem ich gefragt werde: request.Headers.Add (MessageHeader.CreateHeader (name, ns, value));
Program.X
13
+1 OutgoingMessagePropertiessind das, was Sie benötigen, um auf HTTP-Header zuzugreifen - nicht OutgoingMessageHeadersSOAP-Header.
SliverNinja - MSFT
3
Dies erlaubt nur einen fest codierten Benutzeragenten, der - gemäß dem angegebenen Beispiel - in der web.config fest codiert ist!
KristianB
1
Dies ist eine ausgezeichnete Antwort. Es wird auch der Fall behandelt, in dem HttpRequestMessageProperty.Name in den Nachrichteneigenschaften noch nicht verfügbar ist. Aus irgendeinem Grund wurde mir beim Debuggen meines Codes klar, dass dieser Wert abhängig von einigen Zeitproblemen nicht immer vorhanden war. Danke Mark!
Carlos 357
81

Sie fügen es dem Anruf hinzu, indem Sie:

using (OperationContextScope scope = new OperationContextScope((IContextChannel)channel))
{
    MessageHeader<string> header = new MessageHeader<string>("secret message");
    var untyped = header.GetUntypedHeader("Identity", "http://www.my-website.com");
    OperationContext.Current.OutgoingMessageHeaders.Add(untyped);

    // now make the WCF call within this using block
}

Und dann greifen Sie serverseitig zu:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");
AgileJon
quelle
5
Danke für dein Code-Snippet. Aber damit muss ich den Header jedes Mal hinzufügen, wenn ich eine Methode aufrufen möchte. Ich wollte diesen Prozess transparent machen. Ich meine, bei einmaliger Implementierung wird jedes Mal, wenn der Benutzer einen Service-Client erstellt und eine Methode verwendet, der Kundenheader automatisch zur Nachricht hinzugefügt.
Mrtaikandi
Dies ist ein guter MSDN-Link mit einem Beispiel, um den in dieser Antwort enthaltenen
atconway
1
Vielen Dank, dies ist ein großartiger Code, wenn Sie eine benutzerdefinierte Clientbibliothek verwenden. Auf diese Weise müssen Sie den Messageinspector nicht implementieren. Erstellen Sie einfach eine allgemeine Wrapper-Methode, die jeden Client-Aufruf in OperationContextScope umschließt.
JustAMartin
3
Hinweis: Dies ist problematisch, wenn Sie mit Ihren Anrufen asynchrone Aufgaben ausführen, da OperationContextScope(und OperationContext) sind ThreadStatic- Mark Goods Antwort funktioniert, ohne sich auf ThreadStaticElemente zu verlassen.
Zimdanen
2
Dies fügt keinen HTTP-Header hinzu! Es fügt dem SOAP-Umschlag Header hinzu.
16.
32

Wenn Sie nur allen Anforderungen an den Service denselben Header hinzufügen möchten, können Sie dies ohne jegliche Codierung tun!
Fügen Sie einfach den Header-Knoten mit den erforderlichen Headern unter dem Endpunktknoten in Ihrer Client-Konfigurationsdatei hinzu

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  
Nimesh Madhavan
quelle
18
Dies sind SOAP-Header ( alaMessageHeader ) - keine HTTP-Header.
SliverNinja - MSFT
18

Hier ist eine weitere hilfreiche Lösung zum manuellen Hinzufügen benutzerdefinierter HTTP-Header zu Ihrer Client-WCF-Anforderung unter Verwendung von ChannelFactoryals Proxy. Dies müsste für jede Anforderung durchgeführt werden, reicht jedoch als einfache Demo aus, wenn Sie Ihren Proxy nur zur Vorbereitung auf Nicht-.NET-Plattformen einem Komponententest unterziehen müssen.

// create channel factory / proxy ...
using (OperationContextScope scope = new OperationContextScope(proxy))
{
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = new HttpRequestMessageProperty()
    {
        Headers = 
        { 
            { "MyCustomHeader", Environment.UserName },
            { HttpRequestHeader.UserAgent, "My Custom Agent"}
        }
    };    
    // perform proxy operations... 
}
SliverNinja - MSFT
quelle
1
Ich habe 4 andere ähnlich aussehende Vorschläge ausprobiert und dies ist der einzige, der für mich funktioniert hat.
JohnOpincar
Dies fügt tatsächlich HTTP-Header hinzu, danke! :) Aber meine Güte, es ist hässlich aussehender Code.
16.
11

Dies ähnelt der Antwort von NimsDotNet, zeigt jedoch, wie dies programmgesteuert erfolgt.

Fügen Sie einfach den Header zur Bindung hinzu

var cl = new MyServiceClient();

var eab = new EndpointAddressBuilder(cl.Endpoint.Address);

eab.Headers.Add( 
      AddressHeader.CreateAddressHeader("ClientIdentification",  // Header Name
                                         string.Empty,           // Namespace
                                         "JabberwockyClient"));  // Header Value

cl.Endpoint.Address = eab.ToEndpointAddress();
ΩmegaMan
quelle
Ich habe diesen Code zu meinem aktuellen Aufruf hinzugefügt (clientseitig). Wie erhalte ich diesen Kopfwert im System.ServiceModel.OperationContext? (Serverseite) (Ich
drücke die
1
Ich habs ! System.ServiceModel.Channels.MessageHeaders headers = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", string.Empty); var requestName = (headerIndex <0)? "UNBEKANNT": headers.GetHeader <string> (headerIndex);
GranadaCoder
Dies fügt dem SOAP-Umschlag einen Header hinzu, keinen HTTP-Header
am
5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });
shepcom
quelle
12
Dies ist ein SOAP-Nachrichtenkopf, kein HTTP-Kopf.
René
4

Das funktioniert bei mir

TestService.ReconstitutionClient _serv = new TestService.TestClient();

using (OperationContextScope contextScope = new OperationContextScope(_serv.InnerChannel))
{
   HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();

   requestMessage.Headers["apiKey"] = ConfigurationManager.AppSettings["apikey"]; 
   OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = 
      requestMessage;
   _serv.Method(Testarg);
}
Taran
quelle
3

Dies hat bei mir funktioniert, angepasst vom Hinzufügen von HTTP-Headern zu WCF-Aufrufen

// Message inspector used to add the User-Agent HTTP Header to the WCF calls for Server
public class AddUserAgentClientMessageInspector : IClientMessageInspector
{
    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
    {
        HttpRequestMessageProperty property = new HttpRequestMessageProperty();

        var userAgent = "MyUserAgent/1.0.0.0";

        if (request.Properties.Count == 0 || request.Properties[HttpRequestMessageProperty.Name] == null)
        {
            var property = new HttpRequestMessageProperty();
            property.Headers["User-Agent"] = userAgent;
            request.Properties.Add(HttpRequestMessageProperty.Name, property);
        }
        else
        {
            ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).Headers["User-Agent"] = userAgent;
        }
        return null;
    }

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
    }
}

// Endpoint behavior used to add the User-Agent HTTP Header to WCF calls for Server
public class AddUserAgentEndpointBehavior : IEndpointBehavior
{
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new AddUserAgentClientMessageInspector());
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

Nachdem Sie diese Klassen deklariert haben, können Sie Ihrem WCF-Client das neue Verhalten folgendermaßen hinzufügen:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());
Paulwhit
quelle
Dies wird nicht kompiliert: Fehler CS0136 Ein lokaler oder Parameter mit dem Namen 'property' kann in diesem Bereich nicht deklariert werden, da dieser Name in einem umschließenden lokalen Bereich zum Definieren eines lokalen oder Parameters verwendet wird.
Leszek P
Entfernen Sie einfach die nicht verwendete
Kosnkov
2

Context - Bindungen in .NET 3.5 könnte nur sein , was Sie suchen. Es gibt drei sofort einsatzbereite: BasicHttpContextBinding, NetTcpContextBinding und WSHttpContextBinding. Das Kontextprotokoll übergibt grundsätzlich Schlüssel-Wert-Paare im Nachrichtenkopf. Schauen Sie sich Verwalten Staat mit dauerhaften Dienstleistungen Artikel auf MSDN Magazin.

Mehmet Aras
quelle
Beachten Sie außerdem, dass Sie den Kontext nur einmal festlegen, bevor Sie eine Sitzung mit dem Server einrichten. Dann wird der Kontext schreibgeschützt. Wenn Sie möchten, dass das Kontext-Setup auf der Clientseite transparent ist, können Sie es von der Client-Proxt-Klasse ableiten und im Konstruktor die Informationen hinzufügen, aus denen Ihr Kontext besteht. Jedes Mal, wenn der Client eine Instanz des Client-Proxt erstellt, wird der Kontext automatisch erstellt und der Client-Proxy-Instanz hinzugefügt.
Mehmet Aras
2

Wenn ich Ihre Anforderung richtig verstehe, lautet die einfache Antwort: Sie können nicht.

Dies liegt daran, dass der Client des WCF-Dienstes möglicherweise von einem Dritten generiert wird, der Ihren Dienst verwendet.

Wenn Sie die Kontrolle über die Clients Ihres Dienstes haben, können Sie eine Basisclientklasse erstellen, die den gewünschten Header hinzufügt und das Verhalten der Worker-Klassen erbt.

Paulo Santos
quelle
1
Wenn Sie wirklich SOA erstellen, können Sie nicht davon ausgehen, dass alle Clients .NET-basiert sind. Warten Sie, bis Ihr Unternehmen übernommen wurde.
SliverNinja - MSFT
2
Ist das wirklich wahr? Java-Webdienst-Clients können SOAP-Headern keine Namen / Werte hinzufügen? Ich finde das schwer zu glauben. Sicher wäre es eine andere Implementierung, aber dies ist eine interoperable Lösung
Adam
2

Sie können benutzerdefinierte Header im MessageContract angeben .

Sie können auch <endpoint> -Header verwenden , die in der Konfigurationsdatei gespeichert sind und im Header aller vom Client / Service gesendeten Nachrichten kopiert werden. Dies ist nützlich, um einfach einen statischen Header hinzuzufügen.

Philippe
quelle
3
Dies sind SOAP-Header ( alaMessageHeader ) - keine HTTP-Header.
SliverNinja - MSFT
0

Wenn Sie jedem WCF-Aufruf objektorientiert benutzerdefinierte HTTP-Header hinzufügen möchten, suchen Sie nicht weiter.

Genau wie in der Antwort von Mark Good und Paulwhit müssen wir eine Unterklasse erstellen IClientMessageInspector, um die benutzerdefinierten HTTP-Header in die WCF-Anforderung einzufügen. Lassen Sie uns den Inspektor jedoch allgemeiner gestalten, indem Sie ein Wörterbuch akzeptieren, das die Header enthält, die wir hinzufügen möchten:

public class HttpHeaderMessageInspector : IClientMessageInspector
{
    private Dictionary<string, string> Headers;

    public HttpHeaderMessageInspector(Dictionary<string, string> headers)
    {
        Headers = headers;
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        // ensure the request header collection exists
        if (request.Properties.Count == 0 || request.Properties[HttpRequestMessageProperty.Name] == null)
        {
            request.Properties.Add(HttpRequestMessageProperty.Name, new HttpRequestMessageProperty());
        }

        // get the request header collection from the request
        var HeadersCollection = ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).Headers;

        // add our headers
        foreach (var header in Headers) HeadersCollection[header.Key] = header.Value;

        return null;
    }

    // ... other unused interface methods removed for brevity ...
}

Genau wie in der Antwort von Mark Good und Paulwhit müssen wir eine Unterklasse erstellen IEndpointBehavior, um unsere HttpHeaderMessageInspectorin unseren WCF-Client zu injizieren .

public class AddHttpHeaderMessageEndpointBehavior : IEndpointBehavior
{
    private IClientMessageInspector HttpHeaderMessageInspector;

    public AddHttpHeaderMessageEndpointBehavior(Dictionary<string, string> headers)
    {
        HttpHeaderMessageInspector = new HttpHeaderMessageInspector(headers);
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(HttpHeaderMessageInspector);
    }

    // ... other unused interface methods removed for brevity ...
}

Der letzte Teil, der zum Abschluss unseres objektorientierten Ansatzes erforderlich ist, besteht darin, eine Unterklasse unseres automatisch generierten WCF-Clients zu erstellen (ich habe das WCF-Webdienst-Referenzhandbuch von Microsoft zum Generieren eines WCF-Clients verwendet).

In meinem Fall muss ich einen API-Schlüssel an den x-api-keyHTML-Header anhängen .

Die Unterklasse führt Folgendes aus:

  • Ruft den Konstruktor der Basisklasse mit den erforderlichen Parametern auf (in meinem Fall wurde eine EndpointConfigurationAufzählung generiert, die an den Konstruktor übergeben wird - möglicherweise hat Ihre Implementierung dies nicht).
  • Definiert die Header, die an jede Anforderung angehängt werden sollen
  • Wird AddHttpHeaderMessageEndpointBehavioran das EndpointVerhalten des Kunden angehängt
public class Client : MySoapClient
{
    public Client(string apiKey) : base(EndpointConfiguration.SomeConfiguration)
    {
        var headers = new Dictionary<string, string>
        {
            ["x-api-key"] = apiKey
        };

        var behaviour = new AddHttpHeaderMessageEndpointBehavior(headers);
        Endpoint.EndpointBehaviors.Add(behaviour);
    }
}

Verwenden Sie zum Schluss Ihren Client!

var apiKey = 'XXXXXXXXXXXXXXXXXXXXXXXXX';
var client = new Client (apiKey);
var result = client.SomeRequest()

Die resultierende HTTP-Anforderung sollte Ihre HTTP-Header enthalten und ungefähr so ​​aussehen:

POST http://localhost:8888/api/soap HTTP/1.1
Cache-Control: no-cache, max-age=0
Connection: Keep-Alive
Content-Type: text/xml; charset=utf-8
Accept-Encoding: gzip, deflate
x-api-key: XXXXXXXXXXXXXXXXXXXXXXXXX
SOAPAction: "http://localhost:8888/api/ISoapService/SomeRequest"
Content-Length: 144
Host: localhost:8888

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <SomeRequestxmlns="http://localhost:8888/api/"/>
  </s:Body>
</s:Envelope>
br3nt
quelle
-1

Ein bisschen spät zur Party, aber Juval Lowy spricht genau dieses Szenario in seinem Buch und der zugehörigen ServiceModelEx- Bibliothek an.

Grundsätzlich definiert er ClientBase- und ChannelFactory-Spezialisierungen, mit denen typsichere Headerwerte angegeben werden können. Ich empfehle, die Quelle herunterzuladen und die Klassen HeaderClientBase und HeaderChannelFactory zu betrachten.

John

BrizzleOwl
quelle
1
Dies ist so ziemlich nichts anderes, als die Arbeit von jemandem zu fördern. Könnten Sie einen relevanten Auszug / Algorithmus hinzufügen - dh die Frage beantworten - oder Ihre Zugehörigkeit offenlegen? Ansonsten handelt es sich nur um erfundenen Spam.
Fund Monica Klage
Ich würde sagen, dass es jemandem eine Antwort gibt, indem er auf einen Ansatz verweist, den er möglicherweise nicht kennt. Ich habe den entsprechenden Link angegeben. Warum sollte ich weitere hinzufügen müssen? es ist alles in den Referenzen. Und ich bin sicher, Juval Lowy könnte es besser beschreiben als jemals zuvor :-) Was meine Zugehörigkeit betrifft - ich habe das Buch gekauft! Das ist es. Ich habe Mr. Lowy noch nie getroffen, aber ich bin sicher, er ist ein großartiger Kerl. Weiß anscheinend viel über WCF
;-)
Sie sollten mehr hinzufügen, da Sie vermutlich vor dem Antworten gelesen haben, wie man antwortet, und den Abschnitt "Zitieren Sie immer den relevantesten Teil eines wichtigen Links, falls die Zielwebsite nicht erreichbar ist oder dauerhaft offline ist" lesen . Ihre Zugehörigkeit ist nicht wichtig. Nur die Qualität der Antwort ist.
Fund Monica Klage
Fein. Ich bin nicht für die Punkte dabei - wie Sie wahrscheinlich an meiner Punktzahl erkennen können! Ich dachte nur, es könnte ein nützlicher Zeiger sein.
BrizzleOwl
1
Ich sage nicht, dass es ein schlechter Zeiger ist. Ich sage, dass es für sich genommen keine gute Antwort ist. Es mag den Menschen sehr gut helfen, und das ist gut so, aber die Antwort ist besser, wenn Sie die von ihm verwendete Methode beschreiben können, anstatt eine sehr kurze Beschreibung der beteiligten Klassen zu geben. Auf diese Weise hilft Ihre Antwort immer noch, wenn auf die Website aus irgendeinem Grund nicht zugegriffen werden kann.
Fund Monica Klage