2013-04-09 16 views
9

Desidero che il mio servizio sia in grado di accettare e restituire tipi derivati ​​da BaseType senza sapere realmente quali saranno questi tipi. Ho quasi ottenuto una soluzione utilizzando un custom DataContractResolver basato su SharedTypeResolver from this excellent blog post.Deserializzare i tipi derivati ​​nel servizio WCF come tipi di base ma conservare le informazioni sul tipo

Il pezzo mancante del puzzle è che i tipi gestiti dal servizio potrebbero non essere condivisi e noti al servizio, ma desidero comunque accettarli ed essere a conoscenza di cosa il tipo avrebbe dovuto essere. Ho trovato il seguente esempio di un servizio che funge da stack. È possibile premere e inserire qualsiasi tipo derivato da BaseType se si utilizza lo SharedTypeResolver ei tipi sono condivisi tra client e server.

[DataContract] 
public class BaseType 
{ 
    [DataMember] 
    public string SomeText { get; set; } 

    public override string ToString() 
    { 
     return this.GetType().Name + ": " + this.SomeText; 
    } 
} 

[DataContract] 
public class DerivedType : BaseType 
{ 
    [DataMember] 
    public int SomeNumber { get; set; } 

    public override string ToString() 
    { 
     return base.ToString() + ", " + this.SomeNumber; 
    } 
} 

[ServiceContract] 
public interface ITypeStack 
{ 
    [OperationContract] 
    void Push(BaseType item); 

    [OperationContract] 
    BaseType Pop(); 
} 

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] 
public class TypeStackService : ITypeStack 
{ 
    private Stack<BaseType> stack = new Stack<BaseType>(); 

    public void Push(BaseType item) 
    { 
     this.stack.Push(item); 
    } 

    public BaseType Pop() 
    { 
     return this.stack.Pop(); 
    } 
} 

Questo è ovviamente un esempio semplificato del problema riscontrato. Un cliente può tranquillamente spingere e pop BaseType o DerivedType perché sia ​​il client che il server ne sono a conoscenza, ma se il client invia lo UnsharedType che il servizio non conosce, ricevo un errore come ci si aspetterebbe.

Il formattatore ha generato un'eccezione durante il tentativo di deserializzare il messaggio : Si è verificato un errore durante il tentativo di deserializzare parametro http://tempuri.org/:item. Il messaggio InnerException era 'Errore nella riga 1 posizione 316. L'elemento' http://tempuri.org/:item 'contiene i dati da un tipo che corrisponde al nome' TestWcfClient, Versione = 1.0.0.0, Cultura = neutro, PublicKeyToken = null: TestWcfClient.UnsharedType ' . Il deserializzatore non è a conoscenza di alcun tipo associato a questo nome. Si consiglia di modificare l'implementazione del metodo ResolveName sul DataContractResolver per restituire un valore non nullo per il nome 'TestWcfClient.UnsharedType' e spazio dei nomi 'TestWcfClient, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null' '. . Vedere InnerException per ulteriori dettagli.

mio pensiero attuale è quello di aggiungere IExtensibleDataObject a BaseType per contenere i valori da un tipo non condivisa e fare un tipo non condivisa assomigliare BaseType al servizio sulla deserializzazione quando un elemento viene spinto; il contrario dovrebbe accadere quando un oggetto è spuntato. Non sono sicuro di come farlo. I miei pensieri finora su possibili approcci:

  • Ulteriori personalizzazioni a DataContractResolver che potrebbe comportare un TypeDelegator
  • Utilizzando IDataContractSurrogate al posto di un tipo non condivisa
  • In qualche modo mantengono l'XML serializzato il servizio ricevuto quando un elemento viene spinto , quindi utilizzare questo in sede di replica quando un elemento viene spuntato
  • Utilizzando un messaggio ispettore di manipolare i messaggi

Non ho idea se uno di questi funzionerà, cosa potrebbe essere coinvolto o quale sia la soluzione migliore. Fai?

+0

Cambierete oggetti su un servizio, o avete solo bisogno di memorizzarli e inviarli ai clienti? – Enes

+0

Basta archiviarli e inviarli indietro. Ho bisogno di conoscere il tipo di base in quanto ha alcune proprietà che il servizio utilizza per influenzare l'archiviazione, ma è di sola lettura per quanto riguarda il servizio. – batwad

risposta

3

Ho fatto qualche progresso con questo utilizzando un messaggio di ispezione e un tipo di segnaposto che implementa IExtensibleDataObject. L'ispettore impianta il messaggio in arrivo e cambia il suggerimento sul carattere del segnaposto e aggiunge il tipo originale come proprietà.Quando il tipo viene quindi inviato in una risposta, accade il contrario, rendendo quindi il segnaposto simile al tipo originale.

La mia lamentela con questa soluzione è che è legata al servizio perché ho dovuto includere lo spazio dei nomi XML del servizio e denominare esplicitamente metodi e parametri da manipolare. Diverso da quello sembra funzionare abbastanza bene, anche se l'ho provato solo su tipi abbastanza semplici derivati ​​da BaseType.

Qualcuno può migliorare su questo? C'è una taglia in più per te.

public class PlaceholderType : BaseType, IExtensibleDataObject 
{ 
    [IgnoreDataMember] 
    public string OriginalTypeName { get; set; } 

    [IgnoreDataMember] 
    public string OriginalNamespace { get; set; } 

    ExtensionDataObject IExtensibleDataObject.ExtensionData { get; set; } 
} 

public class FunkadelicInspector : IDispatchMessageInspector, IContractBehavior 
{ 
    const string PlaceholderNamespace = "http://my.placeholder.namespace"; 
    const string ServiceNamespace = "http://tempuri.org/"; 

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) 
    { 
     XmlDocument xmlDoc = ReadMessage(request); 

     XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); 
     // Dislike: having to know the service namespace, method and parameters 
     nsmgr.AddNamespace("s", ServiceNamespace); 
     XmlNode itemElement = xmlDoc.SelectSingleNode("//s:Push/s:item", nsmgr); 

     if (itemElement != null) 
     { 
      XmlAttribute typeAttribute = itemElement.Attributes["type", "http://www.w3.org/2001/XMLSchema-instance"]; 
      if (typeAttribute != null) 
      { 
       // Record original type 
       string[] parts = typeAttribute.Value.Split(':'); 
       string originalTypeName = parts[1]; 
       // Replace with placeholder type 
       typeAttribute.Value = parts[0] + ":" + typeof(PlaceholderType).FullName; 

       // Record original assembly 
       XmlAttribute nsAtt = itemElement.Attributes["xmlns:" + parts[0]]; 
       string originalAssembly = nsAtt.Value; 
       // Replace with placeholder type's assembly 
       nsAtt.Value = typeof(PlaceholderType).Assembly.FullName; 

       // Add placeholders 
       itemElement.AppendChild(xmlDoc.CreateElement("OriginalType", PlaceholderNamespace)).InnerText = originalTypeName; 
       itemElement.AppendChild(xmlDoc.CreateElement("OriginalAssembly", PlaceholderNamespace)).InnerText = originalAssembly; 
      } 
     } 

     //Now recreate the message 
     request = WriteMessage(request, xmlDoc); 
     return null; 
    } 

    public void BeforeSendReply(ref Message reply, object correlationState) 
    { 
     XmlDocument xmlDoc = ReadMessage(reply); 

     XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); 
     nsmgr.AddNamespace("s", ServiceNamespace); 
     nsmgr.AddNamespace("plc", PlaceholderNamespace); 
     // Dislike: having to know the service namespace, method and parameters 
     XmlNode resultElement = xmlDoc.SelectSingleNode("//s:PopResponse/s:PopResult", nsmgr); 

     if (resultElement != null) 
     { 
      XmlElement originalType = resultElement.SelectSingleNode("plc:OriginalType", nsmgr) as XmlElement; 
      XmlElement originalAssembly = resultElement.SelectSingleNode("plc:OriginalAssembly", nsmgr) as XmlElement; 
      if (originalType != null && originalAssembly != null) 
      { 
       // Replace original type 
       XmlAttribute type = resultElement.Attributes["type", "http://www.w3.org/2001/XMLSchema-instance"]; 
       string[] parts = type.Value.Split(':'); // 0 is an alias for the assembly, 1 is the type 
       type.Value = parts[0] + ":" + originalType.InnerText; 

       // Replace original assembly 
       XmlAttribute ass = resultElement.Attributes["xmlns:" + parts[0]]; 
       ass.Value = originalAssembly.InnerText; 

       // Remove placeholders 
       resultElement.RemoveChild(originalType); 
       resultElement.RemoveChild(originalAssembly); 
      } 
     } 

     //Now recreate the message 
     reply = WriteMessage(reply, xmlDoc); 
    } 

    private static Message WriteMessage(Message original, XmlDocument xmlDoc) 
    { 
     MemoryStream ms = new MemoryStream(); 
     xmlDoc.Save(ms); 
     ms.Position = 0; 
     XmlReader reader = XmlReader.Create(ms); 
     Message newMessage = Message.CreateMessage(reader, int.MaxValue, original.Version); 
     newMessage.Properties.CopyProperties(original.Properties); 
     return newMessage; 
    } 

    private static XmlDocument ReadMessage(Message message) 
    { 
     MemoryStream ms = new MemoryStream(); 
     using (XmlWriter writer = XmlWriter.Create(ms)) 
     { 
      message.WriteMessage(writer); // the message was consumed here 
      writer.Flush(); 
     } 
     ms.Position = 0; 
     XmlDocument xmlDoc = new XmlDocument(); 
     xmlDoc.Load(ms); 
     return xmlDoc; 
    } 

    void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) 
    { 
    } 

    void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) 
    { 
    } 

    void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) 
    { 
     dispatchRuntime.MessageInspectors.Add(this); 
    } 

    void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) 
    { 
    } 
}