Comment puis-je modifier WCF aux messages de processus dans un autre format (non SOAP)?

StackOverflow https://stackoverflow.com/questions/4437660

  •  09-10-2019
  •  | 
  •  

Question

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 à la ebXML . Idéalement, je voudrais utiliser autant de la pile de WCF que possible et éviter le une méthode pour les traiter toutes les approche comme dans ce cas, cela signifierait écrire une grande partie de l'infrastructure de WCF à nouveau.

Pour autant que je peux voir de mes premières recherches cela me obligerait à écrire ma propre liaison personnalisée mais je me bats pour trouver la clarté dans la documentation MSDN.

J'ai pu trouver un bon nombre de documents détaillés au sujet des mises en œuvre individuelles de chacun d'entre eux, mais très peu de choses sur la façon de mettre tout bout à bout de. Il semblerait que les livres que j'ai sont tout aussi la lumière sur ces sujets aussi sans mention dans « WCF Pro » par Peiris et Mulder.

Qu'est-ce que je visais 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 demande 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, j'ai utilisé XSD.exe pour les convertir en des objets fortement typés. Voir https://gist.github.com/740303 pour les schémas. Notez que ce sont des schémas d'exemple. Je ne suis pas en mesure d'afficher les véritables schémas sans enfreindre les accords de confidentialité client (pas plus que tu me veux aussi qu'ils sont énormes).

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

Était-ce utile?

La solution

Je ne pense pas que vous devez faire quoi que ce soit avec les fixations. Je suppose que vous devez envoyer le message au format ebXML sur HTTP de toute façon?

@ réponse de Ladislav est une approche, mais je pense que les codeurs de messages sont conçus pour fonctionner à un niveau beaucoup plus bas que ce que vous essayez d'atteindre. Ils sont essentiellement les pièces qui codent les messages vers et à partir du flux sous-jacent (à savoir, comment le message est représenté sous forme d'octets sur le flux).

Je pense que ce que vous devez faire est de mettre en œuvre un message personnalisé Formatter. En particulier, puisque vous dites que vous voulez soumettre les messages à un tiers, alors je pense que c'est que l'interface IClientMessageFormatter que vous aurez besoin de mettre en œuvre. L'autre interface (IDispatchMessageFormatter) est utilisé sur le côté serveur.

Vous aurez également besoin de mettre en œuvre un ServiceBehavior et OperationBehavior approprié pour installer le formatter dans la pile, mais le code de ce sera minime (la majeure partie du code sera mise en œuvre ci-dessus l'interface mentionnée).

Une fois mis en œuvre, vous pouvez utiliser la « une méthode pour les traiter tous » approche pour tester et déboguer votre formatter. Il suffit de prendre le message reçu et le jeter sur la console pour vous de passer en revue, puis envoyer également un retour de réponse ebXML. Vous pouvez également utiliser la même approche pour construire votre unité de test.

Autres conseils

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

Je devais écrire ce pour le client je travaille actuellement, donc je pensais que je pourrais aussi bien le poster ici aussi. J'espère que cela aide quelqu'un. J'ai créé un client échantillon et le service que je l'habitude d'essayer quelques-unes de ces idées. Je l'ai nettoyé et ajouté à GitHub. Vous pouvez télécharger ici .

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 comme requis
  3. Tous les messages à considérer pour le traitement
  4. Les messages entrants à être acheminés au bon fonctionnement de les manipuler

1. Configurer WCF ne pas attendre un message SOAP

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

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

2. Formater le message correctement

Le message formatter est nécessaire 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. Ce ne serait pas normalement un problème, mais en cours d'exécution des schémas de mes clients par le biais XSD.exe génère un fichier cs ligne 33000 et je ne voulais pas avoir à modifier de quelque façon que ce. En plus les gens oublient ajouter de nouveau les attributs à l'avenir si cela rend plus facile l'entretien, espérons aussi.

Le formatter personnalisé prévoit de convertir le message entrant au type du premier paramètre et de convertir le type de retour dans un message de réponse. Il inspecte la méthode mise en œuvre 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 alors simplement le XmlSerializer pour sérialiser et désérialiser les données. Une partie intéressante de ce pour moi était l'utilisation d'un BodyWriter personnalisé pour sérialiser l'objet dans l'objet du message. Ci-dessous fait partie de la mise en œuvre du sérialiseur de service. La mise en œuvre 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. Processus Tous les messages entrants

Afin de donner des instructions WCF pour permettre à tous les messages entrants à traiter que je devais définir la propriété ContractFilter sur le EndpointDispatcher à une instance du MatchAllMessageFilter. Voici un extrait illustrant ceci de ma configuration de comportement de point final.

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 suis à la recherche de deux choses:  1. Une vérification de bon sens 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 noms)

Le nom de l'élément racine est la valeur de retour et qui tracera toute opération sur le contrat avec un nom correspondant ou lorsque l'attribut d'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. Je pourrais également avoir créé une reliure pour envelopper 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 était que le comportement des terminaux peut définir les messages formatter pour toutes les opérations du point final d'utiliser un formatter de message personnalisé. Cela permet d'économiser la nécessité de configurer séparément. Je pris cette place de l'un des Microsoft échantillons .

Liens utiles Documentation

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 Manchettes de profondeur - des informations très utiles sur la configuration des liaisons

Extension WCF sur mesure Behaviors - La meilleure source d'information sur 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.

échantillons Microsoft WCF - Il y a beaucoup ici qui ne sont pas très bien documenté ailleurs. J'ai trouvé la lecture à travers le code source pour certains d'entre eux très instructif.

Pour le format de message personnalisé dont vous avez besoin personnalisé MessageEncoder . MSDN contient par exemple comment créer encodeur personnalisé. Si vous utilisez réflecteur , vous trouverez plusieurs implémentations de l'encodeur afin que vous puissiez apprendre comment 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 tryed).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top