Общепринятый способ избежать атрибута KnownType для каждого производного класса
Есть ли общепринятый способ избежать использования атрибутов KnownType в сервисах WCF? Я провел некоторое исследование, и, похоже, есть два варианта:
- Средство обработки данных контракта
- NetDataContractSerializer
Я не большой поклонник необходимости статически добавлять атрибуты KnownType каждый раз, когда я добавляю новый тип, поэтому хочу избегать его.
Есть ли третий вариант, который следует использовать? Если так, то, что это? Если нет, какой из этих двух вариантов является правильным?
Редактировать - использовать метод
Третий вариант будет использовать отражение
[DataContract]
[KnownType("DerivedTypes")]
public abstract class FooBase
{
private static Type[] DerivedTypes()
{
return typeof(FooBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray();
}
}
Ответы
Ответ 1
Я хотел опубликовать то, что кажется самым простым, самым элегантным решением, о котором я могу думать до сих пор. Если еще лучше ответит, я пойду с этим. Но пока это сработало хорошо.
Базовый класс, содержащий только один атрибут 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;
}
Ответ 2
Метод, упомянутый Бобом, будет работать до тех пор, пока все задействованные классы находятся в одной и той же сборке.
Следующий метод будет работать через сборки:
[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));
}
}
Ответ 3
Если вам не нравятся атрибуты везде, то вы можете использовать файл конфигурации.
<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>
Ответ 4
Вы можете реализовать 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);
}
}
Надеюсь, что это поможет.
Ответ 5
Я предпочел бы извлечь мои пользовательские типы сразу и использовать его во время сериализации/десериализации. После прочтения этого сообщения мне потребовалось некоторое время, чтобы понять, куда вводить этот список типов, которые будут полезны для объекта serializer. Ответ был довольно прост: этот список должен использоваться как один из входных аргументов конструктора объекта serializer.
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. У нас есть второй аргумент, который является точкой входа для ввода ваших известных типов объекту serializer.
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, но откуда-то должен быть доступ к конструктору объекта serializer.
Ответ 6
Вот мой вариант по принятому ответу:
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));
}
Различия:
- Смотрит на все загруженные сборки.
- Проверяет некоторые интересующие нас биты (DataContract, я думаю, требуется, если вы используете DataContractJsonSerializer), например, являющийся конкретным классом.
- Вы можете использовать isSubclassOf здесь, я обычно предпочитаю IsAssignableFrom, чтобы перехватить все переопределенные варианты. В частности, я думаю, что это работает с генериками.
- Воспользуйтесь преимуществом KnownTypes, принимающего IEnumerable (если это имеет значение в данном случае, вероятно, нет) вместо преобразования в массив.