【问题标题】:How to implement a ConfigurationSection with a ConfigurationElementCollection如何使用 ConfigurationElementCollection 实现 ConfigurationSection
【发布时间】:2011-04-25 12:29:56
【问题描述】:

我正在尝试在项目中实现自定义配置部分,并且一直遇到我不理解的异常。我希望有人可以在这里填补空白。

我有App.config,看起来像这样:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="ServicesSection" type="RT.Core.Config.ServicesConfigurationSectionHandler, RT.Core"/>
    </configSections>
    <ServicesSection type="RT.Core.Config.ServicesSection, RT.Core">
            <Services>
                <AddService Port="6996" ReportType="File" />
                <AddService Port="7001" ReportType="Other" />
            </Services>
        </ServicesSection>
</configuration>

我有一个 ServiceConfig 元素定义如下:

public class ServiceConfig : ConfigurationElement
  {
    public ServiceConfig() {}

    public ServiceConfig(int port, string reportType)
    {
      Port = port;
      ReportType = reportType;
    }

    [ConfigurationProperty("Port", DefaultValue = 0, IsRequired = true, IsKey = true)]
    public int Port 
    {
      get { return (int) this["Port"]; }
      set { this["Port"] = value; }
    }

    [ConfigurationProperty("ReportType", DefaultValue = "File", IsRequired = true, IsKey = false)]
    public string ReportType
    {
      get { return (string) this["ReportType"]; }
      set { this["ReportType"] = value; }
    }
  }

我有一个 ServiceCollection 定义如下:

public class ServiceCollection : ConfigurationElementCollection
  {
    public ServiceCollection()
    {
      Console.WriteLine("ServiceCollection Constructor");
    }

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

    public void Add(ServiceConfig serviceConfig)
    {
      BaseAdd(serviceConfig);
    }

    public void Clear()
    {
      BaseClear();
    }

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

    protected override object GetElementKey(ConfigurationElement element)
    {
      return ((ServiceConfig) element).Port;
    }

    public void Remove(ServiceConfig serviceConfig)
    {
      BaseRemove(serviceConfig.Port);
    }

    public void RemoveAt(int index)
    {
      BaseRemoveAt(index);
    }

    public void Remove(string name)
    {
      BaseRemove(name);
    }
  }

我缺少的部分是为处理程序做什么。最初,我尝试实现一个IConfigurationSectionHandler,但发现了两件事:

  1. 没用
  2. 已弃用。

我现在完全不知道该怎么做,所以我可以从配置中读取我的数据。请帮忙!

【问题讨论】:

  • 我无法正常工作。我很想看到 RT.Core.Config.ServicesSection。尽管也使用了已接受答案中的代码,但我还是得到了 Unrecognized element 'AddService'。
  • 我一开始也错过了这个 - 这部分:[ConfigurationCollection(typeof(ServiceCollection), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")] AddItemName 必须匹配因此,如果您将“add”更改为“addService”,它将起作用

标签: c# configuration app-config configuration-files


【解决方案1】:

尝试从ConfigurationSection 继承。 Phil Haack 的 blog post 有一个例子。

根据IConfigurationSectionHandler 的文档确认:

在 .NET Framework 2.0 及更高版本中,您必须改为从 ConfigurationSection 类派生以实现相关的配置节处理程序。

【讨论】:

    【解决方案2】:

    前面的答案是正确的,但我也会给你所有的代码。

    您的 app.config 应如下所示:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
       <configSections>
          <section name="ServicesSection" type="RT.Core.Config.ServiceConfigurationSection, RT.Core"/>
       </configSections>
       <ServicesSection>
          <Services>
             <add Port="6996" ReportType="File" />
             <add Port="7001" ReportType="Other" />
          </Services>
       </ServicesSection>
    </configuration>
    

    您的 ServiceConfigServiceCollection 类保持不变。

    你需要一个新的类:

    public class ServiceConfigurationSection : ConfigurationSection
    {
       [ConfigurationProperty("Services", IsDefaultCollection = false)]
       [ConfigurationCollection(typeof(ServiceCollection),
           AddItemName = "add",
           ClearItemsName = "clear",
           RemoveItemName = "remove")]
       public ServiceCollection Services
       {
          get
          {
             return (ServiceCollection)base["Services"];
          }
       }
    }
    

    这应该可以解决问题。要使用它,您可以使用:

    ServiceConfigurationSection serviceConfigSection =
       ConfigurationManager.GetSection("ServicesSection") as ServiceConfigurationSection;
    
    ServiceConfig serviceConfig = serviceConfigSection.Services[0];
    

    【讨论】:

    • ConfigurationCollection 属性上的[Add|Remove|Clear]ItemName 属性在这种情况下并不是必需的,因为“add”/“clear”/“remove”已经是 XML 元素的默认名称。
    • 我怎样才能让它工作,这样标签就不会被添加?它似乎只有在添加它们时才有效。如果它是 ,它将不起作用
    • @JonathanWolfson:只需将 AddItemName = "add" 更改为 AddItemName = "Service"
    • 这仍然是 .NET 4.5 的方法吗?
    • @crush:是的,.NET 这个尘土飞扬的角落没有太大变化。
    【解决方案3】:

    这是配置收集的通用代码:

    public class GenericConfigurationElementCollection<T> :   ConfigurationElementCollection, IEnumerable<T> where T : ConfigurationElement, new()
    {
        List<T> _elements = new List<T>();
    
        protected override ConfigurationElement CreateNewElement()
        {
            T newElement = new T();
            _elements.Add(newElement);
            return newElement;
        }
    
        protected override object GetElementKey(ConfigurationElement element)
        {
            return _elements.Find(e => e.Equals(element));
        }
    
        public new IEnumerator<T> GetEnumerator()
        {
            return _elements.GetEnumerator();
        }
    }
    

    拥有GenericConfigurationElementCollection之后, 您可以在配置部分简单地使用它(这是我的 Dispatcher 中的一个示例):

    public class  DispatcherConfigurationSection: ConfigurationSection
    {
        [ConfigurationProperty("maxRetry", IsRequired = false, DefaultValue = 5)]
        public int MaxRetry
        {
            get
            {
                return (int)this["maxRetry"];
            }
            set
            {
                this["maxRetry"] = value;
            }
        }
    
        [ConfigurationProperty("eventsDispatches", IsRequired = true)]
        [ConfigurationCollection(typeof(EventsDispatchConfigurationElement), AddItemName = "add", ClearItemsName = "clear", RemoveItemName = "remove")]
        public GenericConfigurationElementCollection<EventsDispatchConfigurationElement> EventsDispatches
        {
            get { return (GenericConfigurationElementCollection<EventsDispatchConfigurationElement>)this["eventsDispatches"]; }
        }
    }
    

    Config 元素在这里配置:

    public class EventsDispatchConfigurationElement : ConfigurationElement
    {
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get
            {
                return (string) this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }
    }
    

    配置文件如下所示:

    <?xml version="1.0" encoding="utf-8" ?>
      <dispatcherConfigurationSection>
        <eventsDispatches>
          <add name="Log" ></add>
          <add name="Notification" ></add>
          <add name="tester" ></add>
        </eventsDispatches>
      </dispatcherConfigurationSection>
    

    希望对你有所帮助!

    【讨论】:

    • 酷!也在想同样的事情,发现我并不孤单。希望 MS 为所有 FCL 配置实现这一点
    • 关于如何使用项目的 BasicMap 做到这一点的任何建议?如果可以避免,我不想实现 Add。
    【解决方案4】:

    如果您正在寻找如下的自定义配置部分

    <CustomApplicationConfig>
            <Credentials Username="itsme" Password="mypassword"/>
            <PrimaryAgent Address="10.5.64.26" Port="3560"/>
            <SecondaryAgent Address="10.5.64.7" Port="3570"/>
            <Site Id="123" />
            <Lanes>
              <Lane Id="1" PointId="north" Direction="Entry"/>
              <Lane Id="2" PointId="south" Direction="Exit"/>
            </Lanes> 
    </CustomApplicationConfig>
    

    那么您可以使用我的配置部分的实现,以便开始将System.Configuration 程序集引用添加到您的项目

    查看我使用的每个嵌套元素,第一个是具有两个属性的凭据,所以让我们先添加它

    凭据元素

    public class CredentialsConfigElement : System.Configuration.ConfigurationElement
        {
            [ConfigurationProperty("Username")]
            public string Username
            {
                get 
                {
                    return base["Username"] as string;
                }
            }
    
            [ConfigurationProperty("Password")]
            public string Password
            {
                get
                {
                    return base["Password"] as string;
                }
            }
        }
    

    PrimaryAgent 和 SecondaryAgent

    两者具有相同的属性,并且看起来像是一组服务器的地址,用于主服务器和故障转移,因此您只需为这两个服务器创建一个元素类,如下所示

    public class ServerInfoConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("Address")]
            public string Address
            {
                get
                {
                    return base["Address"] as string;
                }
            }
    
            [ConfigurationProperty("Port")]
            public int? Port
            {
                get
                {
                    return base["Port"] as int?;
                }
            }
        }
    

    我将在本文后面解释如何在一个类中使用两个不同的元素,让我们跳过 SiteId,因为它没有区别。您只需创建一个与上述相同的类,仅具有一个属性。让我们看看如何实现车道集合

    它分为两部分首先你必须创建一个元素实现类然后你必须创建集合元素类

    LaneConfigElement

    public class LaneConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("Id")]
            public string Id
            {
                get
                {
                    return base["Id"] as string;
                }
            }
    
            [ConfigurationProperty("PointId")]
            public string PointId
            {
                get
                {
                    return base["PointId"] as string;
                }
            }
    
            [ConfigurationProperty("Direction")]
            public Direction? Direction
            {
                get
                {
                    return base["Direction"] as Direction?;
                }
            }
        }
    
        public enum Direction
        { 
            Entry,
            Exit
        }
    

    您会注意到LanElement 的一个属性是一个枚举,如果您尝试在配置中使用未在枚举应用程序中定义的任何其他值,则会在启动时抛出一个System.Configuration.ConfigurationErrorsException。好的,让我们继续进行集合定义

    [ConfigurationCollection(typeof(LaneConfigElement), AddItemName = "Lane", CollectionType = ConfigurationElementCollectionType.BasicMap)]
        public class LaneConfigCollection : ConfigurationElementCollection
        {
            public LaneConfigElement this[int index]
            {
                get { return (LaneConfigElement)BaseGet(index); }
                set
                {
                    if (BaseGet(index) != null)
                    {
                        BaseRemoveAt(index);
                    }
                    BaseAdd(index, value);
                }
            }
    
            public void Add(LaneConfigElement serviceConfig)
            {
                BaseAdd(serviceConfig);
            }
    
            public void Clear()
            {
                BaseClear();
            }
    
            protected override ConfigurationElement CreateNewElement()
            {
                return new LaneConfigElement();
            }
    
            protected override object GetElementKey(ConfigurationElement element)
            {
                return ((LaneConfigElement)element).Id;
            }
    
            public void Remove(LaneConfigElement serviceConfig)
            {
                BaseRemove(serviceConfig.Id);
            }
    
            public void RemoveAt(int index)
            {
                BaseRemoveAt(index);
            }
    
            public void Remove(String name)
            {
                BaseRemove(name);
            }
    
        }
    

    你可以注意到我已经设置了AddItemName = "Lane",你可以为你的收藏条目选择任何你喜欢的,我更喜欢使用“添加”默认的,但我只是为了这篇文章而改变了它。

    现在我们所有的嵌套元素都已经实现了,现在我们应该将所有嵌套元素聚合到一个必须实现System.Configuration.ConfigurationSection的类中

    CustomApplicationConfigSection

    public class CustomApplicationConfigSection : System.Configuration.ConfigurationSection
        {
            private static readonly ILog log = LogManager.GetLogger(typeof(CustomApplicationConfigSection));
            public const string SECTION_NAME = "CustomApplicationConfig";
    
            [ConfigurationProperty("Credentials")]
            public CredentialsConfigElement Credentials
            {
                get
                {
                    return base["Credentials"] as CredentialsConfigElement;
                }
            }
    
            [ConfigurationProperty("PrimaryAgent")]
            public ServerInfoConfigElement PrimaryAgent
            {
                get
                {
                    return base["PrimaryAgent"] as ServerInfoConfigElement;
                }
            }
    
            [ConfigurationProperty("SecondaryAgent")]
            public ServerInfoConfigElement SecondaryAgent
            {
                get
                {
                    return base["SecondaryAgent"] as ServerInfoConfigElement;
                }
            }
    
            [ConfigurationProperty("Site")]
            public SiteConfigElement Site
            {
                get
                {
                    return base["Site"] as SiteConfigElement;
                }
            }
    
            [ConfigurationProperty("Lanes")]
            public LaneConfigCollection Lanes
            {
                get { return base["Lanes"] as LaneConfigCollection; }
            }
        }
    

    现在您可以看到我们有两个名为 PrimaryAgentSecondaryAgent 的属性都具有相同的类型,现在您可以轻松理解为什么我们对这两个元素只有一个实现类。

    在您可以在 app.config(或 web.config)中使用这个新发明的配置部分之前,您只需要告诉您的应用程序您已经发明了自己的配置部分并给予它一些尊重,这样做您必须在 app.config 中添加以下行(可能在根标记开始之后)。

    <configSections>
        <section name="CustomApplicationConfig" type="MyNameSpace.CustomApplicationConfigSection, MyAssemblyName" />
      </configSections>
    

    注意: MyAssemblyName 应该没有 .dll,例如如果您的程序集文件名为 myDll.dll,则使用 myDll 而不是 myDll.dll

    要检索此配置,请在应用程序的任何位置使用以下代码行

    CustomApplicationConfigSection config = System.Configuration.ConfigurationManager.GetSection(CustomApplicationConfigSection.SECTION_NAME) as CustomApplicationConfigSection;
    

    我希望上面的帖子可以帮助您开始使用一些复杂的自定义配置部分。

    快乐编码:)

    ****编辑**** 要在 LaneConfigCollection 上启用 LINQ,您必须实现 IEnumerable&lt;LaneConfigElement&gt;

    并添加GetEnumerator的以下实现

    public new IEnumerator<LaneConfigElement> GetEnumerator()
            {
                int count = base.Count;
                for (int i = 0; i < count; i++)
                {
                    yield return base.BaseGet(i) as LaneConfigElement;
                }
            }
    

    对于仍然对 yield 的真正工作原理感到困惑的人,请阅读 this nice article

    从上面的文章中提取的两个关键点是

    它并没有真正结束方法的执行。 yield return 暂停 方法执行和下一次调用它(下一次 枚举值),方法会从最后一个继续执行 产生返回调用。我觉得这听起来有点令人困惑……(Shay Friedman)

    Yield 不是 .Net 运行时的功能。它只是一种 C# 语言 由 C# 编译器编译成简单的 IL 代码的功能。 (Lars Corneliussen)

    【讨论】:

    • 感谢您提供完整的示例,这真的很有帮助!
    【解决方案5】:

    对于那些不想手动编写所有配置样板的人来说,这是一个更简单的选择...

    1) 从 NuGet 安装 Nerdle.AutoConfig

    2) 定义您的 ServiceConfig 类型(具体类或接口都可以)

    public interface IServiceConfiguration
    {
        int Port { get; }
        ReportType ReportType { get; }
    }
    

    3) 你需要一个类型来保存集合,例如

    public interface IServiceCollectionConfiguration
    {
        IEnumerable<IServiceConfiguration> Services { get; } 
    }
    

    4) 像这样添加配置部分(注意驼峰命名)

    <configSections>
      <section name="serviceCollection" type="Nerdle.AutoConfig.Section, Nerdle.AutoConfig"/>
    </configSections>
    
    <serviceCollection>
      <services>
        <service port="6996" reportType="File" />
        <service port="7001" reportType="Other" />
      </services>
    </serviceCollection>
    

    5) 使用 AutoConfig 映射

    var services = AutoConfig.Map<IServiceCollectionConfiguration>();
    

    【讨论】:

    • 感谢上帝的回答
    • 对于那些只想完成它而不一定要从头开始创建所有东西的人来说,这是真正的答案:)
    • 以“安装 {something}”开头的答案不是很好的答案
    • @Gh61 stackoverflow.com/help/how-to-answer 中没有任何内容支持这种观点。在看到这个答案并尝试了 Nerdle.Autoconfig 之后,我个人再也不会编写 ConfigurationSection 代码了。
    猜你喜欢
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多