84

The following example fills the ItemsControl with a List of BackupDirectories which I get from code.

How can I change this so that I get the same information from the app.config file?

XAML:

<Window x:Class="TestReadMultipler2343.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="160"/>
        </Grid.ColumnDefinitions>
        <TextBlock 
            Grid.Row="0"
            Grid.Column="0"
            Text="Title:"/>
        <TextBlock 
            Grid.Row="0"
            Grid.Column="1" 
            Text="{Binding Title}"/>
        <TextBlock 
            Grid.Row="1"
            Grid.Column="0"
            Text="Backup Directories:"/>
        <ItemsControl 
            Grid.Row="1"
            Grid.Column="1"
            ItemsSource="{Binding BackupDirectories}"/>
    </Grid>
</Window>

code-behind:

using System.Collections.Generic;
using System.Windows;
using System.Configuration;
using System.ComponentModel;

namespace TestReadMultipler2343
{
    public partial class Window1 : Window, INotifyPropertyChanged
    {

        #region ViewModelProperty: Title
        private string _title;
        public string Title
        {
            get
            {
                return _title;
            }

            set
            {
                _title = value;
                OnPropertyChanged("Title");
            }
        }
        #endregion

        #region ViewModelProperty: BackupDirectories
        private List<string> _backupDirectories = new List<string>();
        public List<string> BackupDirectories
        {
            get
            {
                return _backupDirectories;
            }

            set
            {
                _backupDirectories = value;
                OnPropertyChanged("BackupDirectories");
            }
        }
        #endregion

        public Window1()
        {
            InitializeComponent();
            DataContext = this;

            Title = ConfigurationManager.AppSettings.Get("title");

            GetBackupDirectoriesInternal();
        }

        void GetBackupDirectoriesInternal()
        {
            BackupDirectories.Add(@"C:\test1");
            BackupDirectories.Add(@"C:\test2");
            BackupDirectories.Add(@"C:\test3");
            BackupDirectories.Add(@"C:\test4");
        }

        void GetBackupDirectoriesFromConfig()
        {
            //BackupDirectories = ConfigurationManager.AppSettings.GetValues("backupDirectories");
        }


        #region INotifiedProperty Block
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

    }
}

app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="title" value="Backup Tool" />
    <!--<add key="backupDirectories">
      <add value="C:\test1"/>
      <add value="C:\test2"/>
      <add value="C:\test3"/>
      <add value="C:\test4"/>
    </add>-->
  </appSettings>
</configuration>
Manfred Radlwimmer
  • 13,257
  • 13
  • 53
  • 62
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047
  • simplest solution is to use [System.Collections.Specialized.StringCollection](http://msdn.microsoft.com/en-us/library/system.collections.specialized.stringcollection.aspx): Answered for question: [Store String Array In appSettings?](http://stackoverflow.com/q/10419116/155207) – Serkan Apr 23 '13 at 09:38
  • take look at my new answer http://stackoverflow.com/a/29487138/105445 – Wahid Bitar Apr 07 '15 at 08:44
  • possible duplicate of [How to implement a ConfigurationSection with a ConfigurationElementCollection](http://stackoverflow.com/questions/3935331/how-to-implement-a-configurationsection-with-a-configurationelementcollection) – Liam Jul 22 '15 at 16:08
  • Seems to be answered at https://stackoverflow.com/a/32637544/69663 as well – unhammer Aug 12 '19 at 19:14

8 Answers8

160

You could have them semi-colon delimited in a single value, e.g.

App.config

<add key="paths" value="C:\test1;C:\test2;C:\test3" />

C#

var paths = new List<string>(ConfigurationManager.AppSettings["paths"].Split(new char[] { ';' }));
Adam Ralph
  • 29,453
  • 4
  • 60
  • 67
  • 21
    That is such a quick way of doing it if you don't need the overhead of a custom config section. This is good enough IMO. – Peter Kelly Jul 08 '10 at 15:08
  • 6
    that was my thinking... custom config sections are very nice and very powerful, but perhaps overkill for a simple string array. – Adam Ralph Jul 10 '10 at 06:46
  • 2
    This is the way I'd done it for a long time... today I am converting to a config section because managing my list (it is a list of pluging classes to load, which can change depending on the environment) has gotten messy with 30+ strings in it. – Moose Feb 05 '11 at 14:33
  • 1
    This solution is really good idea. Just not so good when I want to edit config - erase some of paths. Adding is not a problem. – Ms. Nobody Jun 13 '13 at 12:06
  • 7
    Ugh. Great solution, but just need to mention you have to add System.Configuration as a reference (can't just use the "using") in order to get access to ConfigurationManager. – Jiminion Jun 18 '14 at 15:20
  • Of course, these days I'd just used ConfigR! ;-) http://www.microsoft.com/en-gb/developers/articles/week04jan15/introduction-to-configr-the-solution-to-your-application-configuration-problems – Adam Ralph Feb 12 '15 at 18:26
  • @Moose If you use a user-friendly config screen to save values to app.config, 30+ strings would not be a problem with the semicolon-delimited config value – Roland Jan 22 '16 at 14:06
138

You can create your own custom config section in the app.config file. There are quite a few tutorials around to get you started. Ultimately, you could have something like this:

<configSections>
    <section name="backupDirectories" type="TestReadMultipler2343.BackupDirectoriesSection, TestReadMultipler2343" />
  </configSections>

<backupDirectories>
   <directory location="C:\test1" />
   <directory location="C:\test2" />
   <directory location="C:\test3" />
</backupDirectories>

To complement Richard's answer, this is the C# you could use with his sample configuration:

using System.Collections.Generic;
using System.Configuration;
using System.Xml;

namespace TestReadMultipler2343
{
    public class BackupDirectoriesSection : IConfigurationSectionHandler
    {
        public object Create(object parent, object configContext, XmlNode section)
        {
            List<directory> myConfigObject = new List<directory>();

            foreach (XmlNode childNode in section.ChildNodes)
            {
                foreach (XmlAttribute attrib in childNode.Attributes)
                {
                    myConfigObject.Add(new directory() { location = attrib.Value });
                }
            }
            return myConfigObject;
        }
    }

    public class directory
    {
        public string location { get; set; }
    }
}

Then you can access the backupDirectories configuration section as follows:

List<directory> dirs = ConfigurationManager.GetSection("backupDirectories") as List<directory>;
Victoria
  • 324
  • 2
  • 14
Richard Nienaber
  • 10,324
  • 6
  • 55
  • 66
  • 19
    Am I missing something, or do none of the three tutorials actually show you how to have a list of elements? – Chuu Dec 08 '14 at 23:30
  • @Chuu Check out the examples on this page: https://msdn.microsoft.com/en-us/library/system.configuration.configurationelementcollection.aspx – bonh Sep 14 '15 at 15:02
  • 1
    @Demodave you can always check my answer: http://stackoverflow.com/a/33544322/1955317 here I have provided the C# code needed to do what Richard is talking about :) – Squazz Jul 01 '16 at 08:57
  • @Demodave, the C# code are in the tutorial links in my answer. – Richard Nienaber Dec 06 '16 at 09:40
  • when I used the same code above, im getting the below error: Could not load file or assembly 'namespace' or one of its dependencies. The system cannot find the file specified – Muni Sep 03 '20 at 04:18
37

I love Richard Nienaber's answer, but as Chuu pointed out, it really doesn't tell how to accomplish what Richard is refering to as a solution. Therefore I have chosen to provide you with the way I ended up doing this, ending with the result Richard is talking about.

The solution

In this case I'm creating a greeting widget that needs to know which options it has to greet in. This may be an over-engineered solution to OPs question as I am also creating an container for possible future widgets.

First I set up my collection to handle the different greetings

public class GreetingWidgetCollection : System.Configuration.ConfigurationElementCollection
{
    public List<IGreeting> All { get { return this.Cast<IGreeting>().ToList(); } }

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

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

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

Then we create the acutal greeting element and it's interface

(You can omit the interface, that's just the way I'm always doing it.)

public interface IGreeting
{
    string Greeting { get; set; }
}

public class GreetingElement : System.Configuration.ConfigurationElement, IGreeting
{
    [ConfigurationProperty("greeting", IsRequired = true)]
    public string Greeting
    {
        get { return (string)this["greeting"]; }
        set { this["greeting"] = value; }
    }
}

The greetingWidget property so our config understands the collection

We define our collection GreetingWidgetCollection as the ConfigurationProperty greetingWidget so that we can use "greetingWidget" as our container in the resulting XML.

public class Widgets : System.Configuration.ConfigurationSection
{
    public static Widgets Widget => ConfigurationManager.GetSection("widgets") as Widgets;

    [ConfigurationProperty("greetingWidget", IsRequired = true)]
    public GreetingWidgetCollection GreetingWidget
    {
        get { return (GreetingWidgetCollection) this["greetingWidget"]; }
        set { this["greetingWidget"] = value; }
    }
}

The resulting XML

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <widgets>
       <greetingWidget>
           <add greeting="Hej" />
           <add greeting="Goddag" />
           <add greeting="Hello" />
           ...
           <add greeting="Konnichiwa" />
           <add greeting="Namaskaar" />
       </greetingWidget>
    </widgets>
</configuration>

And you would call it like this

List<GreetingElement> greetings = Widgets.GreetingWidget.All;
Community
  • 1
  • 1
Squazz
  • 3,912
  • 7
  • 38
  • 62
  • Where does the GreetingWidget property go? – Rhyous Aug 03 '16 at 16:07
  • @Rhyous I updated my answer to clarify this. But this also greatly depends on how much you modify the code for your case :) – Squazz Aug 03 '16 at 17:06
  • 1
    @Squazz I think he may have been asking where the C# `GreetingWidget` property should be defined. I assume that is intended to be added to the `GreetingWidgetCollection` class? – PseudoPsyche Aug 22 '16 at 15:36
  • @PseudoPsyche GreetingWidget is defined in the second last code stump (public GreetingWidgetCollection GreetingWidget). This is the magic that defines what we are calling it in web.config :) I deliberately chose this to have concrete and clear naming all around :) – Squazz Aug 22 '16 at 20:01
  • @Rhyous, in case PseudoPsyche is right about what you were asking for, I have now added an explanation, I don't know if that is helping making the code more clear? – Squazz Aug 22 '16 at 20:05
  • 1
    @Squazz I was mentioning that because I was confused as well on that. I'm still having a problem getting it to work for me though. Every time I try to read from the collection, I get a stack overflow exception. I'm assuming the usage for this would simply be: `List greetingWidgets = new GreetingWidgetCollection().GreetingWidget.All;`? Also, how would the `section` need to be defined in the `app.config` `configSections` node? Like this: `
    `?
    – PseudoPsyche Aug 22 '16 at 20:35
  • 1
    @Squazz Yes, that is what I was asking. I figured it out before you responded. I got it working. – Rhyous Sep 01 '16 at 14:38
  • @PseudoPsyche, I never had a stack overflow exception. Sounds unrelated. Likely it is a bug in code where you are calling a property from the getter of the same property. – Rhyous Sep 01 '16 at 14:38
  • @Rhyous glad you got it working. Sorry that the example was not complete... – Squazz Sep 01 '16 at 17:20
  • What exactly is the point of the line: `public static Widgets Widget => ConfigurationManager.GetSection("widgets") as Widgets;` ? I always have to call it like this: `Widgets w = new Widgets(); List = w.GreetingWidget.All` However it always returns an empty list. Calling `ConfigurationManager.GetSection` returns what I'm expecting however. – Tyler Apr 01 '20 at 18:51
34

There's actually a very little known class in the BCL for this purpose exactly: CommaDelimitedStringCollectionConverter. It serves as a middle ground of sorts between having a ConfigurationElementCollection (as in Richard's answer) and parsing the string yourself (as in Adam's answer).

For example, you could write the following configuration section:

public class MySection : ConfigurationSection
{
    [ConfigurationProperty("MyStrings")]
    [TypeConverter(typeof(CommaDelimitedStringCollectionConverter))]
    public CommaDelimitedStringCollection MyStrings
    {
        get { return (CommaDelimitedStringCollection)base["MyStrings"]; }
    }
}

You could then have an app.config that looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="foo" type="ConsoleApplication1.MySection, ConsoleApplication1"/>
  </configSections>
  <foo MyStrings="a,b,c,hello,world"/>
</configuration>

Finally, your code would look like this:

var section = (MySection)ConfigurationManager.GetSection("foo");
foreach (var s in section.MyStrings)
    Console.WriteLine(s); //for example
John Smith
  • 7,243
  • 6
  • 49
  • 61
Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
  • 5
    I'm not sure why you would go through creating a custom section, but then have it limited to a delimited string; however it is something I hadn't seen or known of. It is interesting idea and well documented, thanks for providing! – Matt Klinker Aug 03 '12 at 16:44
9

Had the same problem, but solved it in a different way. It might not be the best solution, but its a solution.

in app.config:

<add key="errorMailFirst" value="test@test.no"/>
<add key="errorMailSeond" value="krister@tets.no"/>

Then in my configuration wrapper class, I add a method to search keys.

        public List<string> SearchKeys(string searchTerm)
        {
            var keys = ConfigurationManager.AppSettings.Keys;
            return keys.Cast<object>()
                       .Where(key => key.ToString().ToLower()
                       .Contains(searchTerm.ToLower()))
                       .Select(key => ConfigurationManager.AppSettings.Get(key.ToString())).ToList();
        }

For anyone reading this, i agree that creating your own custom configuration section is cleaner, and more secure, but for small projects, where you need something quick, this might solve it.

ruffen
  • 1,695
  • 2
  • 25
  • 51
  • ...aside from the fact that you can query `AppSettings.Keys` directly for string equality, why the cast to `object` followed by all the casts back to `string`? – brichins Apr 04 '17 at 16:01
  • Considering I wrote this 4 years ago, I cant remember. When I see it now the casts look unnecessary. – ruffen Jun 19 '17 at 13:38
8

In App.config:

<add key="YOURKEY" value="a,b,c"/>

In C#:

string[] InFormOfStringArray = ConfigurationManager.AppSettings["YOURKEY"].Split(',').Select(s => s.Trim()).ToArray();
List<string> list = new List<string>(InFormOfStringArray);
Kurt Van den Branden
  • 11,995
  • 10
  • 76
  • 85
Divya
  • 373
  • 4
  • 3
  • Excellent, but I have a question, I am a bit confused on the reasoning behind putting those values into an array and then placing them into a list, when you could simply do .ToList() instead? – EasyE Jun 02 '17 at 14:16
2

Found this thread when searching for how to get a list from appsettings.json.

{
  "foo": {
    "bar": [
      "1",
      "2",
      "3"
    ]
  }
}

There you can do it like this:

Configuration.GetSection("foo:bar").Get<List<string>>()

Source:

https://stackoverflow.com/a/42296371/3850405

Ogglas
  • 62,132
  • 37
  • 328
  • 418
  • 1
    You cant really use this for the app.config though, since the app config uses xml and not json. And creating a custom json file is not efficient – Icad Nov 04 '21 at 13:55
0

Thank for the question. But I have found my own solution to this problem. At first, I created a method

    public T GetSettingsWithDictionary<T>() where T:new()
    {
        IConfigurationRoot _configurationRoot = new ConfigurationBuilder()
        .AddXmlFile($"{Assembly.GetExecutingAssembly().Location}.config", false, true).Build();

        var instance = new T();
        foreach (var property in typeof(T).GetProperties())
        {
            if (property.PropertyType == typeof(Dictionary<string, string>))
            {
                property.SetValue(instance, _configurationRoot.GetSection(typeof(T).Name).Get<Dictionary<string, string>>());
                break;
            }

        }
        return instance;
    }

Then I used this method to produce an instance of a class

var connStrs = GetSettingsWithDictionary<AuthMongoConnectionStrings>();

I have the next declaration of class

public class AuthMongoConnectionStrings
{
    public Dictionary<string, string> ConnectionStrings { get; set; }
}

and I store my setting in App.config

<configuration>    
  <AuthMongoConnectionStrings
  First="first"
  Second="second"
  Third="33" />
</configuration> 
Larissa Savchekoo
  • 6,412
  • 1
  • 13
  • 7