Общепринятый способ избежать атрибута KnownType для каждого производного класса

Существует ли общепринятый способ избежать использования атрибутов KnownType в службах WCF? Я провел некоторое исследование, и похоже, что есть два варианта:

  1. Сопоставитель контрактов данных
  2. NetDataContractSerializer

Я не большой поклонник необходимости статически добавлять атрибуты KnownType каждый раз, когда я добавляю новый тип, поэтому хочу этого избежать.

Есть ли третий вариант, который следует использовать? Если так, то, что это? Если нет, то какой из двух вышеперечисленных вариантов правильный?

Изменить – использовать метод

Третий вариант - использовать отражение

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

person Bob Horn    schedule 25.04.2013    source источник


Ответы (6)


Я хотел опубликовать то, что кажется самым простым и элегантным решением, которое я могу придумать до сих пор. Если появится другой ответ, который будет лучше, я соглашусь с ним. Но на данный момент это сработало хорошо.

Базовый класс с одним атрибутом KnownType, указывающим на метод с именем DerivedTypes():

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

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

Метод GetDerivedTypes() в отдельном классе 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;
}
person Bob Horn    schedule 25.04.2013
comment
Если вы не хотите создавать метод расширения, его можно превратить в однострочный. return Assembly.GetExecutingAssembly().GetTypes().Where(_ => _.IsSubclassOf(typeof(TaskBase))).ToArray(); - person x5657; 16.03.2017
comment
Это отличное решение. Чисто и просто. - person kenjara; 03.08.2017

Метод, упомянутый Бобом, будет работать до тех пор, пока все задействованные классы находятся в одной сборке.

Следующий метод будет работать для всех сборок:

[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)); 
  }
}
person Leon van der Walt    schedule 30.07.2014
comment
+1 за хорошую альтернативу. Обратите внимание, что параметр отражения может работать и между сборками. Мне это нравится, потому что здесь чисто. Единственный минус, о котором я могу думать, это то, что производные классы должны помнить о реализации этого статического конструктора. При отражении на память разработчику ничего не остается. - person Bob Horn; 30.07.2014
comment
Верно, но со многими сборками производительность может стать важным фактором. Еще одним недостатком этого подхода является необходимость выставлять общедоступный список типов. - person Leon van der Walt; 30.07.2014
comment
Вы также должны убедиться, что есть что-то для вызова статического конструктора: я полагаю, что среда выполнения CLR не запускает каждый статический конструктор в сборке по мере ее загрузки, а только при первом доступе к классу или его использовании каким-либо образом. - person Quantumplation; 22.07.2015

Вот мой вариант принятого ответа:

    private static IEnumerable<Type> GetKnownTypes() {
        Type baseType = typeof(MyBaseType);
        return AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(x => x.DefinedTypes)
            .Where(x => x.IsClass && !x.IsAbstract && x.GetCustomAttribute<DataContractAttribute>() != null && baseType.IsAssignableFrom(x));
    }

Различия:

  1. Смотрит на все загруженные сборки.
  2. Проверяет некоторые интересующие нас биты (DataContract, я думаю, требуется, если вы используете DataContractJsonSerializer), например, конкретный класс.
  3. Здесь вы можете использовать isSubclassOf, я обычно предпочитаю IsAssignableFrom для перехвата всех переопределенных вариантов. В частности, я думаю, что это работает с дженериками.
  4. Воспользуйтесь преимуществами KnownTypes, принимающими IEnumerable (если это имеет значение в данном случае, вероятно, нет) вместо преобразования в массив.
person user169771    schedule 20.12.2018

Вы можете реализовать IXmlSerializable в своих пользовательских типах и управлять его сложностью вручную. Ниже вы можете найти пример кода:

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

Надеюсь, поможет.

person Farzan    schedule 25.04.2013
comment
Спасибо, @Farzan, но это, кажется, больше, чем я искал, что касается обслуживания. Мой третий вариант, в моем вопросе, позволяет мне реализовать один вызов только для базового класса и сэкономить много кода. Ваш подход приятно видеть, но это просто больше кода для поддержки. - person Bob Horn; 25.04.2013

Если вам не нравятся атрибуты везде, вы можете использовать файл конфигурации.

<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>
person Tim Phan    schedule 25.04.2013
comment
Спасибо за подсказку, Тим. Однако моя главная цель - не обновлять список где-то каждый раз, когда появляется новый тип. Хорошо знать. - person Bob Horn; 25.04.2013
comment
Кроме того, это потребует от вас обновления вашей конфигурации КАЖДЫЙ раз, когда ваша версия сборки будет меняться. - person evgenyl; 26.04.2013

Я бы предпочел сразу извлечь все свои пользовательские типы и использовать их во время сериализации/десериализации. После прочтения этого сообщения мне потребовалось некоторое время, чтобы понять, куда вставить этот список типов, чтобы он был полезен для объекта сериализатора. Ответ был очень прост: этот список нужно использовать как один из входных аргументов конструктора объекта-сериализатора.

1- Я использую два статических универсальных метода для сериализации и десериализации, это может быть более или менее так, как другие выполняют эту работу, или, по крайней мере, это очень ясно для сравнения с вашим кодом:

    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- Обратите внимание на конструктор DataContractSerializer. У нас есть второй аргумент, который является точкой входа для внедрения ваших известных типов в объект сериализатора.

3- Я использую статический метод для извлечения всех моих собственных определенных типов из моих собственных сборок. ваш код для этого статического метода может выглядеть так:

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

Поскольку я не занимался WCF (мне нужна была только бинарная сериализация для работы с файлами), мое решение может не совсем соответствовать архитектуре WCF, но откуда-то должен быть доступ к конструктору объекта сериализатора.

person Alireza Ahmadi Rad    schedule 25.11.2016