How to get a List<string> collection of values from app.config in WPF?
Asked Answered
P

8

86

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>
Pump answered 22/11, 2009 at 15:59 Comment(4)
simplest solution is to use System.Collections.Specialized.StringCollection: Answered for question: Store String Array In appSettings?Contreras
take look at my new answer https://mcmap.net/q/243439/-multiple-values-for-a-single-config-keyCherey
possible duplicate of How to implement a ConfigurationSection with a ConfigurationElementCollectionVenditti
Seems to be answered at https://mcmap.net/q/235342/-custom-app-config-section-with-a-simple-list-of-quot-add-quot-elements as wellTacye
C
140

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>;
Coach answered 22/11, 2009 at 18:6 Comment(5)
Am I missing something, or do none of the three tutorials actually show you how to have a list of elements?Romona
@Romona Check out the examples on this page: msdn.microsoft.com/en-us/library/…Lutanist
@Demodave you can always check my answer: https://mcmap.net/q/238141/-how-to-get-a-list-lt-string-gt-collection-of-values-from-app-config-in-wpf here I have provided the C# code needed to do what Richard is talking about :)Winola
@Demodave, the C# code are in the tutorial links in my answer.Coach
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 specifiedVaginitis
B
163

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[] { ';' }));
Bastinado answered 22/11, 2009 at 16:26 Comment(7)
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.Middaugh
that was my thinking... custom config sections are very nice and very powerful, but perhaps overkill for a simple string array.Bastinado
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.Allonym
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.Sanitation
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.Tedtedd
Of course, these days I'd just used ConfigR! ;-) microsoft.com/en-gb/developers/articles/week04jan15/…Bastinado
@Allonym 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 valueGrunenwald
C
140

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>;
Coach answered 22/11, 2009 at 18:6 Comment(5)
Am I missing something, or do none of the three tutorials actually show you how to have a list of elements?Romona
@Romona Check out the examples on this page: msdn.microsoft.com/en-us/library/…Lutanist
@Demodave you can always check my answer: https://mcmap.net/q/238141/-how-to-get-a-list-lt-string-gt-collection-of-values-from-app-config-in-wpf here I have provided the C# code needed to do what Richard is talking about :)Winola
@Demodave, the C# code are in the tutorial links in my answer.Coach
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 specifiedVaginitis
W
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;
Winola answered 5/11, 2015 at 12:16 Comment(10)
Where does the GreetingWidget property go?Cockspur
@Cockspur I updated my answer to clarify this. But this also greatly depends on how much you modify the code for your case :)Winola
@Winola 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?Quianaquibble
@Quianaquibble 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 :)Winola
@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?Winola
@Winola 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<IGreeting> greetingWidgets = new GreetingWidgetCollection().GreetingWidget.All;? Also, how would the section need to be defined in the app.config configSections node? Like this: <section name="greetingWidget" type="WidgetApp.GreetingWidgetCollection, GreetingWidget"/>?Quianaquibble
@Winola Yes, that is what I was asking. I figured it out before you responded. I got it working.Cockspur
@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.Cockspur
@Cockspur glad you got it working. Sorry that the example was not complete...Winola
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<GreetingElement> = w.GreetingWidget.All However it always returns an empty list. Calling ConfigurationManager.GetSection returns what I'm expecting however.Lithea
Z
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
Zoo answered 13/9, 2011 at 16:10 Comment(1)
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!Ab
M
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="[email protected]"/>
<add key="errorMailSeond" value="[email protected]"/>

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.

Marisolmarissa answered 24/4, 2013 at 12:59 Comment(2)
...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?Evince
Considering I wrote this 4 years ago, I cant remember. When I see it now the casts look unnecessary.Marisolmarissa
A
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);
Aq answered 16/12, 2016 at 14:1 Comment(1)
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?Soft
J
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://mcmap.net/q/165576/-how-to-extract-a-list-from-appsettings-json-in-net-core

Jocosity answered 6/8, 2021 at 23:9 Comment(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 efficientUncommunicative
A
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> 
Alarice answered 20/8, 2019 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.