Jak dodać niestandardowy nagłówek HTTP do każdego wywołania WCF?

162

Mam usługę WCF, która jest hostowana w usłudze systemu Windows. Klienci korzystający z tej usługi muszą przekazywać identyfikator za każdym razem, gdy wywołują metody usługi (ponieważ ten identyfikator jest ważny dla tego, co powinna zrobić wywoływana metoda). Pomyślałem, że dobrym pomysłem jest umieszczenie tego identyfikatora w informacjach nagłówka WCF.

Jeśli to dobry pomysł, w jaki sposób mogę automatycznie dodać identyfikator do informacji w nagłówku. Innymi słowy, za każdym razem, gdy użytkownik wywołuje metodę WCF, identyfikator musi zostać automatycznie dodany do nagłówka.

AKTUALIZACJA: Klienci korzystający z usługi WCF to zarówno aplikacje systemu Windows, jak i aplikacja Windows Mobile (korzystająca z Compact Framework).

mrtaikandi
źródło
1
Czy byłeś w stanie rozwiązać swój problem?
Mark Good
Czy w końcu udało ci się to zadziałać w Compact Framework?
Vaccano,

Odpowiedzi:

185

Zaletą tego jest to, że jest stosowany do każdego połączenia.

Utwórz klasę, która implementuje IClientMessageInspector . W metodzie BeforeSendRequest dodaj niestandardowy nagłówek do wiadomości wychodzącej. Może to wyglądać mniej więcej tak:

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

Następnie utwórz zachowanie punktu końcowego, które zastosuje inspektora komunikatów do środowiska wykonawczego klienta. Zachowanie można zastosować za pomocą atrybutu lub konfiguracji przy użyciu elementu rozszerzenia zachowania.

Oto wspaniały przykład dodawania nagłówka klienta użytkownika HTTP do wszystkich komunikatów żądań. Używam tego u kilku moich klientów. Możesz również zrobić to samo po stronie usługi, implementując IDispatchMessageInspector .

Czy to właśnie miałeś na myśli?

Aktualizacja: znalazłem tę listę funkcji WCF, które są obsługiwane przez kompaktową strukturę. Uważam, że inspektorzy wiadomości sklasyfikowani jako „Channel Extensibility”, zgodnie z tym postem, obsługiwani przez zwarty framework.

Mark Good
źródło
2
@Mark, to naprawdę świetna odpowiedź. Dzięki. Próbowałem tego przez net.tcp, ale używam bezpośrednio kolekcji Headers (nagłówki HTTP nie działały). Otrzymuję nagłówek z moim tokenem (nazwą) w zdarzeniu ServiceHost AfterReceiveRequest, ale nie wartość (nawet nie wydaje się, że istnieje właściwość dla wartości?). Czy jest coś, czego mi brakuje? Spodziewałbym się pary nazwa / wartość, ponieważ podczas tworzenia nagłówka pyta mnie o: request.Headers.Add (MessageHeader.CreateHeader (nazwa, ns, wartość));
Program.X
13
+1 OutgoingMessagePropertiessą tym, czego potrzebujesz, aby uzyskać dostęp do nagłówków HTTP, a nie OutgoingMessageHeadersktóre są nagłówkami SOAP.
SliverNinja - MSFT
1
Po prostu niesamowity kod! :)
abhilashca
3
Pozwala to tylko na zakodowane na stałe klienta użytkownika, który - zgodnie z podanym przykładem - jest zakodowany na stałe w pliku web.config!
KristianB
1
To doskonała odpowiedź. Obsługuje również przypadek, gdy HttpRequestMessageProperty.Name nie jest jeszcze dostępny we właściwościach wiadomości. Z jakiegoś powodu, debugując mój kod, zdałem sobie sprawę, że w zależności od pewnych problemów z synchronizacją ta wartość nie zawsze była dostępna. Dzięki Mark!
carlos357
80

Dodajesz go do rozmowy za pomocą:

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
}

A potem, po stronie serwera, łapiesz go za pomocą:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");
AgileJon
źródło
5
Dzięki za fragment kodu. Ale dzięki temu muszę dodawać nagłówek za każdym razem, gdy chcę wywołać metodę. Chciałem, aby ten proces był przejrzysty. Chodzi mi o to, że raz zaimplementowano, za każdym razem, gdy użytkownik tworzy klienta usługi i korzysta z metody, nagłówek klienta jest automatycznie dodawany do wiadomości.
mrtaikandi
To jest dobre łącze MSDN z przykładem rozszerzającym sugestię
zawartą
1
Dzięki, to świetny fragment kodu, jeśli używasz niestandardowej biblioteki klienta. W ten sposób nie musisz implementować narzędzia Messageinspector. Po prostu utwórz wspólną metodę opakowującą, która zawija każde wywołanie klienta w OperationContextScope.
JustAMartin
3
Uwaga: jest to problematyczne, jeśli wykonujesz jakiekolwiek czynności asynchroniczne ze swoimi połączeniami, ponieważ OperationContextScope(i OperationContext) są ThreadStatic- odpowiedź Marka Gooda będzie działać bez polegania na ThreadStaticelementach.
zimdanen
2
To nie dodaje nagłówka HTTP! Dodaje nagłówki do koperty SOAP.
br3nt
32

Jeśli chcesz tylko dodać ten sam nagłówek do wszystkich żądań do usługi, możesz to zrobić bez kodowania!
Po prostu dodaj węzeł nagłówków z wymaganymi nagłówkami pod węzłem punktu końcowego w pliku konfiguracyjnym klienta

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  
Nimesh Madhavan
źródło
18
To są nagłówki SOAP ( alaMessageHeader ) - nie nagłówki HTTP.
SliverNinja - MSFT
18

Oto kolejne pomocne rozwiązanie do ręcznego dodawania niestandardowych nagłówków HTTP do żądania WCF klienta przy użyciu ChannelFactoryserwera proxy. Musiałoby to być zrobione dla każdego żądania, ale wystarczy jako proste demo, jeśli potrzebujesz tylko przetestować jednostkę proxy w ramach przygotowań do platform innych niż .NET.

// 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
źródło
1
Wypróbowałem 4 inne podobnie wyglądające sugestie i jest to jedyna, która zadziałała.
JohnOpincar,
To faktycznie dodaje nagłówki HTTP, dzięki! :) Ale rany, to brzydko wyglądający kod.
br3nt
11

Jest to podobne do odpowiedzi NimsDotNet, ale pokazuje, jak to zrobić programowo.

Po prostu dodaj nagłówek do wiązania

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
źródło
Mam ten kod dodany do mojego bieżącego połączenia (po stronie klienta). Jak uzyskać tę wartość głowy w System.ServiceModel.OperationContext? (strona serwera) (trzymam kciuki, że to mi pomoże)
granadaCoder
1
Rozumiem ! System.ServiceModel.Channels.MessageHeaders headers = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", string.Empty); var requestName = (headerIndex <0)? „UNKNOWN”: headers.GetHeader <string> (headerIndex);
granadaCoder
1
@granadaCoder Uwielbiam tę stronę! ;-)
ΩmegaMan
To dodaje nagłówek do koperty SOAP, a nie nagłówek HTTP
br3nt
5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });
shepcom
źródło
12
To jest nagłówek wiadomości SOAP, a nie nagłówek HTTP.
René
3

To właśnie zadziałało w moim przypadku, dostosowane z dodawania nagłówków HTTP do wywołań WCF

// 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)
    {
    }
}

Po zadeklarowaniu tych klas można dodać nowe zachowanie do klienta WCF w następujący sposób:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());
Paulwhit
źródło
To się nie skompiluje: Błąd CS0136 Lokalny lub parametr o nazwie „właściwość” nie może zostać zadeklarowany w tym zakresie, ponieważ ta nazwa jest używana w otaczającym zakresie lokalnym do definiowania lokalnego lub parametru.
Leszek P
po prostu usuń ten nie używany
kosnkov
3

To działa dla mnie

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
źródło
2

Wiązania kontekstowe w .NET 3.5 mogą być właśnie tym, czego szukasz. Istnieją trzy gotowe do użycia: BasicHttpContextBinding, NetTcpContextBinding i WSHttpContextBinding. Protokół kontekstowy zasadniczo przekazuje pary klucz-wartość w nagłówku wiadomości. Przeczytaj artykuł Managing State with Durable Services w magazynie MSDN.

Mehmet Aras
źródło
Należy również pamiętać, że kontekst ustawia się tylko raz przed nawiązaniem sesji z serwerem. Wtedy kontekst staje się tylko do odczytu. Jeśli chcesz, aby konfiguracja kontekstu była przezroczysta po stronie klienta, możesz wyprowadzić z klasy klienta proxt, aw konstruktorze możesz dodać informacje, które tworzą Twój kontekst. Następnie za każdym razem, gdy klient tworzy instancję klienta proxy, kontekst zostanie automatycznie utworzony i dodany do instancji klienta proxy.
Mehmet Aras
2

Jeśli dobrze rozumiem twoje wymagania, prosta odpowiedź brzmi: nie możesz.

Dzieje się tak, ponieważ klient usługi WCF może być generowany przez dowolną stronę trzecią korzystającą z Twojej usługi.

JEŚLI masz kontrolę nad klientami usługi, możesz utworzyć podstawową klasę klienta, która doda żądany nagłówek i odziedziczy zachowanie klas roboczych.

Paulo Santos
źródło
1
zgadzam się, jeśli naprawdę budujesz architekturę SOA, nie możesz zakładać, że wszyscy klienci są oparte na platformie .NET. Poczekaj, aż Twoja firma zostanie przejęta.
SliverNinja - MSFT
2
Czy to prawda? Klienci usługi WWW Java nie mają możliwości dodawania nazw / wartości do nagłówków SOAP? Trudno mi w to uwierzyć. Jasne, byłaby to inna implementacja, ale jest to rozwiązanie interoperacyjne
Adam
2

Możesz określić niestandardowe nagłówki w MessageContract .

Możesz również użyć nagłówków <endpoint>, które są przechowywane w pliku konfiguracyjnym i będą kopiowane przez cały czas w nagłówku wszystkich wiadomości wysyłanych przez klienta / usługę. Jest to przydatne do łatwego dodawania statycznego nagłówka.

Philippe
źródło
3
To są nagłówki SOAP ( alaMessageHeader ) - nie nagłówki HTTP.
SliverNinja - MSFT
0

Jeśli chcesz dodać niestandardowe nagłówki HTTP do każdego wywołania WCF w sposób obiektowy, nie szukaj dalej.

Podobnie jak w odpowiedzi Marka Gooda i Paulwhita, musimy podklasę IClientMessageInspectorwstrzyknąć niestandardowe nagłówki HTTP do żądania WCF. Jednak uczyńmy inspektora bardziej ogólnym, akceptując słownik zawierający nagłówki, które chcemy dodać:

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

Podobnie jak w odpowiedzi Marka Gooda i Paulwhita, musimy przejść do podklasy, IEndpointBehavioraby wstrzyknąć naszą HttpHeaderMessageInspectordo naszego klienta WCF.

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

Ostatnią częścią potrzebną do zakończenia naszego podejścia obiektowego jest utworzenie podklasy naszego automatycznie generowanego klienta WCF (do wygenerowania klienta WCF użyłem Przewodnika po usługach sieci Web WCF firmy Microsoft ).

W moim przypadku muszę dołączyć klucz API do x-api-keynagłówka HTML.

Podklasa wykonuje następujące czynności:

  • wywołuje konstruktora klasy bazowej z wymaganymi parametrami (w moim przypadku plik EndpointConfiguration wyliczenie zostało wygenerowane do przekazania do konstruktora - być może Twoja implementacja tego nie będzie)
  • Definiuje nagłówki, które powinny być dołączane do każdego żądania
  • Przywiązuje się AddHttpHeaderMessageEndpointBehaviordo Endpointzachowań klienta
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);
    }
}

Wreszcie, użyj swojego klienta!

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

Wynikowe żądanie HTTP powinno zawierać Twoje nagłówki HTTP i wyglądać mniej więcej tak:

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
źródło
-1

Trochę późno na imprezę, ale Juval Lowy omawia dokładnie ten scenariusz w swojej książce i związanym z nią ServiceModelEx bibliotece .

Zasadniczo definiuje specjalizacje ClientBase i ChannelFactory, które umożliwiają określenie bezpiecznych dla typu wartości nagłówka. Sugeruję pobranie źródła i sprawdzenie klas HeaderClientBase i HeaderChannelFactory.

Jan

BrizzleOwl
źródło
1
To właściwie nic innego jak promowanie czyjejś pracy. Czy możesz dodać odpowiedni fragment / algorytm - tj. Odpowiedzieć na pytanie - lub ujawnić swoją przynależność? W przeciwnym razie jest to po prostu wymyślony spam.
Załóż pozew Moniki
Powiedziałbym, że jest to udzielenie komuś odpowiedzi w formie wskaźnika do podejścia, którego może nie być świadomy. Podałem odpowiedni link, dlaczego powinienem dodawać więcej? wszystko jest w referencjach. I jestem pewien, że Juval Lowy mógł to opisać lepiej niż ja kiedykolwiek :-) A co do mojej przynależności - kupiłem książkę! Otóż ​​to. Nigdy nie spotkałem pana Lowy'ego, ale jestem pewien, że jest świetnym facetem. Widocznie dużo wie o WCF ;-)
BrizzleOwl
Powinieneś dodać więcej, ponieważ prawdopodobnie przeczytałeś Jak odpowiedzieć przed udzieleniem odpowiedzi i zwróciłeś uwagę na sekcję, która mówi: „Zawsze cytuj najbardziej odpowiednią część ważnego linku, na wypadek, gdyby strona docelowa była nieosiągalna lub została trwale wyłączona”. Twoja przynależność nie jest ważna. Tylko jakość odpowiedzi jest.
Załóż pozew Moniki
W porządku. Nie robię tego dla punktów - jak pewnie możesz stwierdzić po moim wyniku! Pomyślałem, że może to być przydatny wskaźnik.
BrizzleOwl
1
Nie mówię, że to zły wskaźnik. Mówię, że sama w sobie to nie jest dobra odpowiedź. Może to bardzo pomóc ludziom i to dobrze, ale odpowiedź będzie lepsza, jeśli możesz opisać metodę, której używa, zamiast bardzo krótkiego opisu zajęć. W ten sposób w przypadku braku dostępu do witryny - z jakiegokolwiek powodu - Twoja odpowiedź nadal pomoże.
Załóż pozew Moniki