Comment ajouter un en-tête HTTP personnalisé à chaque appel WCF?

162

J'ai un service WCF qui est hébergé dans un service Windows. Les clients qui utilisent ce service doivent transmettre un identifiant à chaque fois qu'ils appellent des méthodes de service (car cet identifiant est important pour ce que la méthode appelée doit faire). J'ai pensé que c'était une bonne idée de mettre cet identifiant dans les informations d'en-tête WCF.

Si c'est une bonne idée, comment puis-je ajouter automatiquement l'identifiant aux informations d'en-tête. En d'autres termes, chaque fois que l'utilisateur appelle la méthode WCF, l'identificateur doit être automatiquement ajouté à l'en-tête.

MISE À JOUR: les clients qui utilisent le service WCF sont à la fois des applications Windows et des applications Windows Mobile (utilisant Compact Framework).

mrtaikandi
la source
1
Avez-vous réussi à résoudre votre problème?
Mark Good le
Avez-vous fini par faire fonctionner cela sur le Compact Framework?
Vaccano

Réponses:

185

L'avantage de ceci est qu'il est appliqué à chaque appel.

Créez une classe qui implémente IClientMessageInspector . Dans la méthode BeforeSendRequest, ajoutez votre en-tête personnalisé au message sortant. Cela pourrait ressembler à ceci:

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

Créez ensuite un comportement de point de terminaison qui applique l'inspecteur de messages à l'environnement d'exécution du client. Vous pouvez appliquer le comportement via un attribut ou via une configuration à l'aide d'un élément d'extension de comportement.

Voici un excellent exemple de la façon d'ajouter un en-tête d'agent utilisateur HTTP à tous les messages de requête. J'utilise ceci dans quelques-uns de mes clients. Vous pouvez également faire de même du côté des services en implémentant IDispatchMessageInspector .

Est-ce ce que vous aviez en tête?

Mise à jour: j'ai trouvé cette liste de fonctionnalités WCF prises en charge par le framework compact. Je crois que les inspecteurs de messages classés comme «extensibilité des canaux» qui, selon ce post, sont pris en charge par le cadre compact.

Marquer bien
la source
2
@Mark, c'est une très bonne réponse. Merci. J'ai essayé cela sur net.tcp mais j'utilise directement la collection Headers (les en-têtes Http ne fonctionnaient pas). J'obtiens un en-tête avec mon jeton (nom) dans l'événement ServiceHost AfterReceiveRequest, mais pas la valeur (il ne semble même pas y avoir de propriété pour une valeur?). Y a-t-il quelque chose qui me manque? J'aurais attendu une paire nom / valeur car lorsque je crée l'en-tête, il me demande: request.Headers.Add (MessageHeader.CreateHeader (nom, ns, valeur));
Program.X
13
+1 OutgoingMessagePropertiessont ce dont vous avez besoin pour accéder aux en-têtes HTTP - pas OutgoingMessageHeadersqui sont des en-têtes SOAP.
SliverNinja - MSFT
1
Simplement, code génial! :)
abhilashca
3
Cela n'autorise qu'un agent utilisateur codé en dur, qui - selon l'exemple donné - est codé en dur dans le fichier web.config!
KristianB
1
C'est une excellente réponse. Il gère également le cas où le HttpRequestMessageProperty.Name n'est pas encore disponible dans les propriétés du message. Pour une raison quelconque, en déboguant mon code, j'ai réalisé qu'en fonction de certains problèmes de synchronisation, cette valeur n'était pas toujours présente. Merci Mark!
carlos357
80

Vous l'ajoutez à l'appel en utilisant:

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
}

Et puis, côté serveur, vous le récupérez en utilisant:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");
AgileJon
la source
5
Merci pour votre extrait de code. Mais avec cela, je dois ajouter l'en-tête chaque fois que je veux appeler une méthode. Je voulais rendre ce processus transparent. Je veux dire avec une mise en œuvre unique, chaque fois que l'utilisateur crée un client de service et utilise une méthode, l'en-tête client est automatiquement ajouté au message.
mrtaikandi
Ceci est un bon lien MSDN avec un exemple pour développer la suggestion fournie dans cette réponse: msdn.microsoft.com/en-us/library/…
atconway
1
Merci, c'est un excellent morceau de code si vous utilisez une bibliothèque cliente personnalisée. De cette façon, vous n'avez pas besoin d'implémenter l'inspecteur de message. Créez simplement une méthode wrapper commune qui encapsule chaque appel client dans le OperationContextScope.
JustAMartin
3
En guise de note, ceci est problématique si vous faites une sorte de truc asynchrone avec vos appels, parce que OperationContextScope(et OperationContext) sont ThreadStatic- la réponse de Mark Good fonctionnera sans compter sur des ThreadStaticéléments.
zimdanen
2
Cela n'ajoute pas d'en-tête HTTP! Il ajoute des en-têtes à l'enveloppe SOAP.
br3nt
32

Si vous souhaitez simplement ajouter le même en-tête à toutes les demandes du service, vous pouvez le faire sans aucun codage!
Ajoutez simplement le nœud d'en-têtes avec les en-têtes requis sous le nœud de point de terminaison dans votre fichier de configuration client

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  
Nimesh Madhavan
la source
18
Ce sont des en-têtes SOAP ( alaMessageHeader ) et non des en-têtes HTTP.
SliverNinja - MSFT
18

Voici une autre solution utile pour ajouter manuellement des en-têtes HTTP personnalisés à votre demande WCF client en utilisant le ChannelFactorycomme proxy. Cela devrait être fait pour chaque requête, mais cela suffit comme une simple démonstration si vous avez juste besoin de tester unitaire votre proxy en préparation pour les plates-formes 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... 
}
SliverNinja - MSFT
la source
1
J'ai essayé 4 autres suggestions similaires et c'est la seule qui a fonctionné pour moi.
JohnOpincar
Cela ajoute en fait des en-têtes HTTP, merci! :) Mais bon sang, c'est du code moche.
br3nt
11

Ceci est similaire à la réponse NimsDotNet mais montre comment le faire par programme.

Ajoutez simplement l'en-tête à la reliure

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
la source
J'ai ce code ajouté à mon appel en cours (côté client). Comment obtenir cette valeur principale dans le System.ServiceModel.OperationContext? (côté serveur) (je croise les doigts pour que cela m'aide)
granadaCoder
1
Je l'ai ! System.ServiceModel.Channels.MessageHeaders headers = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", string.Empty); var requestName = (headerIndex <0)? "INCONNU": headers.GetHeader <string> (headerIndex);
grenadeCoder
1
@granadaCoder J'adore ce site! ;-)
ΩmegaMan
Cela ajoute un en-tête à l'enveloppe SOAP, pas un en-tête HTTP
br3nt
5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });
shepcom
la source
12
Il s'agit d'un en-tête de message SOAP, pas d'un en-tête HTTP.
René
3

C'est ce qui a fonctionné pour moi, adapté de l' ajout d'en-têtes HTTP aux appels 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)
    {
    }
}

Après avoir déclaré ces classes, vous pouvez ajouter le nouveau comportement à votre client WCF comme ceci:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());
paulwhit
la source
Cela ne compilera pas: Erreur CS0136 Un local ou un paramètre nommé «propriété» ne peut pas être déclaré dans cette étendue car ce nom est utilisé dans une étendue locale englobante pour définir un paramètre local ou.
Leszek P
il suffit de retirer celui qui n'est pas utilisé
kosnkov
3

Cela fonctionne pour moi

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
la source
2

Les liaisons de contexte dans .NET 3.5 peuvent être exactement ce que vous recherchez. Il y en a trois prêts à l'emploi: BasicHttpContextBinding, NetTcpContextBinding et WSHttpContextBinding. Le protocole de contexte transmet essentiellement des paires clé-valeur dans l'en-tête du message. Consultez l'article Managing State With Durable Services sur le magazine MSDN.

Mehmet Aras
la source
Notez également que vous ne définissez le contexte qu'une seule fois avant d'établir une session avec le serveur. Ensuite, le contexte devient en lecture seule. Si vous voulez que la configuration du contexte soit transparente côté client, vous pouvez dériver de la classe proxt client et dans le constructeur, vous pouvez ajouter les informations qui composent votre contexte. Ensuite, chaque fois que le client crée une instance du client proxt, le contexte sera automatiquement créé et ajouté à l'instance de proxy client.
Mehmet Aras
2

Si je comprends bien votre exigence, la réponse simple est: vous ne pouvez pas.

En effet, le client du service WCF peut être généré par tout tiers qui utilise votre service.

SI vous avez le contrôle des clients de votre service, vous pouvez créer une classe client de base qui ajoute l'en-tête souhaité et hérite du comportement sur les classes de travail.

Paulo Santos
la source
1
d'accord, si vous créez vraiment SOA, vous ne pouvez pas supposer que tous les clients sont basés sur .NET. Attendez que votre entreprise soit acquise.
SliverNinja - MSFT
2
Est-ce vraiment vrai? Les clients de service Web Java n'ont pas la possibilité d'ajouter un nom / des valeurs aux en-têtes SOAP? Je trouve cela difficile à croire. Bien sûr, ce serait une implémentation différente, mais c'est une solution interopérable
Adam
2

Vous pouvez spécifier des en-têtes personnalisés dans MessageContract .

Vous pouvez également utiliser des en - têtes <endpoint> qui sont stockés dans le fichier de configuration et qui seront copiés dans l'en-tête de tous les messages envoyés par le client / service. Ceci est utile pour ajouter facilement un en-tête statique.

Philippe
la source
3
Ce sont des en-têtes SOAP ( alaMessageHeader ) et non des en-têtes HTTP.
SliverNinja - MSFT
0

Si vous souhaitez ajouter des en-têtes HTTP personnalisés à chaque appel WCF d'une manière orientée objet, ne cherchez pas plus loin.

Tout comme dans la réponse de Mark Good et paulwhit, nous devons créer une sous-classe IClientMessageInspectorpour injecter les en-têtes HTTP personnalisés dans la requête WCF. Cependant, rendons l'inspecteur plus générique en acceptant un dictionnaire contenant les en-têtes que nous voulons ajouter:

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

Tout comme dans la réponse de Mark Good et paulwhit, nous devons créer une sous-classe IEndpointBehaviorpour l'injecter HttpHeaderMessageInspectordans notre 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 ...
}

La dernière partie nécessaire pour terminer notre approche orientée objet consiste à créer une sous-classe de notre client généré automatiquement WCF (j'ai utilisé le guide de référence du service Web WCF de Microsoft pour générer un client WCF).

Dans mon cas, je dois attacher une clé API à l'en- x-api-keytête HTML.

La sous-classe effectue les opérations suivantes:

  • appelle le constructeur de la classe de base avec les paramètres requis (dans mon cas un EndpointConfiguration énumération a été générée pour passer au constructeur - peut-être que votre implémentation ne l'aura pas)
  • Définit les en-têtes qui doivent être attachés à chaque demande
  • S'attache AddHttpHeaderMessageEndpointBehavioraux Endpointcomportements du client
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);
    }
}

Enfin, utilisez votre client!

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

La requête HTTP résultante doit contenir vos en-têtes HTTP et ressembler à ceci:

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
la source
-1

Un peu tard à la fête mais Juval Lowy aborde ce scénario exact dans son livre et le ServiceModelEx associé bibliothèque .

Fondamentalement, il définit les spécialisations ClientBase et ChannelFactory qui permettent de spécifier des valeurs d'en-tête de type sécurisé. Je suggère de télécharger la source et de regarder les classes HeaderClientBase et HeaderChannelFactory.

John

BrizzleHibou
la source
1
Ce n'est à peu près rien d'autre que de promouvoir le travail de quelqu'un. Pourriez-vous ajouter un extrait / algorithme pertinent - c'est-à-dire répondre à la question - ou divulguer toute affiliation que vous avez? Sinon, ce n'est que du spam imaginé.
Fund Monica's Lawsuit
Je dirais qu'il s'agit de donner une réponse à quelqu'un en guise d'indicateur sur une approche dont il n'est peut-être pas au courant. J'ai donné le lien pertinent, pourquoi devrais-je en ajouter davantage? tout est dans les références. Et je suis sûr que Juval Lowy pourrait le décrire mieux que je ne pourrais jamais le faire :-) Quant à mon affiliation - j'ai acheté le livre! C'est tout. Je n'ai jamais rencontré M. Lowy mais je suis sûr que c'est un bon gars. En sait beaucoup sur la WCF apparemment ;-)
BrizzleOwl
Vous devriez en ajouter plus, car vous avez probablement lu Comment répondre avant de répondre, et vous avez noté la section qui dit "Toujours citer la partie la plus pertinente d'un lien important, au cas où le site cible serait inaccessible ou serait définitivement hors ligne." Votre affiliation n'est pas importante. Seule la qualité de la réponse l'est.
Fund Monica's Lawsuit
Bien. Je ne suis pas là pour les points - comme vous pouvez probablement le constater d'après mon score! Je pensais juste que cela pourrait être un indicateur utile.
BrizzleOwl
1
Je ne dis pas que c'est un mauvais indicateur. Je dis que, en soi, ce n'est pas une bonne réponse. Cela peut très bien aider les gens, et c'est une bonne chose, mais la réponse sera meilleure si vous pouvez décrire la méthode qu'il utilise, plutôt que de donner une très brève description des classes concernées. De cette façon, à l'occasion que le site ne soit pas accessible - pour quelque raison que ce soit - votre réponse est toujours utile.
Fund Monica's Lawsuit