0

I am trying to deserialize an XML document that I am also serializing at another time. I am using it to store a configuration file.

This is my Code:

namespace OrderTracker
{
    [Serializable]
    public class AutofillValues
    {
        private string fileName = Directory.GetCurrentDirectory() + "\\bin\\settings.db";

        public ComboBox.ObjectCollection Vendors { get; set; }
        public ComboBox.ObjectCollection Products { get; set; }
        public ComboBox.ObjectCollection Companies { get; set; }

        public void save(AutofillValues afv)
        {
            if (!File.Exists(fileName))
            {
                FileStream fs = File.Create(fileName);
                fs.Close();
            }

            XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
            TextWriter writer = new StreamWriter(fileName);
            x.Serialize(writer, afv);
            writer.Close();
        }

        public AutofillValues load()
        {
            XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
            TextReader file = new StreamReader(fileName);

            AutofillValues av = (AutofillValues)x.Deserialize(file);
            file.Close();
            return av;
        }
    }
}

The error message that I am getting when trying to deserialize the file is this;

An unhandled exception of type 'System.InvalidOperationException' occurred in System.Xml.dll Additional information: There is an error in XML document (2, 2).*

This is the XML document:

<?xml version="1.0" encoding="utf-8"?>
<AutofillValues xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Vendors>
    <anyType xsi:type="xsd:string">Test Vendor</anyType>
  </Vendors>
  <Products>
    <anyType xsi:type="xsd:string">Test Product</anyType>
  </Products>
  <Companies>
    <anyType xsi:type="xsd:string">Test Company</anyType>
  </Companies>
</AutofillValues>

How can I deserialize the XML file and get back the serialized data?

keenthinker
  • 7,645
  • 2
  • 35
  • 45
  • You can not deserialize it back - it is a limitation of the XmlSerializer, as stated in this [SO post](http://stackoverflow.com/questions/267724/why-xml-serializable-class-need-a-parameterless-constructor). – keenthinker Aug 11 '15 at 16:47
  • Forgot to mention - the problem is that the `ComboBox.ObjectCollection` does not have a parameterless contructor - XmlSerializer needs a default constructor without parameters! – keenthinker Aug 11 '15 at 17:15

2 Answers2

1

I just changed this part and it worked for me. enter image description here

ken lacoste
  • 894
  • 8
  • 22
0

You can not deserialize the XML back, because the class ComboBox.ObjectCollection does not have a standard (parameterless) constructor. This is a limitation of the XmlSerializer class, as stated in this SO post.

There is however another problem with your current code - even if the deserialization somehow works, than you still need to assign the collection to a ComboBox control, which the deserializer still can't do.

Instead of using the ComboBox.ObjectCollection class to store the items, I would suggest using either an array or a list of objects (as @kenlacoste suggested). Such collections can be easily inserted into the ComboBox using the comboBox.Items.AddRange(arrayOfObjects) method.

Another refactoring would be to extract the serialization logic of the data class. Currently it is confusing to save and load the data, because I presume you want to save/fill the caller object:

  • save: object.save(object); - you can use the this keyword in the save method
  • load: object = object.load(); - same here, there is no need to return the value, use the this keyword to fill the existing properties

The changed code:

public class AutofillValues
{
    private string fileName = @"d:\settings.db";

    public object[] Vendors { get; set; }
    public object[] Products { get; set; }
    public object[] Companies { get; set; }

    public void save()
    {
        XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
        // with using there is no need to close the writer explicitely
        //  second parameter - file is created if it does not exist
        using (var writer = new StreamWriter(fileName, false))
        {
            x.Serialize(writer, this);
        }
    }

    public void load()
    {
        XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
        AutofillValues av = (AutofillValues)x.Deserialize(new StreamReader(fileName));
        this.Companies = av.Companies;
        this.Vendors = av.Vendors;
        this.Products = av.Products;
    }
}

IMO the modified code is easier to read and understand:

var afv = new AutofillValues();
afv.load();
//use avf.Products
// or afv.save();

I would also suggest to extract the data that needs to be saved in an extra class, for example:

[Serializable]
public class AutofillValuesData
{
    public Object[] Vendors { get; set; }
    public Object[] Products { get; set; }
    public Object[] Companies { get; set; }
}

In the class AutofillValues remove the three properties and leave just one:

public AutofillValuesData Data { get; set; }

Then the logic can be modified to fill the ComboBox controls from the filled data object. This way your data will not be hardwired to the UI and this would make the code more maintainable. You can use a helper like AutoMapper to remove the repetitive code (like mappig objA.Vendors to objB.Vendors).

Community
  • 1
  • 1
keenthinker
  • 7,645
  • 2
  • 35
  • 45