Export object to XML file

67 Views Asked by At

I have an object like this:

public partial class CableApplication : StateObject
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public CableApplication()
    {
        this.CableProperties = new HashSet<CableProperty>();
    }

    public int Id { get; set; }
    public int ProjectId { get; set; }
    public string Application { get; set; }
    public byte[] ts { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<CableProperty> CableProperties { get; set; }
    public virtual Project Project { get; set; }
}

which is created in .edmx file automatically in database-first Visual Studio C# project. I want to export all the data of CableApplication into an XML file.

I wrote this code in the service:

public string ExportToXml<T>(T obj)
{
    using (var stringwriter = new System.IO.StringWriter())
    {
        TextWriter writer = new StreamWriter(@"d:\\temp\\check.xml");
        var serializer = new XmlSerializer(typeof(T));
        serializer.Serialize(stringwriter, obj);
        writer.Close();

        return stringwriter.ToString();
    }           
}

And this code in the frontend project:

private void exporToXMLToolStripMenuItem_Click(object sender, EventArgs e)
{
    using (ICableService client = new CableService())
    {                
        var applications = client.GetCableApplications(searchList.ToArray(), null, "").ToList();    // I get the list of cable Applications . works fine

        var str = client.ExportToXml(applications);              
    }
}

But I get the following error:

Cannot serialize member 'Cable1Layer.Domain.CableApplication.CableProperties' of type 'System.Collections.Generic.ICollection`1[[Cable1Layer.Domain.CableProperty, Cable1Layer.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]', see inner exception for more details

There was an error reflecting type 'Cable1Layer.Domain.CableApplication'

How should I serialiaze this object?

1

There are 1 best solutions below

0
dbc On

The innermost error message (not shown in your question) is self explanatory:

System.NotSupportedException: Cannot serialize member CableApplication.CableProperties of type ICollection<CableProperty> because it is an interface.

XmlSerializer cannot serialize interfaces because, not being concrete types, they cannot be constructed during deserialization. For more see

As a workaround you could introduce a serializable surrogate property and mark the original property with [XmlIgnore], however since your class was auto-generated from an .edmx file you probably don't want to modify any of the auto-generated code. Thus what you can do is:

  • Take advantage of the fact that CableApplication was generated as a partial class to add a serializable surrogate property in a manually created partial file.
  • Use XmlAttributeOverrides to suppress serialization of the non-serializable interface property.

To do this, first extend CableProperties as follows:

public partial class CableApplication
{
    public CableProperty [] CablePropertiesArray
    {
        get { return CableProperties.ToArray(); }
        set
        {
            CableProperties.Clear();
            if (value != null)
            {
                foreach (var item in value)
                    CableProperties.Add(item);
            }
        }
    }
}

Then add the following XmlSerializer factory:

public static class XmlSerializerFactory
{
    readonly static object lockObject = new object();
    readonly static Dictionary<Type, XmlSerializer> SerializerDictionary = new Dictionary<Type, XmlSerializer>();
    
    public static XmlSerializer CreateSerializer(Type type)
    {
        lock (lockObject)
        {
            XmlSerializer serializer;
            if (SerializerDictionary.TryGetValue(type, out serializer))
                return serializer;
            var overrides = new XmlAttributeOverrides()
                .AddCableApplicationOverrides()
                // Add customizations for other types as needed here.
                ;
            SerializerDictionary.Add(type, serializer = new XmlSerializer(type, overrides));
            return serializer;
        }
    }
    
    static XmlAttributeOverrides AddCableApplicationOverrides(this XmlAttributeOverrides overrides)
    {
        overrides.Add(typeof(CableApplication), 
                      "CableProperties", // Use nameof(CableApplication.CableProperties) if available in your C# version
                      new XmlAttributes { XmlIgnore = true });
        overrides.Add(typeof(CableApplication), 
                      "CablePropertiesArray", // Use nameof(CableApplication.CablePropertiesArray) if available in your C# version
                      new XmlAttributes { XmlArray = new XmlArrayAttribute { ElementName = "CableProperties" } }); // Use nameof(CableApplication.CableProperties) if available in your C# version
        return overrides;
    }
}

And finally modify ExportToXml<T>(T obj) to use the serializer factory:

public string ExportToXml<T>(T obj)
{
    using (var stringwriter = new System.IO.StringWriter())
    {
        // You never actually write anything to the StreamWriter so I'm not sure what is going in here.  
        // I commented this code out since I did not have a d:\temp\check.xml file
        //TextWriter writer = new StreamWriter( @"d:\\temp\\check.xml");
        var serializer = XmlSerializerFactory.CreateSerializer(typeof(T));
        serializer.Serialize(stringwriter, obj);
        //writer.Close();

        return stringwriter.ToString();
    }           
}

Note that, when constructing serializer using XmlAttributeOverrides, it is necessary to statically cache and reuse the serializer for reasons explained in Memory Leak using StreamReader and XmlSerializer.

Demo fiddle here.