Serialização XML da propriedade da interface

Eu gostaria de XML serializar um object que tem (entre outros) uma propriedade do tipo IModelObject (que é uma interface).

public class Example { public IModelObject Model { get; set; } } 

Quando tento serializar um object dessa class, recebo o seguinte erro:
“Não é possível serializar o membro Example.Model do tipo Exemplo porque é uma interface.”

Eu entendo que o problema é que uma interface não pode ser serializada. No entanto, o tipo de object Modelo concreto é desconhecido até o tempo de execução.

Substituir a interface IModelObject por um tipo abstrato ou concreto e usar inheritance com o XMLInclude é possível, mas parece uma solução feia.

Alguma sugestão?

Isso é simplesmente uma limitação inerente à serialização declarativa em que as informações de tipo não são incorporadas na saída.

Ao tentar converter volta para

 public class Flibble { public object Foo { get; set; } } 

Como o serializador sabe se deve ser um int, uma string, um double (ou outra coisa) …

Para fazer este trabalho você tem várias opções, mas se você realmente não sabe até o tempo de execução, a maneira mais fácil de fazer isso provavelmente estará usando o XmlAttributeOverrides .

Infelizmente isso só funcionará com classs base, não com interfaces. O melhor que você pode fazer é ignorar a propriedade que não é suficiente para as suas necessidades.

Se você realmente precisa ficar com interfaces, você tem três opções reais:

Esconder e lidar com isso em outra propriedade

Feio, desagradável placa de caldeira e muita repetição, mas a maioria dos consumidores da class não terá que lidar com o problema:

 [XmlIgnore()] public object Foo { get; set; } [XmlElement("Foo")] [EditorVisibile(EditorVisibility.Advanced)] public string FooSerialized { get { /* code here to convert any type in Foo to string */ } set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } } 

Isso provavelmente se tornará um pesadelo de manutenção …

Implementar IXmlSerializable

Semelhante à primeira opção em que você assume o controle total das coisas, mas

  • Prós
    • Você não tem propriedades ‘falsas’ desagradáveis ​​por aí.
    • você pode interagir diretamente com a estrutura xml adicionando flexibilidade / versionamento
  • Contras
    • você pode acabar tendo que reimplantar a roda para todas as outras propriedades da class

As questões de duplicação de esforço são semelhantes às primeiras.

Modifique sua propriedade para usar um tipo de quebra automática

 public sealed class XmlAnything : 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(); // consume the value if (type == "null") return;// leave T at default value XmlSerializer serializer = new XmlSerializer(Type.GetType(type)); this.Value = (T)serializer.Deserialize(reader); reader.ReadEndElement(); } public XmlSchema GetSchema() { return(null); } } 

Usar isso envolveria algo como (no projeto 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 Foo; } public static void Main(string[] args) { var x = new Flibble(); x.Foo = new XmlAnything(new RealFoo()); var s = new XmlSerializer(typeof(Flibble)); var sw = new StringWriter(); s.Serialize(sw, x); Console.WriteLine(sw); } } 

que te dá:

 < ?xml version="1.0" encoding="utf-16"?>    0    

Isso é obviamente mais trabalhoso para os usuários da class, embora evite muita placa.

Um meio feliz pode estar mesclando a idéia do XmlAnything na propriedade ‘backing’ da primeira técnica. Desta forma, a maior parte do trabalho pesado é feito para você, mas os consumidores da class não sofrem impacto além da confusão com a introspecção.

A solução para isso é usar a reflection com o DataContractSerializer. Você nem precisa marcar sua turma com [DataContract] ou [DataMember]. Ele serializará qualquer object, independentemente de ter propriedades de tipo de interface (incluindo dictionarys) em xml. Aqui está um método de extensão simples que serializará qualquer object em XML, mesmo que tenha interfaces (note que você pode ajustar isso para executar recursivamente também).

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

O que a expressão LINQ faz é enumera cada propriedade, retorna cada propriedade que é uma interface, obtém o valor dessa propriedade (o object subjacente), obtém o tipo de object concreto que a coloca em uma matriz e adiciona isso ao serializador. lista de tipos conhecidos.

Agora o serializador sabe como os tipos são serializados para que ele possa fazer seu trabalho.

Você pode usar o ExtendedXmlSerializer . Este serializador suporta a serialização da propriedade da interface sem truques.

 var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create(); var obj = new Example { Model = new Model { Name = "name" } }; var xml = serializer.Serialize(obj); 

Seu xml será parecido com:

 < ?xml version="1.0" encoding="utf-8"?>   name   

O ExtendedXmlSerializer suporta .net 4.5 e .net Core.

Se você conhece seus implementadores de interface desde o início, há um hack bastante simples que você pode usar para obter o tipo de interface para serializar sem escrever qualquer código de análise:

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

O xml resultante deve olhar algo ao longo das linhas de

   

Substituir a interface IModelObject por um tipo abstrato ou concreto e usar inheritance com o XMLInclude é possível, mas parece uma solução feia.

Se é possível usar uma base abstrata, eu recomendaria essa rota. Ainda será mais limpo do que usar a serialização manual. O único problema que vejo com a base abstrata é que você ainda vai precisar do tipo concreto? Pelo menos é assim que eu usei no passado, 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; } 

Infelizmente não há uma resposta simples, já que o serializador não sabe o que serializar para uma interface. Eu encontrei uma explicação mais completa sobre como contornar isso no MSDN

no meu projeto, eu tenho um
Listar FormatStyleTemplates;
contendo diferentes tipos.

Em seguida, uso a solução ‘XmlAnything’ acima, para serializar essa lista de tipos diferentes. O xml gerado é lindo.

  [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlArray("FormatStyleTemplates")] [XmlArrayItem("FormatStyle")] public XmlAnything[] FormatStyleTemplatesXML { get { return FormatStyleTemplates.Select(t => new XmlAnything(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)); } } 

Infelizmente para mim, eu tinha um caso em que a class a ser serializada tinha propriedades que tinham interfaces como propriedades também, então eu precisava processar recursivamente cada propriedade. Além disso, algumas das propriedades da interface foram marcadas como [XmlIgnore], então eu queria pular essas. Tomei ideias que encontrei neste tópico e adicionei algumas coisas para torná-lo recursivo. Apenas o código de desserialização é mostrado aqui:

 void main() { var serializer = GetDataContractSerializer(); 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() where T : new() { Type[] types = GetTypesForInterfaces(); // Filter out duplicates Type[] result = types.ToList().Distinct().ToList().ToArray(); var obj = new T(); return new DataContractSerializer(obj.GetType(), types); } Type[] GetTypesForInterfaces() 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; } 

Eu encontrei uma solução mais simples (você não precisa do DataContractSerializer), graças a este blog aqui: XML derivados tipos de serialização quando o tipo base está em outro namespace ou DLL

Mas dois problemas podem subir nesta implementação:

(1) E se DerivedBase não estiver no namespace da class Base ou, pior ainda, em um projeto que depende do namespace Base, então Base não pode XMLInclude DerivedBase

(2) E se nós só tivermos a class Base como dll, então novamente Base não pode XMLInclude DerivedBase

Até agora, …

Portanto, a solução para os 2 problemas é usando o XmlSerializer Constructor (Type, array []) :

 XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)}); 

Um exemplo detalhado é fornecido aqui no MSDN: XmlSerializer Constructor (Type, extraTypesArray [])

Parece-me que para DataContracts ou Soap XMLs, você precisa verificar o XmlRoot como mencionado aqui nesta pergunta SO .

Uma resposta semelhante está aqui no SO, mas ele não está marcado como um, já que o OP não parece tê-lo considerado.