Come aggiungere un'intestazione HTTP personalizzata ad ogni chiamata WCF?


162

Ho un servizio WCF ospitato in un servizio Windows. I client che utilizzano questo servizio devono passare un identificatore ogni volta che chiamano i metodi di servizio (perché tale identificatore è importante per ciò che dovrebbe fare il metodo chiamato). Ho pensato che fosse una buona idea mettere in qualche modo questo identificatore nelle informazioni dell'intestazione WCF.

Se è una buona idea, come posso aggiungere automaticamente l'identificatore alle informazioni dell'intestazione. In altre parole, ogni volta che l'utente chiama il metodo WCF, l'identificatore deve essere aggiunto automaticamente all'intestazione.

AGGIORNAMENTO: i client che utilizzano il servizio WCF sono sia applicazioni Windows sia applicazioni Windows Mobile (utilizzando Compact Framework).


1
Sei riuscito a risolvere il tuo problema?
Segna bene il

Hai finito per farlo funzionare sul Compact Framework?
Vaccano,

Risposte:


185

Il vantaggio è che viene applicato ad ogni chiamata.

Creare una classe che implementa IClientMessageInspector . Nel metodo BeforeSendRequest, aggiungi l'intestazione personalizzata al messaggio in uscita. Potrebbe assomigliare a questo:

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

Quindi creare un comportamento endpoint che applichi la finestra di ispezione dei messaggi al runtime del client. È possibile applicare il comportamento tramite un attributo o tramite la configurazione utilizzando un elemento di estensione del comportamento.

Ecco un ottimo esempio di come aggiungere un'intestazione HTTP agente utente a tutti i messaggi di richiesta. Sto usando questo in alcuni dei miei clienti. Puoi anche fare lo stesso sul lato servizio implementando IDispatchMessageInspector .

È questo che avevi in ​​mente?

Aggiornamento: ho trovato questo elenco di funzionalità WCF supportate dal framework compatto. Credo che gli ispettori di messaggi classificati come "estensibilità del canale" che, secondo questo post, sono supportati dal framework compatto.


2
@Mark, questa è un'ottima risposta. Grazie. Ho provato questo su net.tcp ma sto usando direttamente la raccolta Headers (le intestazioni Http non hanno funzionato). Ricevo un'intestazione con il mio token (Nome) nell'evento ServiceHost AfterReceiveRequest, ma non il valore (non sembra nemmeno esserci una proprietà per un valore?). C'è qualcosa che mi manca? Mi sarei aspettato una coppia nome / valore come quando creo l'intestazione che mi chiede: request.Headers.Add (MessageHeader.CreateHeader (name, ns, value));
Program.X

13
+1 OutgoingMessagePropertiesè ciò di cui hai bisogno per accedere alle intestazioni HTTP, non a OutgoingMessageHeadersquelle SOAP.
SliverNinja - MSFT

1
Semplicemente, codice fantastico! :)
abhilashca,

3
Ciò consente solo un agente utente hardcoded, che - secondo l'esempio fornito - è hardcoded in web.config!
KristianB,

1
Questa è una risposta eccellente Gestisce anche il caso in cui HttpRequestMessageProperty.Name non è ancora disponibile nelle proprietà del messaggio. Per qualche ragione, eseguendo il debug del mio codice, mi sono reso conto che a seconda di alcuni problemi di temporizzazione questo valore non era sempre presente. Grazie Marco!
carlos357,

80

Lo aggiungi alla chiamata usando:

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
}

E poi, sul lato server, lo prendi usando:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");

5
Grazie per lo snippet di codice. Ma con questo devo aggiungere l'intestazione ogni volta che voglio chiamare un metodo. Volevo rendere questo processo trasparente. Intendo con l'implementazione una volta, ogni volta che l'utente crea un client di servizio e utilizza un metodo, l'intestazione del cliente viene automaticamente aggiunta al messaggio.
mrtaikandi,

Questo è un buon collegamento MSDN con un esempio di espandere su suggerimento fornite in questa risposta: msdn.microsoft.com/en-us/library/...
atconway

1
Grazie, questo è un ottimo pezzo di codice se stai usando una libreria client personalizzata. In questo modo non è necessario implementare il messageinspector. Basta creare un metodo wrapper comune che racchiuda tutte le chiamate client in OperationContextScope.
JustAMartin,

3
Come nota, questo è problematico se stai facendo qualcosa di asincrono con le tue chiamate, perché OperationContextScope(e OperationContext) sono ThreadStatic: la risposta di Mark Good funzionerà senza fare affidamento sugli ThreadStaticoggetti.
zimdanen,

2
Questo non aggiunge un'intestazione HTTP! Aggiunge intestazioni alla busta SOAP.
br3nt,

32

Se vuoi solo aggiungere la stessa intestazione a tutte le richieste al servizio, puoi farlo senza alcuna codifica!
Basta aggiungere il nodo delle intestazioni con le intestazioni richieste sotto il nodo dell'endpoint nel file di configurazione del client

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  

18
Queste sono intestazioni SOAP ( alaMessageHeader ), non intestazioni HTTP.
SliverNinja - MSFT

18

Ecco un'altra utile soluzione per aggiungere manualmente intestazioni HTTP personalizzate alla tua richiesta WCF client usando ChannelFactorycome proxy. Questo dovrebbe essere fatto per ogni richiesta, ma è sufficiente una semplice demo se hai solo bisogno di testare l'unità del tuo proxy in preparazione per piattaforme non.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... 
}

1
Ho provato altri 4 suggerimenti simili e questo è l'unico che ha funzionato per me.
JohnOpincar,

Questo in realtà aggiunge le intestazioni HTTP, grazie! :) Ma accidenti è un brutto codice.
br3nt,

11

Questo è simile alla risposta NimsDotNet ma mostra come farlo a livello di codice.

Aggiungi semplicemente l'intestazione al binding

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

Ho ottenuto questo codice aggiunto alla mia chiamata corrente (lato client). Come posso ottenere questo valore principale in System.ServiceModel.OperationContext? (lato server) (Incrocio le dita che questo mi aiuterà)
granadaCoder

1
Fatto ! System.ServiceModel.Channels.MessageHeaders headers = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", string.Empty); var requestName = (headerIndex <0)? "SCONOSCIUTO": headers.GetHeader <string> (headerIndex);
granadaCoder

1
@granadaCoder Adoro quel sito! ;-)
ΩmegaMan

Questo aggiunge un'intestazione alla busta SOAP, non un'intestazione HTTP
br3nt il

5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });

12
Questa è un'intestazione del messaggio SOAP, non un'intestazione HTTP.
René,

3

Questo è ciò che ha funzionato per me, adattato dall'aggiunta di intestazioni HTTP alle chiamate 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)
    {
    }
}

Dopo aver dichiarato queste classi, puoi aggiungere il nuovo comportamento al tuo client WCF in questo modo:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());

Questo non verrà compilato: Errore CS0136 Un locale o un parametro denominato "proprietà" non può essere dichiarato in questo ambito poiché tale nome viene utilizzato in un ambito locale che lo racchiude per definire un locale o un parametro.
Leszek P

basta rimuovere quello non utilizzato
kosnkov

3

Questo funziona per me

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

2

I collegamenti di contesto in .NET 3.5 potrebbero essere proprio quello che stai cercando. Ce ne sono tre pronti all'uso: BasicHttpContextBinding, NetTcpContextBinding e WSHttpContextBinding. Il protocollo di contesto passa sostanzialmente le coppie chiave-valore nell'intestazione del messaggio. Consulta l' articolo sulla gestione dello stato con Durable Services sulla rivista MSDN.


Si noti inoltre che è stato impostato il contesto solo una volta prima di stabilire una sessione con il server. Quindi il contesto diventa di sola lettura. Se si desidera che l'impostazione del contesto sia trasparente sul lato client, è possibile derivare dalla classe proxt client e nel contrector è possibile aggiungere le informazioni che compongono il contesto. Quindi ogni volta che il client crea un'istanza del client proxt, il contesto verrà automaticamente creato e aggiunto all'istanza proxy del client.
Mehmet Aras,

2

Se capisco correttamente le tue esigenze, la semplice risposta è: non puoi.

Questo perché il client del servizio WCF può essere generato da qualsiasi terza parte che utilizza il tuo servizio.

SE hai il controllo dei client del tuo servizio, puoi creare una classe client di base che aggiunge l'intestazione desiderata ed eredita il comportamento sulle classi di lavoro.


1
concordato, se stai davvero realizzando SOA, non puoi presumere che tutti i client siano basati su .NET. Attendi fino a quando la tua attività non viene acquisita.
SliverNinja - MSFT

2
È davvero vero? I client dei servizi Web Java non hanno la possibilità di aggiungere nome / valori alle intestazioni SOAP? Lo trovo difficile da credere. Certo si tratterebbe di un'implementazione diversa, ma questa è una soluzione interoperabile
Adam,

2

È possibile specificare intestazioni personalizzate in MessageContract .

È inoltre possibile utilizzare le intestazioni <endpoint> che sono memorizzate nel file di configurazione e verranno copiate sempre nell'intestazione di tutti i messaggi inviati dal client / servizio. Questo è utile per aggiungere facilmente qualche intestazione statica.


3
Queste sono intestazioni SOAP ( alaMessageHeader ), non intestazioni HTTP.
SliverNinja - MSFT

0

Se si desidera aggiungere intestazioni HTTP personalizzate a ogni chiamata WCF in modo orientato agli oggetti, non cercare oltre.

Proprio come nella risposta di Mark Good e Paul, dobbiamo sottoclassare IClientMessageInspectorper iniettare le intestazioni HTTP personalizzate nella richiesta WCF. Tuttavia, rendiamo l'ispettore più generico accettando un dizionario contenente le intestazioni che vogliamo aggiungere:

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

Proprio come nella risposta di Mark Good e Paul, dobbiamo sottoclassare IEndpointBehaviorper iniettarci HttpHeaderMessageInspectornel nostro client 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 ...
}

L'ultima parte necessaria per completare il nostro approccio orientato agli oggetti, è creare una sottoclasse del nostro client generato automaticamente WCF (ho usato la Guida di riferimento al servizio Web WCF di Microsoft per generare un client WCF).

Nel mio caso, devo collegare una chiave API a x-api-key HTML.

La sottoclasse procede come segue:

  • chiama il costruttore della classe base con i parametri richiesti (nel mio caso a EndpointConfiguration stato generato enum per passare al costruttore - forse la tua implementazione non avrà questo)
  • Definisce le intestazioni che devono essere associate a ogni richiesta
  • Si attacca AddHttpHeaderMessageEndpointBehaviorai Endpointcomportamenti del cliente
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);
    }
}

Infine, usa il tuo client!

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

La richiesta HTTP risultante dovrebbe contenere le intestazioni HTTP e avere un aspetto simile al seguente:

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>

-1

Un po 'in ritardo alla festa, ma Juval Lowy affronta questo esatto scenario nel suo libro e nel ServiceModelEx associato libreria .

Fondamentalmente definisce le specializzazioni ClientBase e ChannelFactory che consentono di specificare valori di intestazione sicuri. Suggerisco di scaricare il sorgente e guardare le classi HeaderClientBase e HeaderChannelFactory.

John


1
Questo non è altro che promuovere il lavoro di qualcuno. Potresti aggiungere un estratto / algoritmo pertinente - ovvero rispondere alla domanda - o rivelare qualsiasi affiliazione che hai? Altrimenti si tratta solo di spam inventato.
Finanzi la causa di Monica il

Direi che sta dando a qualcuno una risposta indicando un approccio di cui potrebbero non essere a conoscenza. Ho fornito il link pertinente perché dovrei aggiungere altro? è tutto nei riferimenti. E sono sicuro che Juval Lowy potrebbe descriverlo meglio di quanto potrei mai fare :-) Per quanto riguarda la mia affiliazione, ho comprato il libro! Questo è tutto. Non ho mai incontrato il signor Lowy, ma sono sicuro che sia un ragazzo fantastico. Apparentemente sa molto di WCF ;-)
BrizzleOwl

Dovresti aggiungere altro perché presumibilmente leggi Come rispondere prima di rispondere e hai notato la sezione che dice "Cita sempre la parte più rilevante di un link importante, nel caso in cui il sito di destinazione sia irraggiungibile o rimanga permanentemente offline". La tua affiliazione non è importante. Solo la qualità della risposta è.
Finanzi la causa di Monica il

Belle. Non ci sono per i punti - come probabilmente puoi dire dal mio punteggio! Ho pensato che potesse essere un utile puntatore.
BrizzleOwl

1
Non sto dicendo che sia un cattivo puntatore. Sto dicendo che, da solo, non è una buona risposta. Può benissimo aiutare le persone, e questa è una buona cosa, ma la risposta sarà migliore se puoi descrivere il metodo che usa, piuttosto che dare una breve descrizione delle classi coinvolte. In questo modo, nel caso in cui non sia possibile accedere al sito - per qualsiasi motivo - la tua risposta è ancora utile.
Finanzi la causa di Monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.