如何在WPF中从app.config获取一个List<string>值集合?

84
以下示例将使用代码中获取的List填充ItemsControl,列表类型为BackupDirectories如何更改以从app.config文件获取相同的信息? 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>

代码后置文件:

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>

最简单的解决方案是使用System.Collections.Specialized.StringCollection:回答了问题: Store String Array In appSettings? - Serkan
请看一下我的新回答 https://dev59.com/A3E85IYBdhLWcg3wXCIv#29487138 - Wahid Bitar
似乎已经在https://dev59.com/w3E85IYBdhLWcg3wkkQ8#32637544得到了回答。 - unhammer
8个回答

161
你可以将它们以分号分隔的形式放在单个值中,例如:

App.config

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

C#

var paths = new List<string>(ConfigurationManager.AppSettings["paths"].Split(new char[] { ';' }));

21
如果您不需要自定义配置部分的开销,那么这是一个非常快速的方法。在我看来,这已经足够好了。 - Peter Kelly
6
那是我的想法...自定义配置部分非常好用而且非常强大,但对于一个简单的字符串数组来说可能过于繁琐。 - Adam Ralph
2
这是我长期以来的做法...今天我正在转换为配置部分,因为管理我的列表(它是一个要加载的插件类列表,根据环境可能会发生变化)已经变得混乱,其中有30多个字符串。 - Moose
1
这个解决方案真的很好。只是当我想编辑配置文件并删除某些路径时,就不那么好了。添加路径不是问题。 - Ms. Nobody
7
啊。非常好的解决方案,但需要提到你必须将System.Configuration作为引用添加(不能仅使用“using”)才能访问ConfigurationManager。 - Jiminion
显示剩余2条评论

138
你可以在app.config文件中创建自己的自定义配置部分。有相当多的教程可以帮助你入门。最终,你可以拥有像这样的内容:

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>

为了补充Richard的回答,这是与他示例配置一起使用的C#代码:
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; }
    }
}

然后,您可以按照以下方式访问backupDirectories配置部分:

List<directory> dirs = ConfigurationManager.GetSection("backupDirectories") as List<directory>;

20
三个教程中没有一个教你如何创建元素列表,我是否漏掉了什么? - Chuu
@Chuu 请查看此页面上的示例:https://msdn.microsoft.com/zh-cn/library/system.configuration.configurationelementcollection.aspx - bonh
1
@Demodave,你可以随时查看我的答案:https://dev59.com/NnI-5IYBdhLWcg3wlpeW#33544322 在那里,我提供了所需的C#代码来完成Richard所说的事情 :) - Squazz
@Demodave,C#代码在我的答案中的教程链接中。 - Richard Nienaber
当我使用上述相同的代码时,会出现以下错误: 无法加载文件或程序集“命名空间”或其某个依赖项。系统找不到指定的文件。 - Muni

37

我喜欢Richard Nienaber的回答,但正如Chuu所指出的那样,它并没有真正告诉我们如何实现Richard所提到的解决方案。

因此,我选择向您展示我最终采取的方法,以达到Richard所述的结果。

解决方案

在这种情况下,我正在创建一个问候小部件,需要知道它可以使用哪些选项进行问候。这可能是对OP问题的过度设计解决方案,因为我还创建了一个容器以用于可能的未来小部件。

首先,我设置了我的集合以处理不同的问候

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;
    }
}

然后我们创建实际的问候元素及其接口

(你可以省略接口,那只是我一直在做的方式。)

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; }
    }
}

greetingWidget属性使我们的配置文件了解该集合

我们将我们的集合GreetingWidgetCollection定义为ConfigurationPropertygreetingWidget,以便我们可以在生成的XML中使用“greetingWidget”作为容器。

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; }
    }
}

生成的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>

然后你可以这样调用它

List<GreetingElement> greetings = Widgets.GreetingWidget.All;

@Rhyous,我更新了我的答案以澄清这一点。但这也在很大程度上取决于您为您的情况修改了多少代码 :) - Squazz
1
@Squazz 我认为他可能在问C#GreetingWidget属性应该在哪里定义。 我假设这是要添加到GreetingWidgetCollection类中的? - PseudoPsyche
1
@Squazz 我提到这个是因为我也很困惑。但我仍然无法解决我的问题。每次我尝试从集合中读取时,都会出现堆栈溢出异常。我假设使用方式应该是:List<IGreeting> greetingWidgets = new GreetingWidgetCollection().GreetingWidget.All;?另外,在 app.configconfigSections 节点中,section 需要如何定义?像这样:<section name="greetingWidget" type="WidgetApp.GreetingWidgetCollection, GreetingWidget"/> - PseudoPsyche
1
@Squazz 是的,那就是我想问的。在你回复之前,我已经解决了它。我让它工作了。 - Rhyous
@Rhyous 很高兴你已经解决了问题。很抱歉示例不完整... - Squazz
显示剩余5条评论

34

实际上,在BCL中有一个很少为人知的类专门用于此目的:CommaDelimitedStringCollectionConverter。 它在某种程度上充当了ConfigurationElementCollection(如Richard的答案)和自己解析字符串(如Adam的答案)之间的中间地带。

例如,您可以编写以下配置节:

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

你可以拥有一个这样的app.config文件:

<?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>

最终,你的代码将如下所示:

var section = (MySection)ConfigurationManager.GetSection("foo");
foreach (var s in section.MyStrings)
    Console.WriteLine(s); //for example

5
我不确定为什么你要创建一个自定义部分,但又将其限制为分隔字符串;不过这是我以前从未见过或知道的。这是一个有趣的想法,并且记录得很好,感谢您的提供! - Matt Klinker

9

我遇到了相同的问题,但是用不同的方法解决了它。这可能不是最好的解决方案,但它是一个解决方案。

在app.config文件中:

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

然后在我的配置包装类中,我添加了一个搜索键的方法。

        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();
        }

任何阅读此文的人,我同意创建自己的自定义配置节更加干净、安全,但对于需要快速解决问题的小项目,这可能是一个解决方案。

除了可以直接查询AppSettings.Keys以进行字符串相等性判断之外,为什么需要将其转换为object,然后再将所有内容都强制转换回string呢? - brichins
考虑到我是4年前写的,我已经记不清了。现在看起来,强制转换似乎是不必要的。 - ruffen

8
在App.config文件中:
<add key="YOURKEY" value="a,b,c"/>

在C#中:

string[] InFormOfStringArray = ConfigurationManager.AppSettings["YOURKEY"].Split(',').Select(s => s.Trim()).ToArray();
List<string> list = new List<string>(InFormOfStringArray);

很好,但我有一个问题,我有点困惑为什么要将这些值放入数组中,然后再将它们放入列表中,而不是直接使用 .ToList()? - EasyE

2
当我在搜索如何从 appsettings.json 获取列表时,发现了这个帖子。
{
  "foo": {
    "bar": [
      "1",
      "2",
      "3"
    ]
  }
}

你可以这样做:

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

Source:

https://dev59.com/WVkT5IYBdhLWcg3wB7Is#42296371


1
你不能真正将它用于app.config,因为app.config使用xml而不是json。创建自定义的json文件也不是高效的方法。 - Icad

0

感谢您的问题。但是我已经找到了自己解决这个问题的方法。 首先,我创建了一个方法

    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;
    }

然后我使用这种方法来生成一个类的实例

var connStrs = GetSettingsWithDictionary<AuthMongoConnectionStrings>();

我有一个类的下一个声明

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

我将我的设置存储在App.config文件中

<configuration>    
  <AuthMongoConnectionStrings
  First="first"
  Second="second"
  Third="33" />
</configuration> 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接