Esto es simplemente una limitación inherente de la serialización declarativa donde la información de tipo no está incrustada en la salida.
Al intentar convertir de <Flibble Foo="10" />
nuevo en
public class Flibble { public object Foo { get; set; } }
¿Cómo sabe el serializador si debe ser un int, una cadena, un doble (o algo más) ...
Para que esto funcione, tiene varias opciones, pero si realmente no sabe hasta el tiempo de ejecución, es probable que la forma más fácil de hacerlo sea utilizando XmlAttributeOverrides .
Lamentablemente, esto solo funcionará con clases base, no interfaces. Lo mejor que puede hacer es ignorar la propiedad que no es suficiente para sus necesidades.
Si realmente debes quedarte con las interfaces, tienes tres opciones reales:
Escóndelo y trátalo en otra propiedad.
Placa de caldera fea, desagradable y mucha repetición, pero la mayoría de los consumidores de la clase no tendrán que lidiar con el problema:
[XmlIgnore()]
public object Foo { get; set; }
[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized
{
get { }
set { }
}
Es probable que esto se convierta en una pesadilla de mantenimiento ...
Implementar IXmlSerializable
Similar a la primera opción en que usted toma el control total de las cosas pero
- Pros
- No tienes desagradables propiedades "falsas" dando vueltas.
- puede interactuar directamente con la estructura xml agregando flexibilidad / control de versiones
- Contras
- puede terminar teniendo que volver a implementar la rueda para todas las demás propiedades de la clase
Los problemas de duplicación de esfuerzos son similares al primero.
Modifique su propiedad para usar un tipo de envoltura
public sealed class XmlAnything<T> : IXmlSerializable
{
public XmlAnything() {}
public XmlAnything(T t) { this.Value = t;}
public T Value {get; set;}
public void WriteXml (XmlWriter writer)
{
if (Value == null)
{
writer.WriteAttributeString("type", "null");
return;
}
Type type = this.Value.GetType();
XmlSerializer serializer = new XmlSerializer(type);
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
serializer.Serialize(writer, this.Value);
}
public void ReadXml(XmlReader reader)
{
if(!reader.HasAttributes)
throw new FormatException("expected a type attribute!");
string type = reader.GetAttribute("type");
reader.Read();
if (type == "null")
return;
XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
this.Value = (T)serializer.Deserialize(reader);
reader.ReadEndElement();
}
public XmlSchema GetSchema() { return(null); }
}
Usar esto implicaría algo como (en el proyecto P):
public namespace P
{
public interface IFoo {}
public class RealFoo : IFoo { public int X; }
public class OtherFoo : IFoo { public double X; }
public class Flibble
{
public XmlAnything<IFoo> Foo;
}
public static void Main(string[] args)
{
var x = new Flibble();
x.Foo = new XmlAnything<IFoo>(new RealFoo());
var s = new XmlSerializer(typeof(Flibble));
var sw = new StringWriter();
s.Serialize(sw, x);
Console.WriteLine(sw);
}
}
que te da:
<?xml version="1.0" encoding="utf-16"?>
<MainClass
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<RealFoo>
<X>0</X>
</RealFoo>
</Foo>
</MainClass>
Obviamente, esto es más engorroso para los usuarios de la clase, aunque evita mucha placa de caldera.
Un medio feliz puede ser fusionar la idea de XmlAnything en la propiedad de 'respaldo' de la primera técnica. De esta manera, la mayor parte del trabajo pesado está hecho por usted, pero los consumidores de la clase no sufren ningún impacto más allá de la confusión con la introspección.
La solución a esto es utilizar la reflexión con DataContractSerializer. Ni siquiera tienes que marcar tu clase con [DataContract] o [DataMember]. Serializará cualquier objeto, independientemente de si tiene propiedades de tipo de interfaz (incluidos diccionarios) en xml. Aquí hay un método de extensión simple que serializará cualquier objeto en XML incluso si tiene interfaces (tenga en cuenta que puede modificar esto para que se ejecute de forma recursiva también).
public static XElement ToXML(this object o) { Type t = o.GetType(); Type[] extraTypes = t.GetProperties() .Where(p => p.PropertyType.IsInterface) .Select(p => p.GetValue(o, null).GetType()) .ToArray(); DataContractSerializer serializer = new DataContractSerializer(t, extraTypes); StringWriter sw = new StringWriter(); XmlTextWriter xw = new XmlTextWriter(sw); serializer.WriteObject(xw, o); return XElement.Parse(sw.ToString()); }
lo que hace la expresión LINQ es enumerar cada propiedad, devuelve cada propiedad que es una interfaz, obtiene el valor de esa propiedad (el objeto subyacente), obtiene el tipo de ese objeto concreto, lo coloca en una matriz y lo agrega al serializador lista de tipos conocidos.
Ahora el serializador sabe qué hay de los tipos que serializa para que pueda hacer su trabajo.
fuente
Si conoce los implementadores de su interfaz desde el principio, hay un truco bastante simple que puede usar para que su tipo de interfaz se serialice sin escribir ningún código de análisis:
public interface IInterface {} public class KnownImplementor01 : IInterface {} public class KnownImplementor02 : IInterface {} public class KnownImplementor03 : IInterface {} public class ToSerialize { [XmlIgnore] public IInterface InterfaceProperty { get; set; } [XmlArray("interface")] [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01))] [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02))] [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03))] public object[] InterfacePropertySerialization { get { return new[] { InterfaceProperty }; ; } set { InterfaceProperty = (IInterface)value.Single(); } } }
El xml resultante debe tener un aspecto similar al de
<interface><ofTypeKnownImplementor01><!-- etc... -->
fuente
Puede utilizar ExtendedXmlSerializer . Este serializador admite la serialización de la propiedad de la interfaz sin ningún truco.
var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create(); var obj = new Example { Model = new Model { Name = "name" } }; var xml = serializer.Serialize(obj);
Su xml se verá así:
<?xml version="1.0" encoding="utf-8"?> <Example xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Simple;assembly=ExtendedXmlSerializer.Samples"> <Model exs:type="Model"> <Name>name</Name> </Model> </Example>
ExtendedXmlSerializer admite .net 4.5 y .net Core.
fuente
Si es posible utilizar una base abstracta, recomendaría esa ruta. Aún será más limpio que usar la serialización enrollada a mano. El único problema que veo con la base abstracta es que todavía vas a necesitar el tipo concreto. Al menos así es como lo he usado en el pasado, algo como:
public abstract class IHaveSomething { public abstract string Something { get; set; } } public class MySomething : IHaveSomething { string _sometext; public override string Something { get { return _sometext; } set { _sometext = value; } } } [XmlRoot("abc")] public class seriaized { [XmlElement("item", typeof(MySomething))] public IHaveSomething data; }
fuente
Desafortunadamente, no hay una respuesta simple, ya que el serializador no sabe qué serializar para una interfaz. Encontré una explicación más completa sobre cómo solucionar esto en MSDN
fuente
Desafortunadamente para mí, tuve un caso en el que la clase a serializar tenía propiedades que también tenían interfaces como propiedades, por lo que necesitaba procesar recursivamente cada propiedad. Además, algunas de las propiedades de la interfaz estaban marcadas como [XmlIgnore], así que quería omitirlas. Tomé ideas que encontré en este hilo y le agregué algunas cosas para hacerlo recursivo. Aquí solo se muestra el código de deserialización:
void main() { var serializer = GetDataContractSerializer<MyObjectWithCascadingInterfaces>(); using (FileStream stream = new FileStream(xmlPath, FileMode.Open)) { XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas()); var obj = (MyObjectWithCascadingInterfaces)serializer.ReadObject(reader); // your code here } } DataContractSerializer GetDataContractSerializer<T>() where T : new() { Type[] types = GetTypesForInterfaces<T>(); // Filter out duplicates Type[] result = types.ToList().Distinct().ToList().ToArray(); var obj = new T(); return new DataContractSerializer(obj.GetType(), types); } Type[] GetTypesForInterfaces<T>() where T : new() { return GetTypesForInterfaces(typeof(T)); } Type[] GetTypesForInterfaces(Type T) { Type[] result = new Type[0]; var obj = Activator.CreateInstance(T); // get the type for all interface properties that are not marked as "XmlIgnore" Type[] types = T.GetProperties() .Where(p => p.PropertyType.IsInterface && !p.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), false).Any()) .Select(p => p.GetValue(obj, null).GetType()) .ToArray(); result = result.ToList().Concat(types.ToList()).ToArray(); // do the same for each of the types identified foreach (Type t in types) { Type[] embeddedTypes = GetTypesForInterfaces(t); result = result.ToList().Concat(embeddedTypes.ToList()).ToArray(); } return result; }
fuente
He encontrado una solución más simple (no necesita el DataContractSerializer), gracias a este blog aquí: XML serializando tipos derivados cuando el tipo base está en otro espacio de nombres o DLL
XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)});
Aquí se proporciona un ejemplo detallado en MSDN: XmlSerializer Constructor (Type, extraTypesArray [])
Me parece que para DataContracts o Soap XML, debe verificar XmlRoot como se menciona aquí en esta pregunta SO .
Una respuesta similar está aquí en SO, pero no está marcada como una, ya que no parece que el OP ya la haya considerado.
fuente
en mi proyecto, tengo una
List <IFormatStyle> FormatStyleTemplates;
que contiene diferentes tipos.
Luego uso la solución 'XmlAnything' de arriba para serializar esta lista de diferentes tipos. El xml generado es hermoso.
[Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlArray("FormatStyleTemplates")] [XmlArrayItem("FormatStyle")] public XmlAnything<IFormatStyle>[] FormatStyleTemplatesXML { get { return FormatStyleTemplates.Select(t => new XmlAnything<IFormatStyle>(t)).ToArray(); } set { // read the values back into some new object or whatever m_FormatStyleTemplates = new FormatStyleProvider(null, true); value.ForEach(t => m_FormatStyleTemplates.Add(t.Value)); } }
fuente