5

I have a working custom configuration section. However, it is a pain to get at my data via a ConfigurationElementCollection but when I try to implement my property as an IEnumerable, it fails with the error:

ConfigurationErrorsException was unhandled "Property 'contacts' is not a ConfigurationElement."

Here is the code causing the failure:

[ConfigurationProperty("contacts", IsDefaultCollection = false)]
public IEnumerable<string> Contacts
{
    get { return ((ContactCollection)base["contacts"]).Cast<ContactElement>().Select(x => x.Address); }
}

However, if I change it to this:

[ConfigurationProperty("contacts", IsDefaultCollection = false)]
public ContactCollection Contacts
{
    get { return ((ContactCollection)base["contacts"]); }
}

Everything works fine. This answer makes it sound like this is just something Microsoft decided was not allowed and so I can't have any properties of types other than ConfigurationElement. Is this really the case? How can I implement my property as an IEnumerable<string>?

In case it matters, I'm trying to store emails and I'd like to have an element for each one since there may be a number of them, we may want to store more information on each contact in the future, and I think a single, comma-separated list might get ugly. For example, something like:

<emergency>
    <contact address="sirdank@stackoverflow.com" />
    <contact address="jon.skeet@stackoverflow.com" />
</emergency>

Or

<emergency>
    <contact>sirdank@stackoverflow.com</contact>
    <contact>jon.skeet@stackoverflow.com</contact>
</emergency>

Thanks!

Community
  • 1
  • 1
sirdank
  • 3,351
  • 3
  • 25
  • 58
  • You have my sympathy. Custom config sections with nested collections are a huge pain. I've written lots of them and I still can't remember the syntax clearly enough to provide an answer. Maybe this post will help: [http://stackoverflow.com/questions/10958054/how-to-create-a-configuration-section-that-contains-a-collection-of-collections](http://stackoverflow.com/questions/10958054/how-to-create-a-configuration-section-that-contains-a-collection-of-collections) – Scott Hannen Jul 05 '16 at 14:39
  • 1
    It seems to me that much of the complexity stems from support for sections that are updated programmatically or support overriding lower-level config files. That's wonderful but in practice we hardly ever use that. It might be safe to say that we *never* use that. – Scott Hannen Jul 05 '16 at 14:42

2 Answers2

5

Any harm in providing two methods? The first to fulfill the Microsoft requirement and the second to fulfill your own requirements.

    public IEnumerable<string> Contacts
    {
        get
        {
            return ContactCollection.Cast<ContactElement>().Select(x => x.Address);     
        }
    }

    [ConfigurationProperty("contacts", IsDefaultCollection = false)]
    public ContactCollection ContactCollection
    {
        get { return ((ContactCollection)base["contacts"]); }
    }
Will
  • 176
  • 2
  • 5
  • I would like to avoid doing this but I don't know why someone downvoted you. If this is the only solution, I'll accept it in the future. Also, thanks for your first answer after five years of being here! :) – sirdank Jul 15 '16 at 19:22
  • 1
    Note you can even declare the ContactCollection as private wich is interesting if you want to mask the property from callers & intellisense. – Simon Mourier Jul 17 '16 at 06:20
  • Lol... my first answer and I was immediately downvoted! I was wise to be afraid ;) – Will Jul 18 '16 at 15:48
  • Although the need for this solution seems silly to me, with the private modifier it works about as well as I could hope. Thanks! – sirdank Jul 22 '16 at 12:26
1

One solution if you only need a list of string, is to declare a comma-separated (or any separator) list of values like this:

    [ConfigurationProperty("contacts")]
    [TypeConverter(typeof(StringSplitConverter))]
    public IEnumerable<string> Contacts
    {
        get
        {
            return (IEnumerable<string>)base["contacts"];
        }
    }

With this TypeConverter class:

public class StringSplitConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        return string.Format("{0}", value).Split(',');
    }
}

Your .config file would then simply be declared like this:

<configuration>
  ...
  <mySection contacts="bill,joe" />
  ...
</configuration>

Note this doesn't work for collections, when you always need to explicitely declare a property, like in Will's answer.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298