简体   繁体   English

如何使用ConfigurationElementCollection实现ConfigurationSection

[英]How to implement a ConfigurationSection with a ConfigurationElementCollection

I am trying to implement a custom configuration section in a project and I keep running up against exceptions that I do not understand. 我正在尝试在项目中实现自定义配置部分,并且我一直在遇到我不理解的异常。 I am hoping someone can fill in the blanks here. 我希望有人能填补这里的空白。

I have App.config that looks like this: 我的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>

I have a ServiceConfig element defined like so: 我有一个像这样定义的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; }
    }
  }

And I have a ServiceCollection defined like so: 我有一个像这样定义的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);
    }
  }

The part I am missing is what to do for the handler. 我缺少的部分是为处理程序做什么。 Originally, I tried to implement an IConfigurationSectionHandler but found two things: 最初,我尝试实现IConfigurationSectionHandler但发现了两件事:

  1. it didn't work 它不起作用
  2. it's deprecated. 它被弃用了。

I'm completely lost now on what to do so I can read my data from config. 我现在完全迷失了该做什么,所以我可以从配置中读取我的数据。 Any help please! 请帮忙!

The previous answer is correct but I'll give you all the code as well. 之前的答案是正确的,但我也会给你所有的代码。

Your app.config should look like this: 您的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>

Your ServiceConfig and ServiceCollection classes remain unchanged. 您的ServiceConfigServiceCollection类保持不变。

You need a new class: 你需要一个新的课程:

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

And that should do the trick. 这应该可以解决问题。 To consume it you can use: 要消费它你可以使用:

ServiceConfigurationSection serviceConfigSection =
   ConfigurationManager.GetSection("ServicesSection") as ServiceConfigurationSection;

ServiceConfig serviceConfig = serviceConfigSection.Services[0];

If you are looking for a custom configuration section like following 如果您正在寻找如下的自定义配置部分

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

then you can use my implementation of configuration section so to get started add System.Configuration assembly reference to your project 然后你可以使用我的配置部分实现,这样就可以开始为你的项目添加System.Configuration程序集引用

Look at the each nested elements I used, First one is Credentials with two attributes so lets add it first 查看我使用的每个嵌套元素,首先是具有两个属性的Credentials,所以我们先添加它

Credentials Element 凭证元素

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 and SecondaryAgent PrimaryAgent和SecondaryAgent

Both has the same attributes and seem like a Address to a set of servers for a primary and a failover, so you just need to create one element class for both of those like following 两者都具有相同的属性,看起来像是主服务器和故障转移的一组服务器的地址,因此您只需要为这两个服务器创建一个元素类,如下所示

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

I'll explain how to use two different element with one class later in this post, let us skip the SiteId as there is no difference in it. 我将在本文稍后解释如何在一个类中使用两个不同的元素,让我们跳过SiteId,因为它没有区别。 You just have to create one class same as above with one property only. 您只需创建一个与上面相同的类,仅使用一个属性。 let us see how to implement Lanes collection 让我们看看如何实现Lanes集合

it is splitted in two parts first you have to create an element implementation class then you have to create collection element class 它分为两部分,首先你必须创建一个元素实现类,然后你必须创建集合元素类

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

you can notice that one attribute of LanElement is an Enumeration and if you try to use any other value in configuration which is not defined in Enumeration application will throw an System.Configuration.ConfigurationErrorsException on startup. 您可以注意到LanElement一个属性是Enumeration,如果您尝试使用Enumeration中未定义的配置中的任何其他值,则会在启动时抛出System.Configuration.ConfigurationErrorsException Ok lets move on to Collection Definition 好的,我们继续前往Collection Definition

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

    }

you can notice that I have set the AddItemName = "Lane" you can choose whatever you like for your collection entry item, i prefer to use "add" the default one but i changed it just for the sake of this post. 你可以注意到我已经设置了AddItemName = "Lane"你可以为你的收藏条目选择你喜欢的任何东西,我更喜欢使用“添加”默认的一个,但我只是为了这篇文章而改变它。

Now all of our nested Elements have been implemented now we should aggregate all of those in a class which has to implement System.Configuration.ConfigurationSection 现在我们已经实现了所有嵌套元素,现在我们应该聚集一个必须实现System.Configuration.ConfigurationSection的类中的所有元素。

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

Now you can see that we have two properties with name PrimaryAgent and SecondaryAgent both have the same type now you can easily understand why we had only one implementation class against these two element. 现在您可以看到我们有两个名为PrimaryAgentSecondaryAgent属性,它们具有相同的类型,现在您可以很容易地理解为什么我们只有一个针对这两个元素的实现类。

Before you can use this newly invented configuration section in your app.config (or web.config) you just need to tell you application that you have invented your own configuration section and give it some respect, to do so you have to add following lines in app.config (may be right after start of root tag). 在app.config(或web.config)中使用这个新发明的配置部分之前,您只需告诉应用程序您已经发明了自己的配置部分并给予了一些尊重,为此您必须添加以下行在app.config中(可能在root标签启动后)。

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

NOTE: MyAssemblyName should be without .dll eg if you assembly file name is myDll.dll then use myDll instead of myDll.dll 注意: MyAssemblyName应该没有.dll,例如,如果您的汇编文件名是myDll.dll,那么使用myDll而不是myDll.dll

to retrieve this configuration use following line of code any where in your application 要检索此配置,请在应用程序的任何位置使用以下代码行

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

I hope above post would help you to get started with a bit complicated kind of custom config sections. 我希望上面的帖子可以帮助您开始使用一些复杂的自定义配置部分。

Happy Coding :) 快乐编码:)

****Edit**** To Enable LINQ on LaneConfigCollection you have to implement IEnumerable<LaneConfigElement> ****编辑****要在LaneConfigCollection上启用LINQ,您必须实现IEnumerable<LaneConfigElement>

And Add following implementation of GetEnumerator 并添加以下GetEnumerator实现

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

for the people who are still confused about how yield really works read this nice article 对于仍然对产量如何真正起作用感到困惑的人们,请阅读这篇精彩的文章

Two key points taken from above article are 从上面的文章中得到的两个关键点是

it doesn't really end the method's execution. 它并没有真正结束方法的执行。 yield return pauses the method execution and the next time you call it (for the next enumeration value), the method will continue to execute from the last yield return call. yield return暂停方法执行,并在下次调用它时(对于下一个枚举值),该方法将从上一次yield return调用继续执行。 It sounds a bit confusing I think… (Shay Friedman) 我觉得这听起来有点令人困惑...... (Shay Friedman)

Yield is not a feature of the .Net runtime. Yield不是.Net运行时的功能。 It is just a C# language feature which gets compiled into simple IL code by the C# compiler. 它只是一个C#语言特性,可以通过C#编译器编译成简单的IL代码。 (Lars Corneliussen) (Lars Corneliussen)

This is generic code for configuration collection : 这是配置集合的通用代码:

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

After you have GenericConfigurationElementCollection , you can simple use it in the config section (this is an example from my Dispatcher): 有了GenericConfigurationElementCollection ,你可以在config部分中简单地使用它(这是我的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"]; }
    }
}

The Config Element is config Here: 配置元素配置在这里:

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

The config file would look like this: 配置文件如下所示:

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

Hope it help ! 希望它有所帮助!

An easier alternative for those who would prefer not to write all that configuration boilerplate manually... 对于那些不想手动编写所有配置样板的人来说,这是一个更容易的选择......

1) Install Nerdle.AutoConfig from NuGet 1)从NuGet安装Nerdle.AutoConfig

2) Define your ServiceConfig type (either a concrete class or just an interface, either will do) 2)定义您的ServiceConfig类型(具体类或只是一个接口,或者都可以)

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

3) You'll need a type to hold the collection, eg 3)你需要一个类型来保存集合,例如

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

4) Add the config section like so (note camelCase naming) 4)像这样添加配置部分(注意camelCase命名)

<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) Map with AutoConfig 5)使用AutoConfig进行映射

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

Try inheriting from ConfigurationSection . 尝试继承ConfigurationSection This blog post by Phil Haack has an example. Phil Haack的这篇博文有一个例子。

Confirmed, per the documentation for IConfigurationSectionHandler : 根据IConfigurationSectionHandler的文档确认:

In .NET Framework version 2.0 and above, you must instead derive from the ConfigurationSection class to implement the related configuration section handler. 在.NET Framework 2.0及更高版本中,您必须从ConfigurationSection类派生以实现相关的配置节处理程序。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用包含自定义“元素”的嵌套“ConfigurationElementCollection”实现自定义“ConfigurationSection” - How to implement a custom "ConfigurationSection" with a nested "ConfigurationElementCollection" containing a custom "Element" 使用 ConfigurationSection / ConfigurationElementCollection 从 web.config 创建项目的嵌套集合 - Use ConfigurationSection / ConfigurationElementCollection to create a nested collection of items from web.config 如何在ConfigurationElementCollection中拥有自定义属性? - how to have custom attribute in ConfigurationElementCollection? 如何遍历configurationSection - How to loop through configurationSection 如何将 ConfigurationSection 转换为 object? - How to convert ConfigurationSection to object? 如何从ConfigurationElementCollection中获取配置属性名称 - How to get the configuration property name out of a ConfigurationElementCollection 如何在ConfigurationElementCollection中按键查找 - how do I find by key in ConfigurationElementCollection 如何解决这个奇怪的ConfigurationSection异常? - How to fix this bizarre ConfigurationSection exceptions? 如何向程序集添加自定义ConfigurationSection? - How do I add custom ConfigurationSection to Assembly? 如何将TypeConverters与ConfigurationSection一起使用? - How can I use TypeConverters with a ConfigurationSection?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM