如何使用ConfigurationElementCollection实现ConfigurationSection


166

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

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. 不推荐使用。

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


我无法正常工作。我希望看到RT.Core.Config.ServicesSection。尽管也使用了接受的答案中的代码,但我只是得到了无法识别的元素'AddService'。
sirdank

我一开始也很想念这个-这部分:[ConfigurationCollection(typeof(ServiceCollection),AddItemName =“ add”,ClearItemsName =“ clear”,RemoveItemName =“ remove”)] AddItemName必须匹配,因此如果将“ add”更改为“ addService”它将起作用
HeatherD

Answers:


188

先前的答案是正确的,但我也会提供所有代码。

您的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];

10
[Add|Remove|Clear]ItemName对性能ConfigurationCollection属性不是在这种情况下确实有必要,因为“添加” /“清除” /“删除”已经是XML元素的默认名称。
Wim Coenen '02

2
我如何使其工作以免添加标签?如果添加它们,它似乎只起作用。这是行不通的,如果它是<服务端口= “6996” REPORTTYPE = “文件”/>或<服务端口= “7001” REPORTTYPE = “其他”/>
JonathanWolfson

7
@JonathanWolfson:只需将AddItemName =“ add”更改为AddItemName =“ Service”
Mubashar 2013年

这仍然是.NET 4.5的方法吗?
暗恋

6
@crush:是的,.NET的这个尘土飞扬的角落没有太多变化。
罗素·麦克卢尔

84

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

<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,因为两者之间没有区别。您只需要使用一个属性创建与上述相同的一个类。让我们看看如何实现Lanes集合

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

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是Enumeration,如果尝试在配置中使用在Enumeration应用程序中未定义的任何其他值,则会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; }
        }
    }

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

在您可以在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;

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

快乐编码:)

****编辑****要启用LINQ,LaneConfigCollection您必须实现IEnumerable<LaneConfigElement>

并添加以下实现 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 return会暂停方法的执行,并且在您下次调用它时(对于下一个枚举值),该方法将从上一次yield return调用继续执行。我觉得这有点令人困惑…… (谢伊·弗里德曼)

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


3
感谢您提供完整的示例,这确实有很大帮助!
约翰·莱德格伦

46

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

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,您可以在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"]; }
    }
}

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配置实现该功能
abatishchev 2010年

关于如何使用Items的BasicMap进行任何建议?如果我可以避免的话,我不想实施添加。
SpaceCowboy74 2013年

28

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

1)从NuGet 安装Nerdle.AutoConfig

2)定义您的ServiceConfig类型(无论是具体类还是接口,都可以)

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

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

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

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

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

5
感谢上帝的回答
Svend

对于只想完成它而不必从头开始创建所有内容的人,这才是真正的答案:)
CodeThief

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.