【问题标题】:.NET configuration with multiple instances of a set of parameters具有一组参数的多个实例的 .NET 配置
【发布时间】:2015-07-11 02:25:53
【问题描述】:

我有一个可以连接到多个服务器的应用程序。服务器的实际数量直到运行时才知道,并且可能每天都在变化。完全定义一个服务器需要几个实际参数。

我正在尝试使用对应用程序配置的 .NET 支持来配置应用程序。

配置文件类似于:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup
           name="userSettings"
            type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="server"
                type="System.Configuration.SingleTagSectionHandler"
                allowExeDefinition="MachineToLocalUser"
                requirePermission="false" />
        </sectionGroup>
    </configSections>
    <userSettings>
        <server name="washington">
            <add name="host" value="abc.presidents.com"/>
            <add name="port" value="1414"/>
            <add name="credentials" value="george"/>
        </server>
        <server name="adams">
            <add name="host" value="def.presidents.com"/>
            <add name="port" value="1419"/>
            <add name="credentials" value="john"/>
        </server>
        <!--insert more server definitions here -->
    </userSettings>
</configuration>

我尝试使用类似这样的代码阅读此内容(WriteLines 用于诊断问题。当/如果它有效时它们会消失。):

try
{
    var exeConfiguration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
    Console.WriteLine(exeConfiguration);

    var userSettings = exeConfiguration.GetSectionGroup("userSettings");
    Console.WriteLine("{0}: {1}", userSettings, userSettings.Name);

    var sections = userSettings.Sections;
    Console.WriteLine("{0}: {1}", sections, sections.Count);
    foreach (ConfigurationSection section in sections)
    {
        Console.WriteLine("{0}", section);
        // todo Here's where we capture information about a server
    }

}
catch (System.Exception ex)
{
    Console.WriteLine("Exception: {0}", ex.Message);
}

这会从 foreach 中引发异常并产生以下输出:

    System.Configuration.Configuration
    System.Configuration.UserSettingsGroup: userSettings
    System.Configuration.ConfigurationSectionCollection: 1
    Exception: Sections must only appear once per config file.  See the help topic <location> for exceptions.

如果我删除第二台服务器,只留下华盛顿,它会“工作”产生以下输出:

System.Configuration.Configuration
System.Configuration.ConfigurationSectionGroup: userSettings
System.Configuration.ConfigurationSectionCollection: 1
System.Configuration.DefaultSection

我已经尝试了该主题的其他变体(嵌套 sectionGroups 等),但没有找到解决方案。我发现的各种教程示例似乎希望我为每个服务器创建一个单独的类。这显然是不切实际的,而且应该是不必要的,因为所有服务器都是平等的。

问题:

  • System.Configuration 是否支持相关设置集合的概念(如结构数组)?
  • 如果是这样.. 怎么办?
  • 如果没有.. 是否有另一种方法来存储支持此概念的持久配置信息,还是我必须自己动手?

进度报告

根据 Robert McKee 的部分回答,我修改了 xml,如下所示:

            <section name="servers"
                type="System.Configuration.DefaultSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

<servers>
    <add name="washington" host="abc.presidents.com" port="1414" credentials="george"/>
    <add name="adams" host="def.presidents.com" port="1419" credentials="john"/>
    <!--insert more server definitions here -->
</servers>

这是一个致命问题的改进。如您所见,我将section元素的type属性更改为将类命名为ystem.Configuration.DefaultSectionDefaultSection成功读取配置信息(我认为,至少它不会抱怨。)但它没有暴露任何方式来访问它读取的信息!

因此我需要使用其他类型的*Section 类。 Microsoft 提供了从 ConfigurationSection 基类派生的 95 个类,但除 DefaultSectionClientSettingsSection 之外的所有类似乎都针对特殊情况(url、数据库连接字符串、日期和时间等)@987654335 @ 甚至不会阅读服务器部分——抱怨 &lt;add/&gt; 不是有效的嵌套元素。

所以,底线是我将不得不编写一个自定义ConfigurationSection 来处理这些设置。如果我得到它的工作,我会添加一个最终解决方案的答案(除非有人首先提供更好的答案。)

【问题讨论】:

    标签: c# .net configuration


    【解决方案1】:

    结论

    • 如果不创建自定义属性类,则无法支持属性集的集合。
    • 对创建自定义属性类的支持非常好。
    • 用于创建自定义属性类的文档非常糟糕,但我终于找到了一个不错的 overview document 来帮助我找到答案。

    配置文件

    我最终得到的 app.config 文件是:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <configSections>
            <sectionGroup name="userSettings" 
                          type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
                <section name="executiveBranch" 
                         type="PropProto.Properties.ExecutiveBranchSection, PropProto, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" 
                         allowExeDefinition="MachineToLocalUser" 
                         requirePermission="false" />
            </sectionGroup>
        </configSections>
        <userSettings>
            <executiveBranch>
                <presidents>
                    <president key="first"
                               name="George Washington"
                               legacy="The Fother of Our County"
                               />
                    <president key="honestAbe"
                               name="Abraham Lincoln"
                               legacy="Freed the Slaves"
                               />
                    <president key="who"
                               name="Chester Arthur"
                               />
                    <president key="dubya"
                               name="George W. Bush"
                               legacy="Mission Accomplished!!!"
                               />
                    <president key="barack"
                               name="Barack Obama"
                               legacy="Affordable Health Care"
                               />
    
                </presidents>
            </executiveBranch>
        </userSettings>
    </configuration>
    

    嵌套级别比我预期的(或想要的)多一个。那是因为presidentsConfigurationElementCollection,而userSettings不能直接包含ConfigurationElementCollection,所以我不得不引入executiveBranch

    使用配置

    读取这些设置的代码是:

    var exeConfiguration = ConfigurationManager.OpenExeConfiguration(
          ConfigurationUserLevel.PerUserRoamingAndLocal);
    var userSettings = exeConfiguration.GetSectionGroup("userSettings");
    var executiveBranch = (ExecutiveBranchSection)userSettings.Sections.Get(
            ExecutiveBranchSection.Tag);
    var presidents = executiveBranch.Presidents;
    foreach (President president in presidents)
    {
        Console.WriteLine("{0}: {1}", president.Name, president.Legacy);
    }
    

    自定义属性类

    以及使所有这些工作的自定义类:

    public class ExecutiveBranchSection : ConfigurationSection
    {
        public const string Tag = "executiveBranch";
        [ConfigurationProperty(PresidentCollection.Tag)]
        public PresidentCollection Presidents { get { return (PresidentCollection)base[PresidentCollection.Tag]; } }
    }
    
    [ConfigurationCollection(typeof(President),
        CollectionType = ConfigurationElementCollectionType.BasicMap,
        AddItemName = President.Tag)]
    public class PresidentCollection : ConfigurationElementCollection
    {
        public const string Tag = "presidents";
        protected override string ElementName { get { return President.Tag; } }
    
        public President this[int index]
        {
            get { return (President)base.BaseGet(index); }
            set
            {
                if (base.BaseGet(index) != null)
                {
                    base.BaseRemoveAt(index);
                }
    
                base.BaseAdd(index, value);
            }
        }
    
        new public President this[string name] { get { return (President)base.BaseGet(name); } }
    
        protected override ConfigurationElement CreateNewElement()
        {
            return new President();
        }
    
        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as President).Key;
        }
    }
    
    public class President : ConfigurationElement
    {
        public const string Tag = "president";
        [ConfigurationProperty("key", IsRequired = true)]
        public string Key { get { return (string)base["key"]; } }
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name { get { return (string)base["name"]; } }
        [ConfigurationProperty("legacy", IsRequired = false)]
        public string Legacy { get { return (string)base["legacy"]; } }
    }
    

    【讨论】:

    • 感谢您发布最终的工作结果!我从这个和 ConnectionStringSettingsCollection 的 .Net Framework 源代码中获得了灵感,它看起来与您在此处所拥有的非常相似。我将这个链接发布到 .Net 参考源,因为它验证了您的方法,并包含大量其他有用的暗示可供收集。 referencesource.microsoft.com/#System.Configuration/System/…
    【解决方案2】:

    没有使用自定义配置部分,但从纯粹逻辑的角度来看,这不是更合适:

    <userSettings>
        <servers>
            <add name="washington" host="abc.presidents.com" port="1414" credentials="george"/>
            <add name="adams" host="def.presidents.com" port="1419" credentials="john"/>
            <!--insert more server definitions here -->
        </servers>
        <!-- insert more user settings here -->
    </userSettings>
    

    你可以用其他方式来做,但是如果你想要一个“服务器”的集合,那么它必须在一个像“服务器”这样的分组元素中。您不能只在一个父元素中拥有多个“服务器”,还可以包含其他类型的子元素。组内的标签几乎总是“添加”。

    无论如何,“System.Configuration.SingleTagSectionHandler”肯定不是正确的类型。

    【讨论】:

    • 有趣。我从未见过使用namevalue 属性以外的任何东西的add 元素示例。我正在尝试这个,但遇到了其他问题。敬请期待......
    • 奇数。您从未见过连接字符串部分?还是页面/命名空间?还是httpHandlers?模块? SqlDependencies?输出缓存设置?编译/汇编?
    • 虽然,system.webserver/runtime/assemblybinding 确实有多个dependentAssembly 子级,它们不使用“add”作为标签,而是使用“dependentAssembly”作为带有自己子级的标签。所以你也可以这样做,只是不太常见。
    • 讽刺不是特别赞赏(或必要)但是,是的,我是使用 System.Configuration 的新手,我不使用数据库,或 http,或者......所以我在 MSDN 中大量但大多无用的文档中,并没有沿着这些路径走下去。令人惊讶的是,除了您想知道的之外,他们如何有效地告诉您一切。 (我知道,这是我的讽刺。)
    • 抱歉,我没有讽刺的意思,只是不常遇到正在尝试创建自定义配置且尚未使用 Web 或数据库的人。
    猜你喜欢
    • 2021-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多