0

I'm trying to implement creating a custom configuration section for the first time and am getting pretty beat up.

As an example, suppose I wanted to create a custom section for storing a list of CSV files to be read in by my program - Each file, therefore having:

  • A Path to the file
  • A Delimiter
  • A List of CsvColumn (another type)

Then, each CsvColumn could have properties such as:

  • The name in the file
  • It's Type

(Please ignore whether this is good to be done via config or not, this is just an example to give a hierarchy so I can learn how to do this).

So, I've created the following config file:

<configSections>
    <section name="CsvData" type="CSV_Loader.CsvData, CSV_Loader" />
</configSections>

<CsvData>
    <CsvFiles>
        <CsvFile Path="MyPath1" Delimiter=",">
            <CsvColumn NameInFile="Col1" Type="System.String"/>
            <CsvColumn NameInFile="Col2" Type="System.String"/>
            <CsvColumn NameInFile="Col3" Type="System.Double"/>
        </CsvFile>
        <CsvFile Path="MyPath2" Delimiter=",">
            <CsvColumn NameInFile="Col1" Type="System.String"/>
        </CsvFile>
    </CsvFiles>
</CsvData>

And, now I'm looking to create custom classes based upon the example / solution given here and am getting terribly stuck. My current code looks roughly as follows:

CsvColumn:

public class CsvColumn : ConfigurationElement
{
    [ConfigurationProperty("NameInFile", IsRequired = true, IsKey = true)]
    public string NameInFile
    {
        get { return (string)this["NameInFile"]; }
    }

    [ConfigurationProperty("Type", IsRequired = true, DefaultValue = "")]
    public string Type
    {
        get { return (string)this["Type"]; }
    }
}

(FYI - I'd like to store my Type as an actual Type variable, but I set it as String just in case that was where my issue was)

CsvFile:

[ConfigurationCollection(typeof(CsvColumn), AddItemName = "CsvColumn", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class CsvFile : ConfigurationElementCollection
{
    [ConfigurationProperty("Path", DefaultValue = "", IsKey = true, IsRequired = true)]
    public string Path
    {
        get { return (string)(base["Path"]); }
    }

    [ConfigurationProperty("Delimiter", DefaultValue = ",", IsRequired = true)]
    public string Delimiter
    {
        get { return (string)(base["Delimiter"]); }
    }

    public CsvColumn this[int index]
    {
        get { return (CsvColumn)BaseGet(index); }
        set
        {
            if (BaseGet(index) != null)
            {
                BaseRemoveAt(index);
            }
            BaseAdd(index, value);
        }
    }

    public void Add(CsvColumn column)
    {
        BaseAdd(column);
    }

    public void Clear()
    {
        BaseClear();
    }

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

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

    public void Remove(CsvColumn column)
    {
        BaseRemove(column.NameInFile);
    }

    public void RemoveAt(int index)
    {
        BaseRemoveAt(index);
    }

    public void Remove(String name)
    {
        BaseRemove(name);
    }
}

CsvFile Collection:

[ConfigurationCollection(typeof(CsvFile), AddItemName = "CsvFile", CollectionType = ConfigurationElementCollectionType.BasicMap)]
public class CsvFiles : ConfigurationElementCollection
{
    public CsvFile this[int index]
    {
        get { return (CsvFile)BaseGet(index); }
        set
        {
            if (BaseGet(index) != null)
            {
                BaseRemoveAt(index);
            }
            BaseAdd(index, value);
        }
    }

    public void Add(CsvFile file)
    {
        BaseAdd(file);
    }

    public void Clear()
    {
        BaseClear();
    }

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

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

    public void Remove(CsvFile file)
    {
        BaseRemove(file.Path);
    }

    public void RemoveAt(int index)
    {
        BaseRemoveAt(index);
    }

    public void Remove(String name)
    {
        BaseRemove(name);
    }   
}

CsvData:

public class CsvData : ConfigurationSection
{
    [ConfigurationProperty("CsvFiles")]
    public CsvFiles CsvFiles
    {
        get { return base["CsvFiles"] as CsvFiles; }
    }
}

And I am trying to call it in my code as follows:

var csvData = (ConfigurationManager.GetSection("CsvData") as CsvData);

But I am getting a ConfigurationException Unrecognized element 'CsvColumn'. And I can't figure out why.

My main questions are:

  1. What am I doing wrong?
  2. Is there an easier (Generic) way to implement this that would make this process easier?

Thanks so much!!!

Community
  • 1
  • 1
John Bustos
  • 19,036
  • 17
  • 89
  • 151

1 Answers1

3

I'm not sure why this happens, presumably a bug in the framework, but when you have these nested ConfigurationElementCollection instances it seems to ignore the AddItemName on the ConfigurationCollection attribute of the inner collection. You can confirm this yourself by changing your CsvColumn elements to add elements and it will deserialize properly.

Adding the following constructor to your CsvFile class should solve your issue:

  public CsvFile()
  {
        AddElementName = "CsvColumn";
  }

For your second question have you tried the Library CsvHelper

Amith Sewnarain
  • 655
  • 6
  • 11
  • IT FREAKING WORKED!!!!!! - **THANK YOU!!!!** - Truly, can't thank you enough!! - This had me going NUTS!!! – John Bustos Mar 31 '16 at 16:34
  • As for the second question - I'm wondering on a generic solution for dealing with Config files, not CSV files... any ideas there?? – John Bustos Mar 31 '16 at 16:35
  • You're welcome. I'm not sure about dealing with custom config sections generically. These days I tend to steer clear of them for my own configuration and store as JSON instead. – Amith Sewnarain Mar 31 '16 at 17:44
  • +1 on using JSON. Configuration sections are going away with ASP.NET Core. I've done lots and lots of nested configuration elements and I can never do them without referring to past code. They are a huge pain. – Scott Hannen Mar 31 '16 at 20:49