2010-12-14 61 views
10

Je travaille avec WCF pour échanger des messages avec une société tierce. Les messages doivent être envoyés et reçus dans une enveloppe qui correspond à ebXML specification. Idéalement, je voudrais utiliser autant de la pile WCF que possible et éviter l'approche one method to process them all comme dans ce cas qui signifierait écrire une grande partie de l'infrastructure de WCF à nouveau. Autant que je puisse voir de mes recherches initiales cela exigerait que j'écrive ma propre liaison personnalisée mais j'ai du mal à trouver la clarté dans la documentation dans MSDN.Comment modifier WCF pour traiter les messages dans un format différent (non SOAP)?

J'ai été capable de trouver beaucoup de documents détaillés sur les implémentations individuelles de chacun d'entre eux, mais très peu sur la façon de tout mettre bout à bout. Il semblerait que les livres que j'ai sont pareillement légers sur ces sujets aussi sans aucune mention de cela dans "Pro WCF" par Peiris et Mulder.

Ce que je vise est quelque chose comme ce qui suit. Les messages envoyés et reçus DOIVENT être formatés comme ci-dessous où le nom du premier élément est le nom de l'opération qui doit être exécutée et l'élément enfant est la charge utile du message de requête serait de la forme:

<?xml version="1.0" encoding="UTF-8"?> 
<op:DoSomething xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com"> 
    <op:AnObject> 
     <payload:ImportantValue>42</payload:ImportantValue> 
    </op:AnObject> 
</op:DoSomething> 

Et la réponse serait:

<?xml version="1.0" encoding="UTF-8"?> 
<op:AcknowledgementResponse xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com"> 
    <op:ResponseObject> 
     <payload:Ok>True</payload:Ok> 
    </op:ResponseObject> 
</op:AcknowledgementResponse> 

Comme les messages sont tous décrits par des schémas XML Je l'ai utilisé pour convertir ces XSD.exe à des objets fortement typés. Voir https://gist.github.com/740303 pour les schémas. Notez que ce sont des exemples de schémas. Je ne suis pas capable de publier les vrais schémas sans enfreindre les accords de confidentialité des clients (et vous ne voudriez pas non plus que je sois trop grand).

Je voudrais maintenant pouvoir écrire la mise en œuvre de service comme suit:

public class MyEndpoint : IMyEndpoint 
{ 
    public AcknowledgementResponse DoSomething(AnObject value) 
    { 
     return new AcknowledgementResponse 
      { 
       Ok = True; 
      }; 
    } 
} 

Toute aide serait très apprécié.

+0

Pourriez-vous fournir une requête complète et un message de réponse (éventuellement avec xsd) sur gist.github.com ou un service similaire? – larsw

+0

@larsw Je ne peux pas fournir les schémas de mes clients mais j'ai créé des schémas pour mes exemples de messages et j'ai mis à jour ma question avec un lien vers eux. L'élément clé pour moi ici est l'aide avec l'approche générale et je pense que mes exemples de messages et de schémas devraient suffire pour cela. – MikeD

Répondre

4

Je ne pense pas que vous ayez besoin de faire quoi que ce soit avec des fixations. Je suppose que vous avez besoin d'envoyer le message formaté ebXML sur HTTP de toute façon?

@ La réponse de ladislav est une approche, mais je pense que les encodeurs de message sont conçus pour fonctionner à un niveau beaucoup plus bas que ce que vous essayez d'atteindre. Ce sont essentiellement les éléments qui codent les messages vers et depuis le flux sous-jacent (c'est-à-dire, la façon dont le message est représenté sous la forme d'octets sur le flux).

Je pense que ce que vous devez faire est d'implémenter un custom Message Formatter. En particulier, puisque vous dites que vous voulez soumettre les messages à un tiers, je pense que c'est seulement l'interface IClientMessageFormatter que vous devrez implémenter. L'autre interface (IDispatchMessageFormatter) est utilisée côté serveur.

Vous devrez également implémenter un ServiceBehavior et un OperationBehavior appropriés pour installer le formateur dans la pile, mais le code pour cela sera minimal (la majeure partie du code sera dans l'implémentation de l'interface mentionnée ci-dessus). Une fois implémenté, vous pouvez utiliser l'approche "une méthode pour les traiter tous" pour tester et déboguer votre formateur. Il suffit de prendre le message reçu et de le télécharger sur la console pour que vous puissiez l'examiner, puis renvoyer une réponse ebXML. Vous pouvez également utiliser la même approche pour développer vos tests unitaires.

+0

Désolé, j'ai accidentellement écrit que j'ai juste besoin d'envoyer ces messages. J'ai corrigé ça. J'ai besoin de recevoir ces messages aussi.Je dois m'assurer que le message reçu est traité par la méthode de service correcte, mais cela nécessite un en-tête d'action. Il semblerait que la seule façon de peupler l'en-tête de l'action est de le fournir dans une liaison à moins que j'ai manqué quelque chose. – MikeD

+0

@MikeD OK, donc vous travaillerez aussi du côté serveur. Dans ce cas, vous devrez également implémenter l'interface IDispatchMessageFormatter, puis vous aurez également besoin d'un moyen d'associer le message entrant à l'opération correcte. Je pense que cela peut être fait en utilisant un IDispatchOperationSelector, mais je ne suis pas sûr pour le moment sur la façon dont vous introduisez cela dans la pile (probablement à travers le Service/OperationBehavior que vous utiliserez pour ajouter dans le formateur). –

+0

Je pense que vous êtes ici. J'ai expérimenté avec un IDispatchOperationSelector personnalisé et si cela est associé à un filtre de contrat qui correspond à tous les messages, je ne devrais pas avoir besoin de l'extension de liaison. Je vais essayer ça cet après-midi. – MikeD

1

Pour le format de message personnalisé, vous devez personnaliser MessageEncoder. MSDN contient un exemple comment créer un encodeur personnalisé. Si vous utilisez Reflector, vous trouverez plusieurs implémentations d'encodeur afin que vous puissiez apprendre à l'écrire.

Vous pouvez également vérifier ce qui se passera si vous essayez d'utiliser TextMessageEncoder avec MessageVersion.None (je ne l'ai jamais essayé).

+0

mmka C'est une excellente suggestion. J'ai regardé dans le même sens en utilisant TextMessageEncoder avec MessageVersion.None qui fonctionne. Tout ce dont j'ai besoin maintenant est un moyen d'obtenir l'action qui est le nom du premier élément ... – MikeD

10

Détails de ma mise en œuvre de la réponse de Tim

je besoin d'écrire ce pour le client je travaille actuellement, donc je pensais que je pourrais aussi bien le poster ici aussi. J'espère que ça aide quelqu'un. J'ai créé un exemple de client et de service que j'ai utilisé pour essayer certaines de ces idées. Je l'ai nettoyé et ajouté à github. Vous pouvez download it here.

Les choses suivantes devaient être mises en œuvre pour permettre WCF à utiliser de la manière que je requis:

  1. WCF ne pas attendre un message SOAP
  2. La possibilité de formater les messages de requête et de réponse exactement au besoin
  3. Tous les messages à considérer pour le traitement
  4. les messages entrants à être acheminés au bon fonctionnement de les traiter

1. Configurer WCF ne pas attendre un message SOAP

La première étape a été faire passer le message entrant par la TextMessageEncoder. Cela a été réalisé en utilisant une liaison personnalisée avec le paramètre MessageVersion.None sur l'élément textMessageEncoding.

<customBinding> 
    <binding name="poxMessageBinding"> 
     <textMessageEncoding messageVersion="None" /> 
     <httpTransport /> 
    </binding> 
    </customBinding> 

2. Format du message correctement

Le formatter message est requis que le message entrant a refusé d'être désérialisé par le formatter XML existant sans ajout d'attributs supplémentaires sur les contrats de message. Cela ne poserait normalement pas de problème, mais l'exécution des schémas ebXML de mes clients via XSD.exe génère un fichier cs de 33 000 lignes et je ne voulais pas avoir à le modifier de quelque façon que ce soit. De plus, les gens oublient de rajouter les attributs à l'avenir, ce qui rend la maintenance plus facile.

Le formateur personnalisé s'attend à convertir le message entrant en le type du premier paramètre et à convertir le type de retour en un message de réponse. Il inspecte la méthode d'implémentation pour déterminer les types du premier paramètre et la valeur de retour dans le constructeur.

public SimpleXmlFormatter(OperationDescription operationDescription) 
{ 
    // Get the request message type 
    var parameters = operationDescription.SyncMethod.GetParameters(); 
    if (parameters.Length != 1) 
     throw new InvalidDataContractException(
"The SimpleXmlFormatter will only work with a single parameter for an operation which is the type of the incoming message contract."); 
    _requestMessageType = parameters[0].ParameterType; 

    // Get the response message type 
    _responseMessageType = operationDescription.SyncMethod.ReturnType; 
} 

Il utilise ensuite simplement XmlSerializer pour sérialiser et désérialiser les données. Une partie intéressante de cela pour moi était l'utilisation d'un BodyWriter personnalisé pour sérialiser l'objet dans l'objet Message. Ci-dessous une partie de l'implémentation pour le sérialiseur de service. L'implémentation du client est l'inverse.

public void DeserializeRequest(Message message, object[] parameters) 
{ 
    var serializer = new XmlSerializer(_requestMessageType); 
    parameters[0] = serializer.Deserialize(message.GetReaderAtBodyContents()); 
} 

public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result) 
{ 
    return Message.CreateMessage(MessageVersion.None, _responseMessageType.Name, 
           new SerializingBodyWriter(_responseMessageType, result)); 
} 

private class SerializingBodyWriter : BodyWriter 
{ 
    private readonly Type _typeToSerialize; 
    private readonly object _objectToEncode; 

    public SerializingBodyWriter(Type typeToSerialize, object objectToEncode) : base(false) 
    { 
     _typeToSerialize = typeToSerialize; 
     _objectToEncode = objectToEncode; 
    } 

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer) 
    { 
     writer.WriteStartDocument(); 
     var serializer = new XmlSerializer(_typeToSerialize); 
     serializer.Serialize(writer, _objectToEncode); 
     writer.WriteEndDocument(); 
    } 
} 

3.Traiter tous les messages entrants

Pour que WCF autorise le traitement de tous les messages entrants, j'ai dû définir la propriété ContractFilter sur endpointDispatcher sur une instance de MatchAllMessageFilter. Voici un extrait illustrant ceci à partir de ma configuration de comportement de point de terminaison.

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) 
{ 
    endpointDispatcher.ContractFilter = new MatchAllMessageFilter(); 
    // Do more config ... 
} 

4. Sélection du bon fonctionnement

Ceci a été réalisé en créant une classe qui implémente IDispatchOperationSelector. Dans la méthode SelectOperation, j'inspecte le message entrant. Ici, je cherche deux choses: 1. Une vérification de la santé que l'espace de noms de l'élément racine est le même que l'espace de noms du contrat de service 2. Le nom de l'élément racine (notez l'utilisation de LocalName pour supprimer tout préfixe d'espace de nom)

Le nom de l'élément racine correspond à la valeur de retour. Il est mappé à toute opération du contrat avec un nom correspondant ou dans lequel l'attribut action a une valeur correspondante.

public string SelectOperation(ref Message message) 
{ 
    var messageBuffer = message.CreateBufferedCopy(16384); 

    // Determine the name of the root node of the message 
    using (var copyMessage = messageBuffer.CreateMessage()) 
    using (var reader = copyMessage.GetReaderAtBodyContents()) 
    { 
     // Move to the first element 
     reader.MoveToContent(); 

     if (reader.NamespaceURI != _namespace) 
      throw new InvalidOperationException(
"The namespace of the incoming message does not match the namespace of the endpoint contract."); 

     // The root element name is the operation name 
     var action = reader.LocalName; 

     // Reset the message for subsequent processing 
     message = messageBuffer.CreateMessage(); 

     // Return the name of the action to execute 
     return action; 
    } 
} 

Emballage It All Up

Pour le rendre plus facile à déployer que j'ai créé un comportement de point de terminaison pour gérer la configuration du formatter de message, filtre à contrat et Sélecteur de fonctionnement. J'aurais aussi pu créer une liaison pour boucler la configuration de liaison personnalisée mais je ne pensais pas que cette partie était trop difficile à retenir. Une découverte intéressante pour moi a été que le comportement du point de terminaison peut définir le formateur de messages pour toutes les opérations dans le point de terminaison pour utiliser un formateur de message personnalisé. Cela évite d'avoir à les configurer séparément. J'ai ramassé cela de l'un des Microsoft samples.

Documentation Liens utiles

Les meilleures références que j'ai trouvé à ce jour sont les articles de magazine MSDN Service Station (Google "site: station service msdn.microsoft.com WCF").

WCF Bindings in Depth - Des informations très utiles sur les liaisons configuration

Extending WCF with Custom Behaviours - La meilleure source d'informations sur les points d'intégration dispatcher que j'ai encore trouvé et ils contiennent des diagrammes très utiles qui illustrent tous les points d'intégration et où ils se produisent dans l'ordre de traitement.

Microsoft WCF samples - Il y a beaucoup de choses ici qui ne sont pas très bien documentées ailleurs. J'ai trouvé que lire le code source pour certains d'entre eux était très instructif.

+0

Ça me va bien! Je suppose que vous devez toujours sélectionner un encodage approprié lorsque vous implémentez un formateur de message personnalisé et je suppose que la liaison HTTP vous donnera une signature SOAP par défaut. L'implémentation du formateur de message est ce que j'avais aussi l'air d'avoir fait (en déléguant au sérialiseur XML standard de bog). L'utilisation d'un EndpointBehavior est cependant quelque chose que je n'ai pas pensé, mais je suppose que cela rend les choses plus faciles. J'ai +1 votre réponse parce que vous méritez le représentant pour l'effort de poster votre solution. Je vais aussi jeter un coup d'oeil sur GitHub. –