Это просто внутреннее ограничение декларативной сериализации, когда информация о типе не встроена в выходные данные.
При попытке преобразовать <Flibble Foo="10" />
обратно в
public class Flibble { public object Foo { get; set; } }
Как сериализатор узнает, должно ли это быть int, string, double (или что-то еще) ...
Чтобы сделать эту работу, у вас есть несколько вариантов, но если вы действительно не знаете, до времени выполнения, самый простой способ сделать это, вероятно, будет использовать XmlAttributeOverrides .
К сожалению, это будет работать только с базовыми классами, но не с интерфейсами. Лучшее, что вы можете сделать, - это игнорировать свойство, которого недостаточно для ваших нужд.
Если вам действительно необходимо придерживаться интерфейсов, у вас есть три реальных варианта:
Спрячьте это и займитесь этим в другой собственности
Уродливая, неприятная плита и много повторений, но большинству потребителей этого класса не придется сталкиваться с проблемой:
[XmlIgnore()]
public object Foo { get; set; }
[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized
{
get { }
set { }
}
Это может превратиться в кошмар обслуживания ...
Реализовать IXmlSerializable
Подобно первому варианту, вы полностью контролируете все, но
- Плюсы
- У вас нет отвратительных «фальшивых» свойств.
- вы можете напрямую взаимодействовать со структурой xml, добавляя гибкости / управления версиями
- Минусы
- вам может потребоваться повторно реализовать колесо для всех других свойств в классе
Вопросы дублирования усилий аналогичны первому.
Измените свойство, чтобы использовать тип упаковки
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); }
}
Для этого потребуется что-то вроде (в проекте 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);
}
}
что дает вам:
<?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>
This is obviously more cumbersome for users of the class though avoids much boiler plate.
A happy medium may be merging the XmlAnything idea into the 'backing' property of the first technique. In this way most of the grunt work is done for you but consumers of the class suffer no impact beyond confusion with introspection.
The solution to this is using reflection with the DataContractSerializer. You don't even have to mark your class with [DataContract] or [DataMember]. It will serialize any object, regardless of whether it has interface type properties (including dictionaries) into xml. Here is a simple extension method that will serialize any object into XML even if it has interfaces (note you could tweak this to run recursively as well).
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()); }
what the LINQ expression does is it enumerates each property, returns each property that is an interface, gets the value of that property (the underlying object), gets the type of that concrete object puts it into an array, and adds that to the serializer's list of known types.
Now the serializer knows how about the types it is serializing so it can do its job.
источник
If you know your interface implementors up-front there's a fairly simple hack you can use to get your interface type to serialize without writing any parsing code:
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(); } } }
The resulting xml should look something along the lines of
<interface><ofTypeKnownImplementor01><!-- etc... -->
источник
You can use ExtendedXmlSerializer. This serializer support serialization of interface property without any tricks.
var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create(); var obj = new Example { Model = new Model { Name = "name" } }; var xml = serializer.Serialize(obj);
Your xml will look like:
<?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 support .net 4.5 and .net Core.
источник
If it is possible to use an abstract base I would recommend that route. It will still be cleaner than using hand-rolled serialization. The only trouble I see with the abstract base is that your still going to need the concrete type? At least that is how I've used it in the past, something like:
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; }
источник
Unfortunately there's no simple answer, as the serializer doesn't know what to serialize for an interface. I found a more complete explaination on how to workaround this on MSDN
источник
Unfortuantely for me, I had a case where the class to be serialized had properties that had interfaces as properties as well, so I needed to recursively process each property. Also, some of the interface properties were marked as [XmlIgnore], so I wanted to skip over those. I took ideas that I found on this thread and added some things to it to make it recursive. Only the deserialization code is shown here:
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; }
источник
I have found a simpler solution (you don't need the DataContractSerializer), thanks to this blog here: XML serializing derived types when base type is in another namespace or DLL
XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)});
A detailed example is provided here on MSDN: XmlSerializer Constructor (Type, extraTypesArray[])
It seems to me that for DataContracts or Soap XMLs, you need to check the XmlRoot as mentioned here in this SO question.
A similar answer is here on SO but it isn't marked as one, as it not the OP seems to have considered it already.
источник
in my project, I have a
List<IFormatStyle> FormatStyleTemplates;
containing different Types.
I then use the solution 'XmlAnything' from above, to serialize this list of different types. The generated xml is beautiful.
[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)); } }
источник