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ましたが、次の2つが見つかりました。

  1. うまくいかなかった
  2. 非推奨です。

設定からデータを読み取ることができるように、何をすべきかについて完全に迷っています。助けてください!


これは機能しません。RT.Core.Config.ServicesSectionを見たいです。受け入れられた回答からのコードも使用しているにもかかわらず、Unrecognizedエレメント「AddService」を取得するだけです。
sirdank

私も最初にこれを逃しました-この部分:[ConfigurationCollection(typeof(ServiceCollection)、AddItemName = "add"、ClearItemsName = "clear"、RemoveItemName = "remove")] AddItemNameは一致する必要があるため、「add」を「addService」は機能します
HeatherD 2017年

回答:


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」/「clear」/「remove」はすでにXML要素のデフォルト名であるため[Add|Remove|Clear]ItemNameConfigurationCollection属性のプロパティは実際には必要ありません。
Wim Coenen

2
タグが追加されないようにするにはどうすればよいですか?追加された場合にのみ機能するようです。<Service Port = "6996" ReportType = "File" />または<Service Port = "7001" ReportType = "Other" />の場合は機能しません
JonathanWolfson

7
@JonathanWolfson:AddItemName = "add"をAddItemName = "Service"に変更する
Mubashar

これはまだ.NET 4.5のアプローチですか?
2014

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アセンブリ参照をプロジェクトにします。

私が使用した各ネストされた要素を見て、最初の要素は2つの属性を持つ資格情報なので、最初に追加しましょう

資格情報要素

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

どちらも同じ属性を持ち、プライマリサーバーとフェイルオーバーのサーバーセットへのアドレスのように見えるため、次のように両方に対して1つの要素クラスを作成するだけで済みます。

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

この投稿の後半で、1つのクラスで2つの異なる要素を使用する方法について説明します。SiteIdには違いがないため、スキップします。上記と同じ1つのクラスを1つのプロパティのみで作成する必要があります。レーンコレクションを実装する方法を見てみましょう

最初に要素実装クラスを作成する必要があり、次にコレクション要素クラスを作成する必要があります。

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
    }

の1つの属性が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"コレクションエントリアイテムには、好きなように選択できるように。デフォルトの「add」を使用したいのですが、この投稿のために変更しました。

ネストされたすべての要素が実装されたので、実装する必要があるクラスの要素をすべて集約する必要があります 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どちらも同じ型であることがわかります。これで、これらの2つの要素に対して実装クラスが1つしかなかった理由を簡単に理解できます。

app.config(またはweb.config)でこの新しく発明された構成セクションを使用する前に、独自の構成セクションを発明したことをアプリケーションに通知し、それを尊重する必要があります。そのためには、次の行を追加する必要があります。 app.config内(ルートタグの開始直後)

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

注意: MyAssemblyNameには.dllを含めないでください。たとえば、アセンブリファイル名がmyDll.dllの場合、myDll.dllではなくmyDllを使用します。

この構成を取得するには、次のコード行をアプリケーションの任意の場所で使用します

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

収量が実際にどのように機能するかについてまだ混乱している人のために、この素晴らしい記事を読んでください

上記の記事から取られた2つの重要なポイントは

メソッドの実行が実際に終了するわけではありません。yield returnはメソッドの実行を一時停止し、次に(次の列挙値に対して)呼び出したときに、メソッドは最後のyield return呼び出しから実行を継続します。少しわかりにくいかもしれませんが…かもしれ(Shay Friedman)

Yieldは.Netランタイムの機能ではありません。これは、C#コンパイラによって単純なILコードにコンパイルされる単なるC#言語機能です。(ラースコーネリアッセン)


3
完全な例を提供してくれてありがとう、これは本当に役に立ちます!
John Leidegren 2017年

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

構成要素はここに構成です:

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

アイテムのBasicMapでそれを行う方法に関する提案はありますか?回避できる場合は、追加を実装したくありません。
SpaceCowboy74 2013

28

手作業で構成の定型文をすべて記述したくない人のためのより簡単な代替案...

1)Nerdle.AutoConfigをインストールします NuGetからを

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

5
この答えを神に感謝します
スベン、

それをやりたいだけで、必ずしもすべてを最初から作成する必要がない人にとっては、これが本当の答えです:)
CodeThief

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.