.NET4 中带有集合的简单自定义配置部分

发布于 2024-11-29 12:31:11 字数 3928 浏览 0 评论 0原文

我正在尝试为 .NET4 应用程序编写一个非常简单的自定义配置部分。我的目标是这样的:

<configuration>
  <configSections>
    <section name="myServices" type="My.ConfigSection, My.Assembly" />
  </configSections>
  <myServices>
    <add name="First" />
    <add name="Second" />
  </myServices>
</configuration>

但是,当我调用 ConfigurationManager.GetSection("myServices") 时,我不断收到 ConfigurationErrorsException: 'Unrecognized element 'add''。我已经盯着它看了有一段时间了,但还没有弄清楚我做错了什么。下面是我的代码。它包含三个类:ConfigSectionMyServiceSettingsCollectionMyServiceSettings

首先是代表整个配置部分的类。它有一个 MyServiceSettingsCollection 类型的无名默认集合。 IsDefaultCollection 属性应该允许我直接“添加”到我的来自根元素的集合。

public sealed class ConfigSection : ConfigurationSection
{
  private static readonly ConfigurationProperty _propMyServices;

  private static readonly ConfigurationPropertyCollection _properties;

  public static ConfigSection Instance { get { return _instance; } }

  static ConfigSection()
  {
    _propMyServices = new ConfigurationProperty(
          null, typeof(MyServiceSettingsCollection), null,
          ConfigurationPropertyOptions.IsDefaultCollection);
    _properties = new ConfigurationPropertyCollection { _propMyServices };
  }

  [ConfigurationProperty("", IsDefaultCollection = true)]
  public MyServiceSettingsCollection MyServices
  {
    get { return (MyServiceSettingsCollection) base[_propMyServices]; }
    set { base[_propMyServices] = value; }
  }

  protected override ConfigurationPropertyCollection Properties
  { get { return _properties; } }
}

接下来是集合类本身。它的类型为 AddRemoveClearMap

[ConfigurationCollection(typeof(MyServiceSettings),
    CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap)]
public sealed class MyServiceSettingsCollection : ConfigurationElementCollection
{
  public MyServiceSettings this[int index]
  {
    get { return (MyServiceSettings) BaseGet(index); }
    set
    {
      if (BaseGet(index) != null) { BaseRemoveAt(index); }
      BaseAdd(index, value);
    }
  }

  public new MyServiceSettings this[string key]
  {
    get { return (MyServiceSettings) BaseGet(key); }
  }

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

  protected override object GetElementKey(ConfigurationElement element)
  {
    return ((MyServiceSettings) element).Key;
  }
}

最后是集合中元素的类。目前,此类有一个属性,但稍后会有更多属性(这会阻止我使用 NameValueSectionHandler)。

public class MyServiceSettings : ConfigurationElement
{
  private static readonly ConfigurationProperty _propName;

  private static readonly ConfigurationPropertyCollection properties;

  static MyServiceSettings()
  {
    _propName = new ConfigurationProperty("name", typeof(string), null, null,
                                          new StringValidator(1),
                                          ConfigurationPropertyOptions.IsRequired |
                                          ConfigurationPropertyOptions.IsKey);
    properties = new ConfigurationPropertyCollection { _propName };
  }

  [ConfigurationProperty("name", DefaultValue = "",
        Options = ConfigurationPropertyOptions.IsRequired |
                  ConfigurationPropertyOptions.IsKey)]
  public string Name
  {
      get { return (string) base[_propKey]; }
      set { base[_propKey] = value; }
  }

  protected override ConfigurationPropertyCollection Properties
  { get { return properties; } }
}

I'm trying to write a very simple custom configuration section for a .NET4 application. My goal is this:

<configuration>
  <configSections>
    <section name="myServices" type="My.ConfigSection, My.Assembly" />
  </configSections>
  <myServices>
    <add name="First" />
    <add name="Second" />
  </myServices>
</configuration>

However, I keep getting a ConfigurationErrorsException: 'Unrecognized element 'add'' when I call ConfigurationManager.GetSection("myServices"). I've been staring at it for a while now but haven't figured out yet what I'm doing wrong. Below is my code. It's three classes: ConfigSection, MyServiceSettingsCollection and MyServiceSettings.

First the class that represents the entire config section. It has a nameless default collection of type MyServiceSettingsCollection. The IsDefaultCollection property should allow me to 'add' directly to my collection from the root element.

public sealed class ConfigSection : ConfigurationSection
{
  private static readonly ConfigurationProperty _propMyServices;

  private static readonly ConfigurationPropertyCollection _properties;

  public static ConfigSection Instance { get { return _instance; } }

  static ConfigSection()
  {
    _propMyServices = new ConfigurationProperty(
          null, typeof(MyServiceSettingsCollection), null,
          ConfigurationPropertyOptions.IsDefaultCollection);
    _properties = new ConfigurationPropertyCollection { _propMyServices };
  }

  [ConfigurationProperty("", IsDefaultCollection = true)]
  public MyServiceSettingsCollection MyServices
  {
    get { return (MyServiceSettingsCollection) base[_propMyServices]; }
    set { base[_propMyServices] = value; }
  }

  protected override ConfigurationPropertyCollection Properties
  { get { return _properties; } }
}

Next, the collection class itself. It is of type AddRemoveClearMap.

[ConfigurationCollection(typeof(MyServiceSettings),
    CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap)]
public sealed class MyServiceSettingsCollection : ConfigurationElementCollection
{
  public MyServiceSettings this[int index]
  {
    get { return (MyServiceSettings) BaseGet(index); }
    set
    {
      if (BaseGet(index) != null) { BaseRemoveAt(index); }
      BaseAdd(index, value);
    }
  }

  public new MyServiceSettings this[string key]
  {
    get { return (MyServiceSettings) BaseGet(key); }
  }

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

  protected override object GetElementKey(ConfigurationElement element)
  {
    return ((MyServiceSettings) element).Key;
  }
}

And finally a class for the elements in the collection. For now, this class has one property but there will be more later (which prevents me from using NameValueSectionHandler).

public class MyServiceSettings : ConfigurationElement
{
  private static readonly ConfigurationProperty _propName;

  private static readonly ConfigurationPropertyCollection properties;

  static MyServiceSettings()
  {
    _propName = new ConfigurationProperty("name", typeof(string), null, null,
                                          new StringValidator(1),
                                          ConfigurationPropertyOptions.IsRequired |
                                          ConfigurationPropertyOptions.IsKey);
    properties = new ConfigurationPropertyCollection { _propName };
  }

  [ConfigurationProperty("name", DefaultValue = "",
        Options = ConfigurationPropertyOptions.IsRequired |
                  ConfigurationPropertyOptions.IsKey)]
  public string Name
  {
      get { return (string) base[_propKey]; }
      set { base[_propKey] = value; }
  }

  protected override ConfigurationPropertyCollection Properties
  { get { return properties; } }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

醉态萌生 2024-12-06 12:31:11

好吧,我发现了看似随机的修复。而不是这样:

[ConfigurationProperty("", IsDefaultCollection = true)]
public ProvisiorServiceSettingsCollection ProvisiorServices
{ ... }

您应该使用:

[ConfigurationProperty("", Options = ConfigurationPropertyOptions.IsDefaultCollection)]
public ProvisiorServiceSettingsCollection ProvisiorServices
{ ... }

不知道两者之间有什么区别。对我来说,它们看起来惊人地相似……或者至少,没有任何地方表明为什么其中一个比另一个更受欢迎。

Ok, I found the seemingly random fix. Instead of this:

[ConfigurationProperty("", IsDefaultCollection = true)]
public ProvisiorServiceSettingsCollection ProvisiorServices
{ ... }

you should use:

[ConfigurationProperty("", Options = ConfigurationPropertyOptions.IsDefaultCollection)]
public ProvisiorServiceSettingsCollection ProvisiorServices
{ ... }

No idea what the difference is between the two. To me, they look strikingly similar... or at least, there is no suggestion anywhere why one is preferred over the other.

沉默的熊 2024-12-06 12:31:11

由于我在这方面花了很多时间,所以我想添加一个我刚刚在这次提交中实现的真实示例: https://github.com/rhythmagency/formulate/commit/4d2a95e1a82eb6b3500ab0869b8f8b15bd3deaa9

这是我的 web.config 目标(我能够实现):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="formulateConfiguration">
      <section name="templates" type="formulate.app.Configuration.TemplatesConfigSection, formulate.app" requirePermission="false"/>
    </sectionGroup>
  </configSections>
  <formulateConfiguration>
    <templates>
      <template name="Responsive" path="~/Views/Formulate/Responsive.Bootstrap.Angular.cshtml" />
    </templates>
  </formulateConfiguration>
</configuration>

这是最高级别的课程-level“模板”配置部分:

namespace formulate.app.Configuration
{

    // Namespaces.
    using System.Configuration;


    /// <summary>
    /// A configuration section for Formulate templates.
    /// </summary>
    public class TemplatesConfigSection : ConfigurationSection
    {

        #region Properties

        /// <summary>
        /// The templates in this configuration section.
        /// </summary>
        [ConfigurationProperty("", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(TemplateCollection), AddItemName = "template")]
        public TemplateCollection Templates
        {
            get
            {
                return base[""] as TemplateCollection;
            }
        }

        #endregion

    }

}

这是下一个级别,集合类:

namespace formulate.app.Configuration
{

    // Namespaces.
    using System.Configuration;


    /// <summary>
    /// A collection of templates from the configuration.
    /// </summary>
    [ConfigurationCollection(typeof(TemplateElement))]
    public class TemplateCollection : ConfigurationElementCollection
    {

        #region Methods

        /// <summary>
        /// Creates a new template element.
        /// </summary>
        /// <returns>The template element.</returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return new TemplateElement();
        }


        /// <summary>
        /// Gets the key for an element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns>The key.</returns>
        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as TemplateElement).Name;
        }

        #endregion

    }

}

这是最深级别的类(各个模板):

namespace formulate.app.Configuration
{

    //  Namespaces.
    using System.Configuration;


    /// <summary>
    /// A "template" configuration element.
    /// </summary>
    public class TemplateElement : ConfigurationElement
    {

        #region Constants

        private const string DefaultPath = "~/*Replace Me*.cshtml";

        #endregion


        #region Properties

        /// <summary>
        /// The name of the template.
        /// </summary>
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get
            {
                return base["name"] as string;
            }
            set
            {
                this["name"] = value;
            }
        }


        /// <summary>
        /// The path to this template.
        /// </summary>
        /// <remarks>
        /// Should start with "~" and end with ".cshtml".
        /// </remarks>
        [ConfigurationProperty("path", IsRequired = true, DefaultValue = DefaultPath)]
        [RegexStringValidator(@"^~.*\.[cC][sS][hH][tT][mM][lL]$")]
        public string Path
        {
            get
            {
                var result = base["path"] as string;
                return result == DefaultPath ? null : result;
            }
            set
            {
                this["path"] = value;
            }
        }

        #endregion

    }

}

对我来说重要的是在 ConfigurationPropertyAttribute 中包含空字符串并将 IsDefaultCollection 设置为 true。顺便说一句,我将我的配置放在一个外部文件中,如下所示:

<?xml version="1.0" encoding="utf-8" ?>
<templates>
    <template name="Responsive" path="~/Views/Formulate/Responsive.Bootstrap.Angular.cshtml" />
</templates>

在这种情况下,我的 web.config 如下所示:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="formulateConfiguration">
      <section name="templates" type="formulate.app.Configuration.TemplatesConfigSection, formulate.app" requirePermission="false"/>
    </sectionGroup>
  </configSections>
  <formulateConfiguration>
    <templates configSource="config\Formulate\templates.config"/>
  </formulateConfiguration>
</configuration>

我想我会提到,以防其他人尝试将其添加到外部文件中(外部文件中的根级项与 web.config 中的外部化元素相同,这有点不直观)。

Since I spent a good amount of time on this, thought I'd add a real world example I just implemented in this commit: https://github.com/rhythmagency/formulate/commit/4d2a95e1a82eb6b3500ab0869b8f8b15bd3deaa9

Here was my goal for my web.config (which I was able to achieve):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="formulateConfiguration">
      <section name="templates" type="formulate.app.Configuration.TemplatesConfigSection, formulate.app" requirePermission="false"/>
    </sectionGroup>
  </configSections>
  <formulateConfiguration>
    <templates>
      <template name="Responsive" path="~/Views/Formulate/Responsive.Bootstrap.Angular.cshtml" />
    </templates>
  </formulateConfiguration>
</configuration>

This is the class for the highest-level "templates" configuration section:

namespace formulate.app.Configuration
{

    // Namespaces.
    using System.Configuration;


    /// <summary>
    /// A configuration section for Formulate templates.
    /// </summary>
    public class TemplatesConfigSection : ConfigurationSection
    {

        #region Properties

        /// <summary>
        /// The templates in this configuration section.
        /// </summary>
        [ConfigurationProperty("", IsDefaultCollection = true)]
        [ConfigurationCollection(typeof(TemplateCollection), AddItemName = "template")]
        public TemplateCollection Templates
        {
            get
            {
                return base[""] as TemplateCollection;
            }
        }

        #endregion

    }

}

Here's the next level down, the collection class:

namespace formulate.app.Configuration
{

    // Namespaces.
    using System.Configuration;


    /// <summary>
    /// A collection of templates from the configuration.
    /// </summary>
    [ConfigurationCollection(typeof(TemplateElement))]
    public class TemplateCollection : ConfigurationElementCollection
    {

        #region Methods

        /// <summary>
        /// Creates a new template element.
        /// </summary>
        /// <returns>The template element.</returns>
        protected override ConfigurationElement CreateNewElement()
        {
            return new TemplateElement();
        }


        /// <summary>
        /// Gets the key for an element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns>The key.</returns>
        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as TemplateElement).Name;
        }

        #endregion

    }

}

And here's the deepest level class (the individual templates):

namespace formulate.app.Configuration
{

    //  Namespaces.
    using System.Configuration;


    /// <summary>
    /// A "template" configuration element.
    /// </summary>
    public class TemplateElement : ConfigurationElement
    {

        #region Constants

        private const string DefaultPath = "~/*Replace Me*.cshtml";

        #endregion


        #region Properties

        /// <summary>
        /// The name of the template.
        /// </summary>
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get
            {
                return base["name"] as string;
            }
            set
            {
                this["name"] = value;
            }
        }


        /// <summary>
        /// The path to this template.
        /// </summary>
        /// <remarks>
        /// Should start with "~" and end with ".cshtml".
        /// </remarks>
        [ConfigurationProperty("path", IsRequired = true, DefaultValue = DefaultPath)]
        [RegexStringValidator(@"^~.*\.[cC][sS][hH][tT][mM][lL]$")]
        public string Path
        {
            get
            {
                var result = base["path"] as string;
                return result == DefaultPath ? null : result;
            }
            set
            {
                this["path"] = value;
            }
        }

        #endregion

    }

}

The important bit for me was to have the empty string in the ConfigurationPropertyAttribute and setting IsDefaultCollection to true. By the way, I put my config in an external file that looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<templates>
    <template name="Responsive" path="~/Views/Formulate/Responsive.Bootstrap.Angular.cshtml" />
</templates>

And in that case, my web.config looks like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <sectionGroup name="formulateConfiguration">
      <section name="templates" type="formulate.app.Configuration.TemplatesConfigSection, formulate.app" requirePermission="false"/>
    </sectionGroup>
  </configSections>
  <formulateConfiguration>
    <templates configSource="config\Formulate\templates.config"/>
  </formulateConfiguration>
</configuration>

Figured I'd mention that in case somebody else is trying to add it to an external file (it's somewhat non-intuitive that the root-level item in the external file is the same as the externalized element from the web.config).

时光礼记 2024-12-06 12:31:11

您似乎缺少与此类似的内容,

[ConfigurationProperty("urls", IsDefaultCollection = false)]
    [ConfigurationCollection(typeof(UrlsCollection),
        AddItemName = "add",
        ClearItemsName = "clear",
        RemoveItemName = "remove")]

有关详细信息,请参阅 http: //msdn.microsoft.com/en-us/library/system.configuration.configurationcollectionattribute.aspx

it seems you are missing something similar to this

[ConfigurationProperty("urls", IsDefaultCollection = false)]
    [ConfigurationCollection(typeof(UrlsCollection),
        AddItemName = "add",
        ClearItemsName = "clear",
        RemoveItemName = "remove")]

for more information see http://msdn.microsoft.com/en-us/library/system.configuration.configurationcollectionattribute.aspx

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文