วิธีการปรับใช้ ConfigurationSection ด้วย ConfigurationElementCollection


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. มันเลิกใช้แล้ว

ตอนนี้ฉันทำสิ่งที่ต้องทำเสร็จแล้วดังนั้นฉันสามารถอ่านข้อมูลของฉันได้จากการตั้งค่า ความช่วยเหลือใด ๆ โปรด!


ฉันทำงานนี้ไม่ได้ ฉันชอบดู RT.Core.Config.ServicesSection ฉันเพิ่งได้รับองค์ประกอบที่ไม่รู้จัก 'AddService' แม้จะใช้รหัสจากคำตอบที่ยอมรับเช่นกัน
sirdank

ฉันพลาดสิ่งนี้ในตอนแรกเช่นกัน - ส่วนนี้: [ConfigurationCollection (typeof (ServiceCollection), AddItemName = "เพิ่ม", ClearItemsName = "ล้าง", RemoveItemName = "ลบ")] AddItemName มีการจับคู่ดังนั้นหากคุณเปลี่ยน "เพิ่ม" เป็น "addService" จะใช้งานได้
HeatherD

คำตอบ:


188

คำตอบก่อนหน้านี้ถูกต้อง แต่ฉันจะให้รหัสทั้งหมดแก่คุณเช่นกัน

แอปของคุณควรมีลักษณะดังนี้:

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

คุณServiceConfigและServiceCollectionชั้นเรียนยังคงไม่เปลี่ยนแปลง

คุณต้องการคลาสใหม่:

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

2
ฉันจะทำให้มันทำงานเพื่อไม่ให้แท็กเพิ่มได้อย่างไร ดูเหมือนว่าจะทำงานหากมีการเพิ่ม มันจะไม่ทำงานหากเป็น <Service Port = "6996" ReportType = "File" /> หรือ <Service Port = "7001" ReportType = "อื่น ๆ " />
JonathanWolfson

7
@JonathanWolfson: เพียงแค่เปลี่ยน AddItemName = "add" เป็น AddItemName = "Service"
Mubashar

นี่เป็นวิธีการสำหรับ. NET 4.5 หรือไม่
สนใจ

6
@ crush: ใช่ไม่เปลี่ยนแปลงมากนักในมุมที่เต็มไปด้วยฝุ่นของ. NET
Russell McClure

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คือการแจงนับและถ้าคุณพยายามที่จะใช้ค่าอื่น ๆ ในการกำหนดค่าซึ่งไม่ได้กำหนดไว้ในการประยุกต์ใช้การแจงนับจะโยน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;

ฉันหวังว่าโพสต์ด้านบนจะช่วยให้คุณเริ่มต้นใช้งานส่วนกำหนดค่าแบบกำหนดเองที่ซับซ้อนเล็กน้อย

Happy Coding :)

**** แก้ไข **** เพื่อเปิดใช้งาน 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;
            }
        }

สำหรับผู้ที่ยังคงสับสนเกี่ยวกับวิธีการทำงานได้จริงอ่านบทความดีนี้

ประเด็นสำคัญสองข้อที่นำมาจากบทความข้างต้นคือ

มันไม่ได้จบการทำงานของวิธีการจริงๆ คืนผลตอบแทนหยุดการดำเนินการวิธีการและในครั้งต่อไปที่คุณเรียกมัน (สำหรับค่าการแจงนับต่อไป) วิธีการจะดำเนินการต่อไปจากการเรียกผลตอบแทนผลตอบแทนที่ผ่านมา ฟังดูสับสนเล็กน้อยที่ฉันคิดว่า… (Shay Friedman)

ผลตอบแทนไม่ได้เป็นคุณสมบัติของรันไทม์. Net มันเป็นเพียงคุณสมบัติภาษา C # ซึ่งได้รับการรวบรวมเป็นรหัส IL ง่ายโดยคอมไพเลอร์ C # (Lars Corneliussen)


3
ขอบคุณที่ให้ตัวอย่างที่สมบูรณ์สิ่งนี้ช่วยได้มากจริงๆ!
John Leidegren

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คุณสามารถใช้งานได้ง่ายในส่วนการตั้งค่า (นี่คือตัวอย่างจากดิสแพตเชอร์ของฉัน):

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

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) เพิ่มส่วนการกำหนดค่าเช่นนั้น (การตั้งชื่อ note 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
ขอบคุณพระเจ้าสำหรับคำตอบนี้
วุนด์

สำหรับคนที่ต้องการทำมันให้เสร็จและไม่จำเป็นต้องสร้างทุกอย่างตั้งแต่เริ่มต้นนี่เป็นคำตอบที่แท้จริง :)
CodeThief

5

ลองสืบทอดจากConfigurationSection โพสต์บล็อกนี้โดย Phil Haack มีตัวอย่าง

ได้รับการยืนยันตามเอกสารสำหรับIConfigurationSectionHandler :

ใน. NET Framework เวอร์ชัน 2.0 ขึ้นไปคุณจะต้องสืบทอดมาจากคลาส ConfigurationSection เพื่อใช้ตัวจัดการส่วนกำหนดค่าที่เกี่ยวข้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.