Search code examples
c#xmlxmlserializer

Unable to Deserialize XML in C# - InvalidOperationException


I have a C# app that has a custom section of configuration info in the App.config file. At this time, I am able to successfully load the custom info via code. However, I'm trying to load that same configuration info from a database as well. In an attempt to do this, I took a string of XML from my App.config file that I know is working. That string of XML looks like this:

<departments>
  <department id="1" name="Sporting Goods">
    <products>
      <product name="Basketball" price="9.99">
        <add key="Color" value="Orange" />
        <add key="Brand" value="[BrandName]" />
      </product>
    </products>
  </department>
</departments>

I am trying to deserialize this XML into C# objects. I've defined these objects like this:

Departments.cs

public class Departments : ConfigurationSection
{
  private Departments() { }

  [ConfigurationProperty("", IsRequired = false, IsKey = false, IsDefaultCollection = true)]
  public DepartmentItemCollection Items
  {
    get
    {
      var items = base[""] as DepartmentItemCollection;
      return items;
    }
    set { base["items"] = value; }
  }

  public static Departments Deserialize(string xml)
  {
    Departments departments = null;

    var serializer = new XmlSerializer(typeof(Departments));
    using (var reader = new StringReader(xml))
    {
      departments = (Departments)(serializer.Deserialize(reader));
    }

    return departments;
  }
}

[ConfigurationCollection(typeof(Department), CollectionType = ConfigurationElementCollectionType.BasicMapAlternate)]
public class DepartmentItemCollection : ConfigurationElementCollection
{
  private const string ItemPropertyName = "department";

  public override ConfigurationElementCollectionType CollectionType
  {
    get { return ConfigurationElementCollectionType.BasicMapAlternate; }
  }

  protected override string ElementName
  {
    get { return ItemPropertyName; }
  }

  protected override bool IsElementName(string elementName)
  {
    return (elementName == ItemPropertyName);
  }

  protected override object GetElementKey(ConfigurationElement element)
  {
    return ((Department)element).Name;
  }

  protected override ConfigurationElement CreateNewElement()
  {
    return new Department();
  }

  public override bool IsReadOnly()
  {
    return false;
  }
}

Department.cs

public class Department : ConfigurationElement
{
  public Department()
  { }

  [ConfigurationProperty("id", IsRequired = false, IsKey = true)]
  public int Id
  {
    get { return (int)(this["id"]); }
    set { this["id"] = value; }
  }

  [ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]
  public string Name
  {
    get { return (string)(this["name"]); }
    set { this["name"] = value; }
  }

  [ConfigurationProperty("products", IsRequired = false, IsKey = false, IsDefaultCollection = false)]
  public ProductCollection Products
   {
     get { return ((ProductCollection)(base["products"])); }
     set { base["products"] = value; }
   }
}

DepartmentProducts.cs

[ConfigurationCollection(typeof(Product), AddItemName = "product", CollectionType = ConfigurationElementCollectionType.BasicMapAlternate)]
public class ProductCollection: ConfigurationElementCollection
{
    public override ConfigurationElementCollectionType CollectionType
    {
        get { return ConfigurationElementCollectionType.BasicMapAlternate; }
    }

    protected override string ElementName
    {
        get { return string.Empty; }
    }

    protected override bool IsElementName(string elementName)
    {
        return (elementName == "product");
    }

    protected override object GetElementKey(ConfigurationElement element)
    {
        return element;
    }

    protected override ConfigurationElement CreateNewElement()
    {
        return new Product();
    }

    protected override ConfigurationElement CreateNewElement(string elementName)
    {
        var product = new Product();
        return product;
    }

    public override bool IsReadOnly()
    {
        return false;
    }
}

DepartmentProduct.cs

public class Product : ConfigurationElement
{
  public Product()
  { }

  [ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]
  public string Name
  {
    get { return (string)(this["name"]); }
    set { this["name"] = value; }
  }

  [ConfigurationProperty("price", IsRequired = false)]
  public decimal Price
  {
    get { return (decimal)(this["price"]); }
    set { price["name"] = value; }
  }

  [ConfigurationProperty("", IsRequired = false, IsKey = false, IsDefaultCollection = true)]
  public KeyValueConfigurationCollection Items
  {
    get
    {
      var items = base[""] as KeyValueConfigurationCollection;
      return items;
    }
    set { base["items"] = value; }
  }
}

When I pass the XML shown above to the Departments.Deserialize method, I receive the following error:

InvalidOperationException: You must implement a default accessor on System.Configuration.ConfigurationLockCollection because it inherits from ICollection.

How do I deserialize the XML I shared into the C# objects shared?


Solution

  • I had a similar issue in the past. While I couldn't figure out how to deal with the InvalidOperationException, I managed to get it working by directly tagging the class as IXmlSerializable

     [XmlRoot("departments")]
     public class Departments : ConfigurationSection, IXmlSerializable
     {
        //Your code here..
    
        public XmlSchema GetSchema()
        {
            return this.GetSchema();
        }
    
        public void ReadXml(XmlReader reader)
        {
            this.DeserializeElement(reader, false);
        }
    
        public void WriteXml(XmlWriter writer)
        {
            this.SerializeElement(writer, false);
        }
     }