2016-03-26 61 views
12

Sto lavorando all'interfacciamento con un servizio SOAP che sembra non occuparsi degli spazi dei nomi predefiniti, ma funziona bene con gli spazi dei nomi globali e i prefissi dei namespace dichiarati a livello di busta SOAP.Client WCF: forzatura degli spazi dei nomi globali

Il problema è che WCF non crea questi spazi dei nomi globali nella radice, ma utilizza piuttosto spazi dei nomi predefiniti non prefissati che il servizio apparentemente sta soffocando. Ora so che questo non è un errore di WCF - credo che i messaggi generati dalla WCF siano XML validi, ma il servizio lo soffoca comunque.

Uso WCF L'output è generato in questo modo:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 
    <s:Header> 
    <h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis- 
      ... 
    </h:Security> 
    </s:Header> 
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <cancelShipmentRequest xmlns="http://www.royalmailgroup.com/api/ship/V2"> 
     <integrationHeader> 
     <dateTime xmlns="http://www.royalmailgroup.com/integration/core/V1">2016-03-26T01:44:37.0493801Z</dateTime> 
     <version xmlns="http://www.royalmailgroup.com/integration/core/V1">2</version> 
     <identification xmlns="http://www.royalmailgroup.com/integration/core/V1"> 
      <applicationId>RMG-API-G-01</applicationId> 
      <transactionId>ozhckwej6sxg</transactionId> 
     </identification> 
     </integrationHeader> 
     <cancelShipments> 
     <shipmentNumber>TTT001908905GB</shipmentNumber> 
     </cancelShipments> 
    </cancelShipmentRequest> 
    </s:Body> 
</s:Envelope> 

che non funziona.

Utilizzando il seguente envelope SOAP (manualmente SoapUI) non funziona comunque:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
    xmlns:v2="http://www.royalmailgroup.com/api/ship/V2" 
    xmlns:v1="http://www.royalmailgroup.com/integration/core/V1"> 
    <soapenv:Header> 
    <h:Security xmlns:h="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
     ... 
    </h:Security> 
    </soapenv:Header> 
    <soapenv:Body> 
    <v2:cancelShipmentRequest> 
     <v2:integrationHeader> 
     <v1:dateTime>2016-03-02T14:55:00Z</v1:dateTime> 
     <v1:version>2</v1:version> 
     <v1:identification> 
      <v1:applicationId>RMG-API-G-01</v1:applicationId> 
      <v1:transactionId>wftdaife96gv</v1:transactionId> 
     </v1:identification> 
     </v2:integrationHeader> 
     <v2:cancelShipments> 
     <v2:shipmentNumber>TTT001908905GB</v2:shipmentNumber> 
     </v2:cancelShipments> 
    </v2:cancelShipmentRequest> 
    </soapenv:Body> 
</soapenv:Envelope> 

La differenza tra i due è che i namespace V1 e V2 sono dichiarati globalmente nella parte superiore del documento e non ci sono dichiarazioni di spazio dei nomi locali nel secondo documento.

Forse mi manca qualcosa ma per me l'XML generato da WCF sembra valido e rappresenta lo stesso stato del documento in termini di namespace.

L'unica differenza che posso dire è che il modo in cui gli spazi dei nomi sono dichiarati. E anche se la versione di WCF sembra essere valida e produce lo stesso namespacing, il servizio si lamenta di riferimenti di spazio dei nomi non validi.

Impossibile convalida dello schema: messaggio non inviato la convalida dello schema: errore di validità schemi: Element 'xmlns': Questo elemento non è previsto. Previsto è ({http://www.royalmailgroup.com/api/ship/V2} integrationHeader).

La domanda è: qual è il modo migliore per forzare il WCF ad aggiungere i riferimenti dello spazio dei nomi nella parte superiore anziché in linea? L'unico modo che ho trovato finora è quello di usare un'ispettore messaggi e riscrivere esplicitamente il messaggio, ma se passo tutto ciò che potrei fare, creo i messaggi manualmente.

Qualche idea su cosa posso provare a imporre a WCF di utilizzare prefissi di namespace espliciti senza dover riscrivere manualmente i messaggi?

+0

Ho avuto la stessa situazione recentemente cercando di unire uno spazio dei nomi esistente con un nuovo spazio dei nomi .. Non posso dare la risposta definitiva. Quello che ho trovato è stato che dopo aver rimosso il tentativo di unire, ricostruire, uscire da VS e quindi integrare il codice esistente, tutto andava bene. Risposta zoppa, sono d'accordo. Ho concluso che si tratta di un bug in VS, ma troppo complicato per tentare uno scenario di riproduzione. Buona fortuna, e aspetto una soluzione migliore/risposta alla tua situazione. – JamieMeyer

+1

Hai provato i suggerimenti in [questo post del blog] (http://vanacosmin.ro/Articles/Read/WCFEnvelopeNamespacePrefix) o [questa risposta] (http://stackoverflow.com/a/17798306/124386)? –

+0

Grazie Richard. Il post del blog è stato utile e mi ha indirizzato nella giusta direzione. –

risposta

7

Quindi la risposta a questo problema è stato quello di creare un costume IClientMessageFormatter e Message poi ignorando l'Message.OnWriteStartEnvelope() di scrivere esplicitamente tutti gli spazi dei nomi alla radice del documento di sapone. Il documento sottoposto a rendering, quindi riutilizza questi spazi dei nomi anziché assegnare in modo esplicito spazi dei nomi agli elementi figlio.

Ci sono 3 classi da creare per questo lavoro:

  • Messaggio Attuazione che gestisce l'attuale OnWriteStartEnvelope()
  • IClientMessageFormatter che viene agganciato in WCF
  • FormatMessageAttribute di allegare a ciascun metodo del cliente

Ecco il codice per tutti e tre:

public class RoyalMailCustomMessage : Message 
{ 
    private readonly Message message; 

    public RoyalMailCustomMessage(Message message) 
    { 
     this.message = message; 
    } 
    public override MessageHeaders Headers 
    { 
     get { return this.message.Headers; } 
    } 
    public override MessageProperties Properties 
    { 
     get { return this.message.Properties; } 
    } 
    public override MessageVersion Version 
    { 
     get { return this.message.Version; } 
    } 

    protected override void OnWriteStartBody(XmlDictionaryWriter writer) 
    { 
     writer.WriteStartElement("Body", "http://schemas.xmlsoap.org/soap/envelope/"); 
    } 
    protected override void OnWriteBodyContents(XmlDictionaryWriter writer) 
    { 
     this.message.WriteBodyContents(writer); 
    } 
    protected override void OnWriteStartEnvelope(XmlDictionaryWriter writer) 
    { 
     writer.WriteStartElement("soapenv", "Envelope", "http://schemas.xmlsoap.org/soap/envelope/"); 
     writer.WriteAttributeString("xmlns", "oas", null, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); 
     writer.WriteAttributeString("xmlns", "v2", null, "http://www.royalmailgroup.com/api/ship/V2"); 
     writer.WriteAttributeString("xmlns", "v1", null, "http://www.royalmailgroup.com/integration/core/V1"); 
     writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance"); 
     writer.WriteAttributeString("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");    
    } 
} 

public class RoyalMailMessageFormatter : IClientMessageFormatter 
{ 
    private readonly IClientMessageFormatter formatter; 

    public RoyalMailMessageFormatter(IClientMessageFormatter formatter) 
    { 
     this.formatter = formatter; 
    } 

    public Message SerializeRequest(MessageVersion messageVersion, object[] parameters) 
    { 
     var message = this.formatter.SerializeRequest(messageVersion, parameters); 
     return new RoyalMailCustomMessage(message); 
    } 

    public object DeserializeReply(Message message, object[] parameters) 
    { 
     return this.formatter.DeserializeReply(message, parameters); 
    } 
} 


[AttributeUsage(AttributeTargets.Method)] 
public class RoyalMailFormatMessageAttribute : Attribute, IOperationBehavior 
{ 
    public void AddBindingParameters(OperationDescription operationDescription, 
     BindingParameterCollection bindingParameters) 
    { } 

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) 
    { 
     var serializerBehavior = operationDescription.Behaviors.Find<XmlSerializerOperationBehavior>(); 

     if (clientOperation.Formatter == null) 
      ((IOperationBehavior)serializerBehavior).ApplyClientBehavior(operationDescription, clientOperation); 

     IClientMessageFormatter innerClientFormatter = clientOperation.Formatter; 
     clientOperation.Formatter = new RoyalMailMessageFormatter(innerClientFormatter); 
    } 

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) 
    { } 

    public void Validate(OperationDescription operationDescription) { } 
} 

La maggior parte di questo è il codice di cerimonia e di codice. I pezzi del codice chiave sono OnWriteStartEnvelope dove sono collegati gli spazi dei nomi effettivi, SerializeRequest dove il formattatore è agganciato nella pipeline WCF e ApplyClientBehavior in cui il formattatore del messaggio è collegato all'operazione effettiva.

Per collegarlo, ho aggiunto l'attributo al metodo client sull'interfaccia di servizio, in questo caso nel mio client WCF generato in Reference.cs.

// CODEGEN: Generating message contract since the operation cancelShipment is neither RPC nor document wrapped. 
    [System.ServiceModel.OperationContractAttribute(Action="cancelShipment", ReplyAction="*")] 
    [System.ServiceModel.FaultContractAttribute(typeof(MarvelPress.Workflow.Business.RoyalShippingApi.exceptionDetails), Action="cancelShipment", Name="exceptionDetails")] 
    [System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)] 
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(contactMechanism))] 
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(baseRequest))] 
    [RoyalMailFormatMessage()] 
    MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentResponse1 cancelShipment(MarvelPress.Workflow.Business.RoyalShippingApi.cancelShipmentRequest1 request); 

messaggi generati dal WCF ora guarda come previsto con gli spazi dei nomi tutti definiti nella parte superiore del documento:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:oas="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:v2="http://www.royalmailgroup.com/api/ship/V2" xmlns:v1="http://www.royalmailgroup.com/integration/core/V1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
    <s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 
    <h:Security>...</h:Security> 
    </s:Header> 
    <soapenv:Body> 
    <v2:cancelShipmentRequest> 
     <v2:integrationHeader> 
     <v1:dateTime>2016-04-02T01:04:50.4122473Z</v1:dateTime> 
     <v1:version>2</v1:version> 
     <v1:identification> 
      <v1:applicationId>RMG-API-G-01</v1:applicationId> 
      <v1:transactionId>fshrxevdnc7n</v1:transactionId> 
     </v1:identification> 
     </v2:integrationHeader> 
     <v2:cancelShipments> 
     <v2:shipmentNumber>TTT001908905GB</v2:shipmentNumber> 
     </v2:cancelShipments> 
    </v2:cancelShipmentRequest> 
    </soapenv:Body> 
</soapenv:Envelope>  

Per ulteriori informazioni e un formattatore namespace generica aggiunta di controllare il mio relativo post sul blog qui : http://weblog.west-wind.com/posts/2016/Apr/02/Custom-Message-Formatting-in-WCF-to-add-all-Namespaces-to-the-SOAP-Envelope