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:
- WCF ne pas attendre un message SOAP
- La possibilité de formater les messages de requête et de réponse exactement au besoin
- Tous les messages à considérer pour le traitement
- 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.
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
@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