2013-04-25 4 views
16

Gibt es eine allgemein akzeptierte Möglichkeit, die Verwendung von KnownType-Attributen in WCF-Diensten zu vermeiden? Ich habe einige der Forschung getan, und es sieht so aus gibt es zwei Möglichkeiten:Im Allgemeinen akzeptierte Methode zur Vermeidung des KnownType-Attributs für jede abgeleitete Klasse

  1. Data contract resolver
  2. NetDataContractSerializer

Ich bin kein großer Fan von statisch Knowntype in den Attributen jedes Mal, Ich füge einen neuen Typ hinzu und möchte es daher vermeiden.

Gibt es eine dritte Option, die verwendet werden sollte? Wenn ja, was ist das? Wenn nicht, welche der beiden oben genannten Möglichkeiten ist der richtige Weg?

Bearbeiten - verwenden, um eine Methode

Eine dritte Option Reflexion

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

Antwort

23

Ich wollte die scheinbar einfachste und eleganteste Lösung posten, die mir bisher einfällt. Wenn eine andere Antwort kommt, die besser ist, werde ich damit gehen. Aber fürs erste hat das gut funktioniert.

Die Basisklasse mit nur einemKnownType Attribute, ein Verfahren zeigt DerivedTypes() genannt:

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

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

Die GetDerivedTypes() Verfahren, in einer separaten ReflectionUtility Klasse:

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

Wenn Sie die Erweiterungsmethode nicht erstellen möchten, können Sie diese zu einem Einzeiler machen. 'return Assembly.GetExecutingAssembly(). GetTypes(). Wo (_ => _.IsSubclassOf (typeof (TaskBase))). ToArray();' – x5657

+0

Dies ist eine großartige Lösung. Sauber und einfach. – kenjara

+0

Dies ist ein Lebensretter, danke! –

1

zu verwenden wäre, wenn Sie Attribute nicht wie überall, dann können Sie Konfigurationsdatei verwenden.

<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

Danke für den Tipp, Tim. Mein Hauptziel ist jedoch, dass ich nicht jedes Mal, wenn ein neuer Typ existiert, eine Liste aktualisieren muss. Gut zu wissen. –

+0

Außerdem müssen Sie Ihre Konfiguration jedes Mal aktualisieren, wenn Ihre Assemblyversion geändert wird. – evgenyl

1

Sie können IXmlSerializable in Ihren benutzerdefinierten Typen implementieren und deren Komplexität manuell behandeln. Nach Sie können einen Beispielcode finden:

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

Hoffe, es hilft.

+0

Danke, @Farzan, aber das scheint mehr zu sein, als ich gesucht habe, was die Wartung angeht. Meine dritte Option, in meiner Frage, ermöglicht es mir, einen Anruf nur für die Basisklasse zu implementieren und viel Code zu sparen. Ihr Ansatz ist schön zu sehen, aber es ist einfach mehr Code zu pflegen. –

7

das Verfahren erwähnt von Bob wird funktionieren, solange alle beteiligten Klassen in der gleichen Versammlung sind.

Die folgende Methode wird über Baugruppen arbeiten:

[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 für eine nette Alternative. Beachten Sie, dass die Reflektionsoption auch für Assemblys verwendet werden kann. Ich mag das, weil es sauber ist. Das einzige Negative, an das ich denken kann, ist, dass die abgeleiteten Klassen daran denken müssen, diesen statischen Konstruktor zu implementieren. Mit Reflektion bleibt dem Entwickler nichts mehr im Gedächtnis. –

+0

Richtig, aber bei vielen Baugruppen kann Leistung eine Rolle spielen. Ein weiterer Nachteil dieses Ansatzes besteht darin, die öffentliche Typenliste offen legen zu müssen. –

+1

Sie müssen auch sicherstellen, dass der statische Konstruktor aufgerufen werden kann: Die CLR-Laufzeit führt meines Erachtens nicht jeden statischen Konstruktor in einer Assembly beim Laden aus, nur beim ersten Zugriff auf die Klasse oder bei der Verwendung der Klasse. – Quantumplation

0

Ich würde eher auf einmal meine benutzerdefinierte Typen extrahieren und sie während der Serialisierung/Deserialisierung verwenden. Nachdem ich diesen Beitrag gelesen hatte, brauchte ich eine Weile, um zu verstehen, wo diese Typenliste eingefügt werden sollte, um für das Serializer-Objekt nützlich zu sein. Die Antwort war ziemlich einfach: Diese Liste wird als eines der Eingabeargumente des Konstruktors des Serialisierungsobjekts verwendet.

1- Ich bin zwei statische generische Methoden für die Serialisierung und Deserialisierung verwenden, das mehr oder weniger so, wie andere auch die Arbeit erledigen kann, oder zumindest ist es für die Herstellung Vergleich mit Ihrem Code sehr klar:

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- Bitte beachten Sie den Konstruktor von DataContractSerializer.Wir haben dort ein zweites Argument, das den Einstiegspunkt für das Einfügen Ihrer bekannten Typen in das Serializer-Objekt darstellt.

3- Ich verwende eine statische Methode, um alle meine eigenen definierten Typen aus meinen eigenen Baugruppen zu extrahieren.

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

Da ich nicht in WCF beteiligt war (ich brauchte nur eine binäre Serialisierung für Dateioperation) kann meine Lösung nicht genau adressieren die WCF-Architektur, aber es muss: Code für diese statische Methode wie folgt aussehen Zugriff auf den Konstruktor des Serialisierungsobjekts von irgendwo.

Verwandte Themen