2010-12-14 4 views
10

Sto lavorando con WCF per scambiare messaggi con un'azienda di terze parti. I messaggi devono essere inviati e ricevuti in una busta corrispondente allo ebXML specification. Idealmente mi piacerebbe utilizzare il maggior numero possibile di stack WCF ed evitare l'approccio one method to process them all come in questo caso significherebbe scrivere di nuovo gran parte dell'infrastruttura di WCF.Come posso modificare WCF per elaborare i messaggi in un formato diverso (non SOAP)?

Per quanto posso vedere dalla mia ricerca iniziale questo mi richiederebbe di scrivere il mio binding personalizzato ma sto cercando di trovare chiarezza nella documentazione di MSDN.

Sono stato in grado di trovare molti documenti dettagliati sulle singole implementazioni di ciascuno di questi, ma molto poco su come mettere tutto insieme fino alla fine. Sembrerebbe che anche i libri che ho sono altrettanto chiari su questi argomenti senza menzione di ciò in "Pro WCF" di Peiris e Mulder.

Quello che sto cercando è qualcosa come il seguente.

I messaggi inviati e ricevuti DEVONO essere formattati come di seguito dove il nome del primo elemento è il nome dell'operazione che deve essere eseguita e l'elemento figlio è il carico utile del messaggio di richiesta sarebbe del formato:

<?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> 

E la risposta sarebbe:

<?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> 

come i messaggi sono tutti descritti da schemi XML ho usato XSD.exe convertire questi agli oggetti fortemente tipizzati. Vedi https://gist.github.com/740303 per gli schemi. Si noti che questi sono schemi di esempio. Non sono in grado di pubblicare i veri schemi senza violare gli accordi di riservatezza del cliente (né vorresti che anch'io, dato che sono enormi).

Vorrei ora piace essere in grado di scrivere l'implementazione del servizio nel seguente modo:

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

Qualsiasi aiuto sarebbe molto apprezzato.

+0

Potrebbe fornire un messaggio di richiesta e risposta completo (possibilmente con xsd) su gist.github.com o un servizio simile? – larsw

+0

@larsw Non posso fornire schemi ai miei clienti ma ho creato schemi per i miei messaggi di esempio e ho aggiornato la mia domanda con un collegamento a loro. La cosa fondamentale per me qui è l'aiuto con l'approccio generale e penso che i miei messaggi e schemi di esempio dovrebbero essere sufficienti per questo. – MikeD

risposta

4

Non penso che sia necessario fare nulla con le associazioni. Suppongo che tu debba comunque inviare il messaggio formattato ebXML su HTTP?

La risposta di @ ladislav è un approccio, ma penso che i codificatori di messaggi siano progettati per funzionare a un livello molto inferiore rispetto a quello che si sta tentando di ottenere. Sono essenzialmente i pezzi che codificano i messaggi verso e dallo stream sottostante (cioè, come il messaggio è rappresentato come byte sullo stream).

Penso che quello che devi fare è implementare un custom Message Formatter. In particolare, dal momento che dici di voler inviare i messaggi a una terza parte, penso che sia solo l'interfaccia IClientMessageFormatter che dovrai implementare. L'altra interfaccia (IDispatchMessageFormatter) viene utilizzata sul lato server.

Avrete anche bisogno di implementare un ServiceBehavior e OperationBehavior appropriati per installare il formattatore nello stack, ma il codice per questo sarà minimo (la maggior parte del codice sarà implementando l'interfaccia sopra menzionata).

Una volta implementato, è possibile utilizzare l'approccio "un metodo per elaborarli tutti" per testare e eseguire il debug del formattatore. Basta prendere il messaggio ricevuto e scaricarlo nella console per la revisione, quindi inviare anche una risposta ebXML. Potresti anche utilizzare lo stesso approccio per sviluppare i tuoi test unitari.

+0

Mi dispiace che abbia scritto per sbaglio che ho solo bisogno di inviare questi messaggi. L'ho corretto. Ho bisogno di ricevere anche questi messaggi.Devo assicurarmi che il messaggio ricevuto sia elaborato dal metodo di servizio corretto ma che richiede un'intestazione di azione. Sembrerebbe che l'unico modo per popolare l'intestazione dell'azione sia quello di fornire un binding a meno che non mi sia sfuggito qualcosa. – MikeD

+0

@MikeD OK, quindi lavorerai anche sul lato server. In tal caso, è anche necessario implementare l'interfaccia IDispatchMessageFormatter e quindi sarà necessario anche un modo per associare il messaggio in arrivo con l'operazione corretta. Penso che questo possa essere fatto usando un IDispatchOperationSelector, ma al momento non sono sicuro di come lo introduci nello stack (probabilmente attraverso il Service/OperationBehavior che userai per aggiungere nel formatter). –

+0

Sto pensando che sei proprio qui. Ho sperimentato un IDispatchOperationSelector personalizzato e, se questo è associato a un Filtro contratto che soddisfa solo tutti i messaggi, non avrei bisogno dell'estensione di associazione. Ci proverò questo pomeriggio. – MikeD

1

Per il formato dei messaggi personalizzato è necessario Personalizzato MessageEncoder. MSDN contiene un esempio su come creare un encoder personalizzato. Se usi Reflector troverai diverse implementazioni di encoder in modo che tu possa imparare a scriverlo.

È inoltre possibile controllare cosa accadrà se si tenta di utilizzare TextMessageEncoder con MessageVersion.None (non l'ho mai provato).

+0

mmka Questo è un ottimo suggerimento. Ho osservato le stesse linee usando TextMessageEncoder con MessageVersion.None funziona. Tutto ciò di cui ho bisogno ora è un modo per ottenere l'azione che è il nome del primo elemento ... – MikeD

10

dettagli della mia implementazione della risposta di Tim

avevo bisogno di scrivere questo per il cliente Attualmente sto lavorando per così ho pensato che potrei pure postare qui anche. Spero che aiuti qualcuno. Ho creato un client e un servizio di esempio che ho utilizzato per provare alcune di queste idee. L'ho ripulito e l'ho aggiunto a github. È possibile download it here.

Le cose seguenti necessari per essere attuati per consentire WCF da utilizzare nel modo in cui avevo bisogno:

  1. WCF di non aspettarsi un messaggio SOAP
  2. La possibilità di formattare i messaggi di richiesta e di risposta esattamente come richiesto
  3. Tutti i messaggi da considerare per l'elaborazione
  4. I messaggi in arrivo per essere indirizzati al corretto funzionamento di gestirli

1. Configurare WCF di non aspettarsi un messaggio SOAP

Il primo passo è stato sempre il messaggio in arrivo attraverso il TextMessageEncoder. Ciò è stato ottenuto utilizzando un'associazione personalizzata con l'impostazione MessageVersion.None sull'elemento textMessageEncoding.

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

2. Formato correttamente il messaggio

Il formattatore messaggio è richiesto come il messaggio in arrivo rifiutato di essere de-serializzati dal formattatore XML esistente senza l'aggiunta di attributi aggiuntivi sui contratti dei messaggi. Questo normalmente non sarebbe un problema, ma l'esecuzione dei miei schemi ebXML tramite XSD.exe genera un file cs di 33000 righe e non ho voluto modificarlo in alcun modo. Inoltre, le persone si dimenticheranno di aggiungere nuovamente gli attributi in futuro, quindi questo rende la manutenzione più facile.

Il formattatore personalizzato si aspetta di convertire il messaggio in arrivo nel tipo del primo parametro e di convertire il tipo di ritorno in un messaggio di risposta. Ispeziona il metodo di implementazione per determinare i tipi del primo parametro e il valore restituito nel costruttore.

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

Quindi utilizza semplicemente XmlSerializer per serializzare e deserializzare i dati. Una parte interessante di questo per me è stata l'uso di una BodyWriter personalizzata per serializzare l'oggetto nell'oggetto Messaggio. Di seguito è parte dell'implementazione per il serializzatore di servizio. L'implementazione del client è il contrario.

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.Elabora tutti i messaggi in arrivo

Per consentire a WCF di consentire l'elaborazione di tutti i messaggi in arrivo, era necessario impostare la proprietà ContractFilter su endpointDispatcher su un'istanza di MatchAllMessageFilter. Ecco uno snippet che illustra questo aspetto dalla mia configurazione del comportamento degli endpoint.

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

4. Selezione il corretto funzionamento

Ciò è stato possibile attraverso la creazione di una classe che implementa IDispatchOperationSelector. Nel metodo SelectOperation ispeziono il messaggio in arrivo. Qui sto cercando due cose: 1. Un controllo di integrità controlla che lo spazio dei nomi dell'elemento principale corrisponda allo spazio dei nomi del contratto di servizio 2. Il nome dell'elemento root (notare l'uso di LocalName per rimuovere qualsiasi prefisso dello spazio dei nomi)

Il nome dell'elemento radice è il valore restituito e che verrà associato a qualsiasi operazione sul contratto con un nome corrispondente o dove l'attributo azione ha un valore corrispondente.

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

avvolgendolo All Up

Per rendere più semplice per distribuire ho creato un comportamento endpoint per gestire la configurazione del selettore messaggio formattatore, il filtro del contratto e il funzionamento. Potrei anche aver creato un binding per concludere la configurazione del binding personalizzato ma non pensavo che quella parte fosse troppo difficile da ricordare.

Una scoperta interessante per me era che il comportamento dell'endpoint può impostare il formattatore dei messaggi per tutte le operazioni nell'endpoint per utilizzare un formattatore di messaggi personalizzato. Ciò evita la necessità di configurarli separatamente. Ho preso questo da uno dei Microsoft samples.

documentazione link utili

Le migliori referenze che ho trovato finora sono gli articoli di riviste Service Station MSDN ("del sito: msdn.microsoft.com stazione di servizio WCF" Google).

WCF Bindings in Depth - informazioni molto utili sulle associazioni configurazione

Extending WCF with Custom Behaviours - La migliore fonte di informazioni sui punti di integrazione dispatcher che ho ancora trovato e contengono alcuni diagrammi veramente utili che illustrano tutti i punti di integrazione e dove si verificano in l'ordine di elaborazione.

Microsoft WCF samples - C'è molto qui che non è documentato molto bene altrove. Ho trovato la lettura attraverso il codice sorgente per alcuni di questi molto istruttivo.

+0

Mi sembra buono! Immagino che sia ancora necessario selezionare una codifica appropriata quando si implementa un formattatore di messaggi personalizzati e suppongo che il binding HTTP ti fornisca uno SOAP uno per impostazione predefinita. L'implementazione del formattatore del messaggio è ciò che avrei anche dovuto fare (delegando al serializer XML standard della bog). Anche se l'uso di un EndpointBehavior è qualcosa che non ho pensato, ma credo che renderà le cose più facili. Ho +1 la tua risposta perché ti meriti il ​​rappresentante per lo sforzo nel pubblicare la tua soluzione. Daremo anche un'occhiata a GitHub. –

Problemi correlati