46

I have an invocation logger that is intended to record all method calls along with the parameters associated with the method using XmlSerializer. It works well for most of the calls, but it throws an exception for all methods that has a parameter of IEnumerable type.

For example, void MethodWithPlace( Place value ) would be serialized, but void MethodWithPlace( IEnumerable<Place> value ) would not.

The exception is

System.NotSupportedException: Cannot serialize interface System.Collections.Generic.IEnumerable`1[[Place, Test, Version=0.0.0.0, Culture=neutral]].

What should I do to make it work with those methods with IEnumerable as one of its parameters?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
uni
  • 613
  • 1
  • 7
  • 11
  • 2
    Can you replace the method definitions with a concrete implementation of IEnumerable, such as List? – Evan M Feb 01 '12 at 19:51
  • possible duplicate of [Cannot serialize parameter of type 'System.Linq.Enumerable... ' when using WCF, LINQ, JSON](http://stackoverflow.com/questions/2068897/cannot-serialize-parameter-of-type-system-linq-enumerable-when-using-wcf) – Coincoin Feb 01 '12 at 19:52
  • possible duplicate of [Serialize Objects using xmlSerializer.Serialize and IEnumerable objects](http://stackoverflow.com/questions/2729875/serialize-objects-using-xmlserializer-serialize-and-ienumerable-objects) – Sergey Vyacheslavovich Brunov Feb 01 '12 at 19:52
  • @evanM I wish I could, but I'm afraid that I'm not allowed to touch other part of the project. – uni Feb 01 '12 at 20:01
  • 1
    How do you log a method, can you show the code? – L.B Feb 01 '12 at 20:07
  • Changed the title because `XmlSerializer` is not specific to C# – John Saunders Jan 14 '15 at 15:42

7 Answers7

35

The way you serialize an IEnumerable property is with a surrogate property

[XmlRoot]
public class Entity {
   [XmlIgnore]
   public IEnumerable<Foo> Foo { get; set; }

   [XmlElement, Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
   public List<Foo> FooSurrogate { get { return Foo.ToList(); } set { Foo = value; } }
}

It's ugly, but it gets the job done. The nicer solution is to write a surrogate class (i.e. EntitySurrogate).

Stephen Turner
  • 7,125
  • 4
  • 51
  • 68
Herman Schoenfeld
  • 8,464
  • 4
  • 38
  • 49
  • 1
    Throwing `[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]` on the surrogate will help hide it. Also, may want to consider the ShouldSerialize* pattern. [Similar to this answer](http://stackoverflow.com/a/10840603/425809). – Richard Jun 18 '13 at 16:33
13

Basically an XmlSerializer can't serialize an interface. The solution, then, is to give it a concrete instance to serialize. Depending on how your invocation logger works, I would consider using

var serializer = new XmlSerializer(value.GetType());
Kyle W
  • 3,702
  • 20
  • 32
  • What we did was to get all possible method parameters in a class and put them as extraTypes in the constructor of XmlSerializer. Then, we create a transparent proxy for that class, capture method calls, serialize them, and invoke the real method.. – uni Feb 01 '12 at 20:08
13

I don't think you'll be able to serialize that. Try converting the IEnumerable to a List and then you will be able to serialize.

Chris
  • 360
  • 1
  • 8
  • 2
    Since I'm unable to change the method signature, is there any workaround that can possibly solve this problem? – uni Feb 01 '12 at 20:02
  • 2
    You can if you were to just add the .ToList() to that Method Signature or should I say have it return the IEnumberable.ToList() – MethodMan Feb 01 '12 at 20:04
  • 2
    Can you use a different serializer, like the NetDataContractSerializer? You will not be able to do it with the XML serializer. – Joe Feb 01 '12 at 20:04
  • You'll have to use a different serializer. Say, for example, someone passes a non-serializable argument that inherits from IEnumerable. The XML serializer won't be able to handle it. – Evan M Feb 01 '12 at 20:18
  • 1
    I think you are saying that you CANNOT modify the methods that are being logged, so you can't control if an IEnumerable is a parameter in the method that is being logged, correct? So, then, can you modify the code that attempts to serialize this data and change any IEnumerable objects by using the .ToList() method on them before the serialization attempt is made? Or, can you pass the logging to a middleman class that can turn any IEnumerables into Lists before passing it along to the Serializer? Otherwise, I think you will need to have the method signatures modified as DJ KRAZE suggested. – Chris Feb 01 '12 at 22:13
3

To be XML serializable, types which inherit from IEnumerable must have an implementation of Add(System.Object) at all levels of their inheritance hierarchy. {your class} does not implement Add(System.Object).

implement the Add() function, you might solve the problem

what is sleep
  • 916
  • 4
  • 12
0

Maybe not the best way, but it worked for me. I created a method that makes serialization.

Use

var xml = Util.ObjetoToXML(obj, null, null).OuterXml;

method

        public static XmlDocument ObjetoToXML(object obj, XmlDocument xmlDocument, XmlNode rootNode)
    {

        if (xmlDocument == null)
            xmlDocument = new XmlDocument();

        if (obj == null) return xmlDocument;

        Type type = obj.GetType();

        if (rootNode == null) { 
            rootNode = xmlDocument.CreateElement(string.Empty, type.Name, string.Empty);
            xmlDocument.AppendChild(rootNode);
        }

        if (type.IsPrimitive || type == typeof(Decimal) || type == typeof(String) || type == typeof(DateTime))
        {

            // Simples types
            if (obj != null)
                rootNode.InnerText = obj.ToString();

        }
        else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
        {
            // Genericis types

            XmlNode node = null;

            foreach (var item in (IEnumerable)obj)
            {
                if (node == null)
                {
                    node = xmlDocument.CreateElement(string.Empty, item.GetType().Name, string.Empty);
                    node = rootNode.AppendChild(node);
                }


                ObjetoToXML(item, xmlDocument, node);
            }

        }
        else
        {

            // Classes types
            foreach (var propertie in obj.GetType().GetProperties())
            {

                XmlNode node = xmlDocument.CreateElement(string.Empty, propertie.Name, string.Empty);
                node = rootNode.AppendChild(node);
                var valor = propertie.GetValue(obj, null);

                ObjetoToXML(valor, xmlDocument, node);
            }

        }


        return xmlDocument;

    }
Everson Rafael
  • 2,043
  • 2
  • 20
  • 20
0

XmlSerializer does not support this. Try YAXLib for these kinds serializations.

Sina Iravanian
  • 16,011
  • 4
  • 34
  • 45
  • The url is broken. The right url is https://www.nuget.org/packages/YAXLib/ or https://www.nuget.org/packages/XSerializer/ – ADM-IT Jan 07 '22 at 19:18
-1

You can use DataContractSerializer

        using (var ms = new MemoryStream())
        {
            var serialiser = new DataContractSerializer(typeof (EnvironmentMetadata));
            serialiser.WriteObject(ms, environmentMetadata);

            var s = Encoding.ASCII.GetString(ms.ToArray());
            return s;
        }
Maxim Eliseev
  • 3,248
  • 4
  • 29
  • 35