modificadores - metodos y funciones en c#
Manera generalmente aceptada de evitar el atributo KnownType para cada clase derivada (6)
¿Existe una forma generalmente aceptada de evitar tener que usar los atributos KnownType en los servicios WCF? He estado investigando y parece que hay dos opciones:
- Resolución de contratos de datos
- NetDataContractSerializer
No soy un gran fan de tener que agregar de forma estática los atributos KnownType cada vez que agrego un nuevo tipo, por lo que quiero evitarlo.
¿Hay una tercera opción que debería usarse? Si es así, ¿qué es? Si no es así, ¿cuál de las dos opciones anteriores es la mejor opción?
Editar - usa un método
Una tercera opción sería utilizar la reflexión.
[DataContract]
[KnownType("DerivedTypes")]
public abstract class FooBase
{
private static Type[] DerivedTypes()
{
return typeof(FooBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray();
}
}
Aquí está mi variante en la respuesta aceptada:
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));
}
Las diferencias son:
- Mira todos los ensamblajes cargados.
- Comprueba algunos bits que nos interesan (DataContract, creo que es necesario si está utilizando DataContractJsonSerializer), como ser una clase concreta.
- Puede usar isSubclassOf aquí, tiendo a preferir IsAssignableFrom en general para capturar todas las variantes anuladas. En particular creo que funciona con genéricos.
- Aproveche que KnownTypes acepte un IEnumerable (si es importante en este caso, probablemente no) en lugar de convertirlo a una matriz.
El método mencionado por Bob funcionará siempre que todas las clases involucradas estén en el mismo ensamblaje.
El siguiente método funcionará en todos los ensamblajes:
[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));
}
}
Prefiero extraer todos mis tipos personalizados a la vez y usarlos durante la serialización / deserialización. Después de leer esta publicación, me tomó un tiempo entender dónde inyectar esta lista de tipos para que sea útil para el objeto serializador. La respuesta fue bastante sencilla: esta lista se utilizará como uno de los argumentos de entrada del constructor del objeto serializador.
1- Estoy usando dos métodos genéricos estáticos para la serialización y deserialización, esto puede ser más o menos la forma en que otros también hacen el trabajo, o al menos es muy claro para hacer una comparación con su código:
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- Preste atención al constructor de DataContractSerializer. Tenemos un segundo argumento allí, que es el punto de entrada para inyectar sus tipos conocidos al objeto serializador.
3- Estoy usando un método estático para extraer todos mis propios tipos definidos de mis propios ensamblajes. Su código para este método estático puede verse así:
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;
}
Como no estaba involucrado en WCF (solo necesitaba una serialización binaria para la operación de archivos), mi solución puede no abordar exactamente la arquitectura de WCF, pero debe haber acceso al constructor del objeto serializador desde algún lugar.
Puede implementar IXmlSerializable en sus tipos personalizados y manejar su complejidad manualmente. A continuación puede encontrar un código de muestra:
[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);
}
}
Espero eso ayude.
Quería publicar lo que parece ser la solución más simple y elegante que puedo imaginar hasta ahora. Si viene otra respuesta, mejor, voy con eso. Pero por ahora, esto funcionó bien.
La clase base, con un KnownType
atributo KnownType
, apunta a un método llamado DerivedTypes()
:
[KnownType("DerivedTypes")]
[DataContract]
public abstract class TaskBase : EntityBase
{
// other class members here
private static Type[] DerivedTypes()
{
return typeof(TaskBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray();
}
}
El método GetDerivedTypes()
, en una clase separada de 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;
}
Si no te gustan los atributos en todas partes, entonces puedes usar el archivo de configuración.
<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>