2013-04-25 15 views
16

Czy istnieje ogólnie przyjęty sposób unikania konieczności używania atrybutów KnownType w usługach WCF? Robiłem rozeznanie, i wygląda na to są dwie opcje:Ogólnie akceptowany sposób unikania atrybutu KnownType dla każdej pochodnej klasy

  1. Data contract resolver
  2. NetDataContractSerializer

Nie jestem wielkim fanem konieczności statycznie dodać KnownType atrybuty za każdym razem Dodaję nowy typ, dlatego chcę go ominąć.

Czy istnieje trzecia opcja, którą należy zastosować? Jeśli tak, co to jest? Jeśli nie, to która z dwóch powyższych opcji jest właściwa?

Edit - stosować metodę

Trzecią opcją byłoby użyć refleksji

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

Odpowiedz

23

Chciałem opublikować to, co wydaje się być najprostszym, najbardziej eleganckim rozwiązaniem, jakie mogę wymyślić do tej pory. Jeśli przyjdzie kolejna odpowiedź, to lepiej. Ale na razie to działało dobrze.

Klasa bazowej tylko jedenKnownType atrybutu wskazuje metodę zwaną DerivedTypes():

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

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

Sposób GetDerivedTypes() w oddzielnej kategorii ReflectionUtility:

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

Jeśli nie chcesz tworzyć metody rozszerzeń, można ją przekształcić w jednolinijkową. 'return Assembly.GetExecutingAssembly(). GetTypes(). Gdzie (_ => _.IsSubclassOf (typeof (TaskBase))) ToArray();' – x5657

+0

To jest świetne rozwiązanie. Czyste i proste. – kenjara

+0

To jest ratownik, dziękuję! –

1

Jeśli nie podoba atrybuty wszędzie można użyć pliku konfiguracyjnego.

<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

Dzięki za napiwek, Tim. Jednak moim głównym celem jest nie aktualizowanie listy za każdym razem, gdy pojawia się nowy typ. Dobrze wiedzieć. –

+0

Co więcej, będzie to wymagało zaktualizowania konfiguracji KAŻDEGO czasu, w którym zmieni się wersja zestawu. – evgenyl

1

można zaimplementować w swoim rodzaju IXmlSerializable niestandardowych i obsługi jego złożoność ręcznie. obserwuję można znaleźć przykładowy kod:

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

Nadzieję, że to pomaga.

+0

Dzięki, @Farzan, ale wydaje mi się, że to więcej niż szukałem, jeśli chodzi o konserwację. Moja trzecia opcja, w moim pytaniu, pozwala mi zaimplementować jedno połączenie, tylko na klasie bazowej i zaoszczędzić dużo kodu. Twoje podejście jest miłe do zobaczenia, ale jest po prostu więcej kodu do utrzymania. –

7

metodzie opisanej przez Boba będzie działać tak długo, jak wszystkie zaangażowane klasy będą w tym samym zespole.

Poniższa metoda zadziała w poprzek zespołów:

[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 za miłą alternatywę. Zauważ, że opcja odbicia może również działać w poprzek złożeń. Podoba mi się to, ponieważ jest czyste. Jedynym minusem, jaki mogę sobie wyobrazić, jest to, że klasy pochodne muszą pamiętać o implementacji tego statycznego konstruktora. Z refleksją nic nie pozostało pamięci programisty. –

+0

To prawda, ale przy wielu zgromadzeniach wydajność może być brana pod uwagę. Kolejną wadą tego podejścia jest konieczność ujawnienia publicznej listy typów. –

+1

Należy również upewnić się, że istnieje coś do wywołania konstruktora statycznego: środowisko wykonawcze CLR, jak sądzę, nie uruchamia każdego konstruktora statycznego w złożeniu podczas jego ładowania, tylko po raz pierwszy, gdy klasa jest dostępna lub używana w jakiś sposób. – Quantumplation

0

wolałbym wyodrębnić moich własnych typów naraz i użyć go podczas serializacji/deserializacji. Po przeczytaniu tego postu zajęło mi trochę czasu, aby zrozumieć, gdzie wprowadzić tę listę typów, aby była użyteczna dla obiektu serializera. Odpowiedź była dość prosta: ta lista ma być używana jako jeden z argumentów wejściowych konstruktora obiektu serializera.

1- używam dwóch statycznych metod generycznych dla serializacji i deserializacji, może to być mniej więcej taki sposób inni też wykonać zadanie, albo przynajmniej jest bardzo jasne dokonywania porównań z kodem:

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- Proszę zwrócić uwagę na konstruktora DataContractSerializer.Mamy tam drugi argument, który jest punktem wyjściowym do wstrzykiwania twoich znanych typów do serializera.

3- Używam statycznej metody wyodrębniania wszystkich własnych zdefiniowanych typów z moich własnych zespołów. Twój kod dla tej metody statycznej może wyglądać następująco:

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

Ponieważ nie był zaangażowany w WCF (I tylko potrzebne binarny serializacji do pracy pliku), moje rozwiązanie może nie dokładnie rozwiązania architektury WCF, ale musi mieć dostęp do konstruktora obiektu serializera skądś.

Powiązane problemy