2013-04-25 11 views
16

Esiste un modo generalmente accettato per evitare di dover utilizzare gli attributi di KnownType sui servizi WCF? Ho fatto qualche ricerca, e sembra che ci sono due opzioni:Metodo generalmente accettato per evitare l'attributo KnownType per ogni classe derivata

  1. Data contract resolver
  2. NetDataContractSerializer

Io non sono un grande fan di dover aggiungere staticamente KnownType attributi ogni volta Aggiungo un nuovo tipo, quindi voglio evitarlo.

Esiste una terza opzione da utilizzare? Se è così, che cosa è? In caso contrario, quale delle due opzioni di cui sopra è la giusta via da percorrere?

Modifica - utilizzare un metodo

Una terza opzione sarebbe quella di utilizzare riflessione

[DataContract] 
[KnownType("DerivedTypes")] 
public abstract class FooBase 
{ 
    private static Type[] DerivedTypes() 
    { 
     return typeof(FooBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray(); 
    } 
} 

risposta

23

Volevo postare quella che sembra la soluzione più semplice ed elegante a cui possa pensare fino ad ora. Se arriva un'altra risposta è meglio, vado con quello. Ma per ora, questo ha funzionato bene.

La classe di base, con un solo unoKnownType attributo, indicando un metodo chiamato DerivedTypes():

[KnownType("DerivedTypes")] 
[DataContract] 
public abstract class TaskBase : EntityBase 
{ 
    // other class members here 

    private static Type[] DerivedTypes() 
    { 
     return typeof(TaskBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray(); 
    } 
} 

Procedimento GetDerivedTypes(), in una classe ReflectionUtility separata:

public static IEnumerable<Type> GetDerivedTypes(this Type baseType, Assembly assembly) 
{ 
    var types = from t in assembly.GetTypes() 
       where t.IsSubclassOf(baseType) 
       select t; 

    return types; 
} 
+4

Se preferisci non creare il metodo di estensione, questo può essere trasformato in un unico elemento. 'return Assembly.GetExecutingAssembly(). GetTypes(). Where (_ => _.IsSubclassOf (typeof (TaskBase))). ToArray();' – x5657

+0

Questa è un'ottima soluzione. Pulito e semplice – kenjara

+0

Questo è un vero toccasana! –

1

Se non ti piace attributi ovunque quindi è possibile utilizzare il file di configurazione.

<system.runtime.serialization> 
    <dataContractSerializer> 
     <declaredTypes> 
     <add type = "Contact,Host,Version=1.0.0.0,Culture=neutral, 
                   PublicKeyToken=null"> 
      <knownType type = "Customer,MyClassLibrary,Version=1.0.0.0, 
              Culture=neutral,PublicKeyToken=null"/> 
     </add> 
     </declaredTypes> 
    </dataContractSerializer> 
</system.runtime.serialization> 
+1

Grazie per il suggerimento, Tim. Tuttavia il mio obiettivo principale è non dover aggiornare una lista da qualche parte ogni volta che c'è un nuovo tipo. Buono a sapersi. –

+0

Inoltre, questo richiederà di aggiornare la configurazione OGNI volta che la versione dell'assembly verrà modificata. – evgenyl

1

È possibile implementare IXmlSerializable nei vostri tipi personalizzati e gestire la sua complessità manualmente. seguito potete trovare un esempio di codice:

[XmlRoot("ComplexTypeA")] 
public class ComplexTypeA : IXmlSerializable 
{ 
    public int Value { get; set; } 

    public void WriteXml (XmlWriter writer) 
    { 
     writer.WriteAttributeString("Type", this.GetType().FullName); 
     writer.WriteValue(this.Value.ToString()); 
    } 

    public void ReadXml (XmlReader reader) 
    { 
     reader.MoveToContent(); 
     if (reader.HasAttributes) { 
      if (reader.GetAttribute("Type") == this.GetType().FullName) { 
       this.Value = int.Parse(reader.ReadString()); 
      } 
     } 
    } 

    public XmlSchema GetSchema() 
    { 
     return(null); 
    } 
} 

[XmlRoot("ComplexTypeB")] 
public class ComplexTypeB : IXmlSerializable 
{ 
    public string Value { get; set; } 

    public void WriteXml (XmlWriter writer) 
    { 
     writer.WriteAttributeString("Type", this.GetType().FullName); 
     writer.WriteValue(this.Value); 
    } 

    public void ReadXml (XmlReader reader) 
    { 
     reader.MoveToContent(); 
     if (reader.HasAttributes) { 
      if (reader.GetAttribute("Type") == this.GetType().FullName) { 
       this.Value = reader.ReadString(); 
      } 
     } 
    } 

    public XmlSchema GetSchema() 
    { 
     return(null); 
    } 
} 


[XmlRoot("ComplexTypeC")] 
public class ComplexTypeC : IXmlSerializable 
{ 
    public Object ComplexObj { get; set; } 

    public void WriteXml (XmlWriter writer) 
    { 
     writer.WriteAttributeString("Type", this.GetType().FullName); 
     if (this.ComplexObj != null) 
     { 
      writer.WriteAttributeString("IsNull", "False"); 
      writer.WriteAttributeString("SubType", this.ComplexObj.GetType().FullName); 
      if (this.ComplexObj is ComplexTypeA) 
      { 
       writer.WriteAttributeString("HasValue", "True"); 
       XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeA)); 
       serializer.Serialize(writer, this.ComplexObj as ComplexTypeA); 
      } 
      else if (tthis.ComplexObj is ComplexTypeB) 
      { 
       writer.WriteAttributeString("HasValue", "True"); 
       XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeB)); 
       serializer.Serialize(writer, this.ComplexObj as ComplexTypeB); 
      } 
      else 
      { 
       writer.WriteAttributeString("HasValue", "False"); 
      } 
     } 
     else 
     { 
      writer.WriteAttributeString("IsNull", "True"); 
     } 
    } 

    public void ReadXml (XmlReader reader) 
    { 
     reader.MoveToContent(); 
     if (reader.HasAttributes) { 
      if (reader.GetAttribute("Type") == this.GetType().FullName) { 
       if ((reader.GetAttribute("IsNull") == "False") && (reader.GetAttribute("HasValue") == "True")) { 
        if (reader.GetAttribute("SubType") == typeof(ComplexTypeA).FullName) 
        { 
         XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeA)); 
         this.ComplexObj = serializer.Deserialize(reader) as ComplexTypeA; 
        } 
        else if (reader.GetAttribute("SubType") == typeof(ComplexTypeB).FullName) 
        { 
         XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeB)); 
         this.ComplexObj = serializer.Deserialize(reader) as ComplexTypeB; 
        } 
       } 
      } 
     } 
    } 

    public XmlSchema GetSchema() 
    { 
     return(null); 
    } 
} 

Speranza che aiuta.

+0

Grazie, @Farzan, ma questo sembra essere più di quello che stavo cercando, per quanto riguarda la manutenzione. La mia terza opzione, nella mia domanda, mi consente di implementare una chiamata, solo sulla classe base, e di risparmiare un sacco di codice. Il tuo approccio è bello da vedere, ma è semplicemente più codice da mantenere. –

7

Il metodo menzionato da Bob funzionerà finché tutte le classi coinvolte sono nello stesso assembly.

Il seguente metodo funziona attraverso assemblee:

[DataContract] 
[KnownType("GetDerivedTypes")] 
public class BaseClass 
{ 
    public static List<Type> DerivedTypes = new List<Type>(); 

    private static IEnumerable<Type> GetDerivedTypes() 
    { 
    return DerivedTypes; 
    } 
} 


[DataContract] 
public class DerivedClass : BaseClass 
{ 
    //static constructor 
    static DerivedClass() 
    { 
    BaseClass.DerivedTypes.Add(typeof(DerivedClass)); 
    } 
} 
+0

+1 per una bella alternativa. Si noti che l'opzione di riflessione può essere fatta per funzionare anche tra gli assiemi. Mi piace perché è pulito. L'unico aspetto negativo che posso pensare è che le classi derivate devono ricordare di implementare questo costruttore statico. Con la riflessione, nulla è lasciato alla memoria dello sviluppatore. –

+0

Vero, ma con molti assiemi, le prestazioni possono diventare una considerazione. Un'altra caduta di questo approccio è dover esporre l'elenco pubblico di tipi. –

+1

È inoltre necessario assicurarsi che ci sia qualcosa da invocare il costruttore statico: il runtime CLR, credo, non esegue ogni costruttore statico in un assembly mentre lo carica, solo la prima volta che si accede o si utilizza la classe in qualche modo. – Quantumplation

0

preferirei estraggo i miei tipi personalizzati tutto in una volta e utilizzarlo durante la serializzazione/deserializzazione. Dopo aver letto questo post, mi ci è voluto un po 'per capire dove iniettare questa lista di tipi per essere utile per l'oggetto serializzatore. La risposta è stata abbastanza semplice: questa lista deve essere utilizzata come uno degli argomenti di input del costruttore dell'oggetto serializzatore.

1- sto usando due metodi generici statici per la serializzazione e deserializzazione, questo può essere più o meno come gli altri anche fare il lavoro, o almeno è molto chiaro per fare il confronto con il tuo codice:

public static byte[] Serialize<T>(T obj) 
    { 
     var serializer = new DataContractSerializer(typeof(T), MyGlobalObject.ResolveKnownTypes()); 
     var stream = new MemoryStream(); 
     using (var writer = 
      XmlDictionaryWriter.CreateBinaryWriter(stream)) 
     { 
      serializer.WriteObject(writer, obj); 
     } 
     return stream.ToArray(); 
    } 
    public static T Deserialize<T>(byte[] data) 
    { 
     var serializer = new DataContractSerializer(typeof(T), MyGlobalObject.ResolveKnownTypes()); 
     using (var stream = new MemoryStream(data)) 
     using (var reader = 
      XmlDictionaryReader.CreateBinaryReader(
       stream, XmlDictionaryReaderQuotas.Max)) 
     { 
      return (T)serializer.ReadObject(reader); 
     } 
    } 

2- Prestare attenzione al costruttore di DataContractSerializer.Qui abbiamo un secondo argomento, che è il punto di ingresso per l'iniezione dei tipi noti all'oggetto serializzatore.

3- Sto utilizzando un metodo statico per estrarre tutti i miei tipi definiti dai miei stessi assiemi. il codice per questo metodo statico può apparire come segue:

private static Type[] KnownTypes { get; set; } 
    public static Type[] ResolveKnownTypes() 
    { 
     if (MyGlobalObject.KnownTypes == null) 
     { 
      List<Type> t = new List<Type>(); 
      List<AssemblyName> c = System.Reflection.Assembly.GetEntryAssembly().GetReferencedAssemblies().Where(b => b.Name == "DeveloperCode" | b.Name == "Library").ToList(); 
      foreach (AssemblyName n in c) 
      { 
       System.Reflection.Assembly a = System.Reflection.Assembly.Load(n); 
       t.AddRange(a.GetTypes().ToList()); 
      } 
      MyGlobalObject.KnownTypes = t.ToArray(); 
     } 
     return IOChannel.KnownTypes; 
    } 

Dal momento che non è stato coinvolto in WCF (Ho solo bisogno di una serializzazione binaria per il funzionamento di file), la mia soluzione potrebbe non esattamente affrontare l'architettura WCF, ma ci deve accedere al costruttore di oggetti serializer da qualche parte.

Problemi correlati