Correct Implementation of a Custom Config Section with Nested Collections

Correct implementation of a custom config section with nested collections?

I finally found this guy's example. It was coded and worked right out of the box.

http://manyrootsofallevilrants.blogspot.com/2011/07/nested-custom-configuration-collections.html

I am going to paste the code here......only because I cannot stand it when someone says "Your answer is here", and the link is dead.

Please try his website first, and leave a "thank you" if it works.

using System;
using System.Configuration;

namespace SSHTunnelWF
{
public class TunnelSection : ConfigurationSection
{
[ConfigurationProperty("", IsDefaultCollection = true)]
public HostCollection Tunnels
{
get
{
HostCollection hostCollection = (HostCollection)base[""];
return hostCollection;
}
}
}

public class HostCollection : ConfigurationElementCollection
{
public HostCollection()
{
HostConfigElement details = (HostConfigElement)CreateNewElement();
if (details.SSHServerHostname != "")
{
Add(details);
}
}

public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.BasicMap;
}
}

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

protected override Object GetElementKey(ConfigurationElement element)
{
return ((HostConfigElement)element).SSHServerHostname;
}

public HostConfigElement this[int index]
{
get
{
return (HostConfigElement)BaseGet(index);
}
set
{
if (BaseGet(index) != null)
{
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}

new public HostConfigElement this[string name]
{
get
{
return (HostConfigElement)BaseGet(name);
}
}

public int IndexOf(HostConfigElement details)
{
return BaseIndexOf(details);
}

public void Add(HostConfigElement details)
{
BaseAdd(details);
}

protected override void BaseAdd(ConfigurationElement element)
{
BaseAdd(element, false);
}

public void Remove(HostConfigElement details)
{
if (BaseIndexOf(details) >= 0)
BaseRemove(details.SSHServerHostname);
}

public void RemoveAt(int index)
{
BaseRemoveAt(index);
}

public void Remove(string name)
{
BaseRemove(name);
}

public void Clear()
{
BaseClear();
}

protected override string ElementName
{
get { return "host"; }
}
}

public class HostConfigElement:ConfigurationElement
{
[ConfigurationProperty("SSHServerHostname", IsRequired = true, IsKey = true)]
[StringValidator(InvalidCharacters = " ~!@#$%^&*()[]{}/;’\"|\\")]
public string SSHServerHostname
{
get { return (string)this["SSHServerHostname"]; }
set { this["SSHServerHostname"] = value; }
}

[ConfigurationProperty("username", IsRequired = true)]
[StringValidator(InvalidCharacters = " ~!@#$%^&*()[]{}/;’\"|\\")]
public string Username
{
get { return (string)this["username"]; }
set { this["username"] = value; }
}

[ConfigurationProperty("SSHport", IsRequired = true, DefaultValue = 22)]
[IntegerValidator(MinValue = 1, MaxValue = 65536)]
public int SSHPort
{
get { return (int)this["SSHport"]; }
set { this["SSHport"] = value; }
}

[ConfigurationProperty("password", IsRequired = false)]
public string Password
{
get { return (string)this["password"]; }
set { this["password"] = value; }
}

[ConfigurationProperty("privatekey", IsRequired = false)]
public string Privatekey
{
get { return (string)this["privatekey"]; }
set { this["privatekey"] = value; }
}

[ConfigurationProperty("privatekeypassphrase", IsRequired = false)]
public string Privatekeypassphrase
{
get { return (string)this["privatekeypassphrase"]; }
set { this["privatekeypassphrase"] = value; }
}

[ConfigurationProperty("tunnels", IsDefaultCollection = false)]
public TunnelCollection Tunnels
{
get { return (TunnelCollection)base["tunnels"]; }
}
}

public class TunnelCollection : ConfigurationElementCollection
{
public new TunnelConfigElement this[string name]
{
get
{
if (IndexOf(name) < 0) return null;
return (TunnelConfigElement)BaseGet(name);
}
}

public TunnelConfigElement this[int index]
{
get { return (TunnelConfigElement)BaseGet(index); }
}

public int IndexOf(string name)
{
name = name.ToLower();

for (int idx = 0; idx < base.Count; idx++)
{
if (this[idx].Name.ToLower() == name)
return idx;
}
return -1;
}

public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}

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

protected override object GetElementKey(ConfigurationElement element)
{
return ((TunnelConfigElement)element).Name;
}

protected override string ElementName
{
get { return "tunnel"; }
}
}

public class TunnelConfigElement : ConfigurationElement
{
public TunnelConfigElement()
{
}

public TunnelConfigElement(string name, int localport, int remoteport, string destinationserver)
{
this.DestinationServer = destinationserver;
this.RemotePort = remoteport;
this.LocalPort = localport;
this.Name = name;
}

[ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}

[ConfigurationProperty("localport", IsRequired = true, DefaultValue =1)]
[IntegerValidator(MinValue = 1, MaxValue = 65536)]
public int LocalPort
{
get { return (int)this["localport"]; }
set { this["localport"] = value; }
}

[ConfigurationProperty("remoteport", IsRequired = true, DefaultValue =1)]
[IntegerValidator(MinValue = 1, MaxValue = 65536)]
public int RemotePort
{
get { return (int)this["remoteport"]; }
set { this["remoteport"] = value; }
}

[ConfigurationProperty("destinationserver", IsRequired = true)]
[StringValidator(InvalidCharacters = " ~!@#$%^&*()[]{}/;’\"|\\")]
public string DestinationServer
{
get { return (string)this["destinationserver"]; }
set { this["destinationserver"] = value; }
}
}
}

And the configuration code

 <?xml version="1.0"?>
<configuration>
<configSections>
<section name="TunnelSection" type="SSHTunnelWF.TunnelSection,SSHTunnelWF" />
</configSections>
<TunnelSection>
<host SSHServerHostname="tsg.edssdn.net" username="user" SSHport="22" password="pass" privatekey="" privatekeypassphrase="">
<tunnels>
<tunnel name="tfs" localport="8081" remoteport="8080" destinationserver="tfs2010.dev.com" />
<tunnel name="sql" localport="14331" remoteport="1433" destinationserver="sql2008.dev.com" />
<tunnel name="crm2011app" localport="81" remoteport="80" destinationserver="crm2011betaapp.dev.com" />
</tunnels>
</host>
<host SSHServerHostname="blade16" username="root" SSHport="22" password="pass" privatekey="" privatekeypassphrase="">
<tunnels>
<tunnel name="vnc" localport="5902" remoteport="5902" destinationserver="blade1.dev.com" />
</tunnels>
</host>
</TunnelSection>
</configuration>

And then the "call"

TunnelSection tunnels = ConfigurationManager.GetSection("TunnelSection") as TunnelSection

Use ConfigurationSection / ConfigurationElementCollection to create a nested collection of items from web.config

To fix your immediate error, you just need to add wrapping <email> tag to your <add/> elements. That is because your SystemEmailsElement contains Emails property which is collection with root tag defined as "email" (via [ConfigurationProperty("email")]). Like this:

<systemEmails>
<emails>
<email name="SystemEmail01" defaultAddress="SystemEmail01@email.com" defaultName="John Doe 01" />
<email name="SystemEmail02" source="UserName" username="Username02" defaultAddress="SystemEmail02@email.com" defaultName="Jane Doe 02" />
<email name="SystemEmail03" defaultAddress="SystemEmail03@email.com" defaultName="John Doe 03" />
<email name="SystemEmail04" source="UserName" username="Username04" />
<email name="SystemEmail05" source="RoleName" rolename="administrators" defaultEmailName="SystemEmail02" />
<email name="SystemEmailGroup01" source="Group">
<email>
<add name="SystemEmail06" defaultAddress="SystemEmail06@email.com" defaultName="Jane Doe 06" />
<!-- ^^^ LINE 736 ^^^ -->
<add name="SystemEmail07" defaultAddress="SystemEmail07@email.com" defaultName="John Doe 07" />
<add name="SystemEmail08" defaultAddress="SystemEmail08@email.com" defaultName="Jane Doe 08" />
</email>
</email>
</emails>
</systemEmails>

Now, to make it work like in ideal world, you can utilize IsDefaultCollection property of ConfigurationProperty attribute like this:

[ConfigurationProperty("", IsDefaultCollection = true)]
[ConfigurationCollection(typeof (SystemEmailsElementCollection),
AddItemName = "email")]
public SystemEmailsElementCollection Emails
{
get { return base[""] as SystemEmailsElementCollection; }
}

This will allow you to create recursive email nodes the way you would expect to:

  <emails>
<email name="SystemEmail01" defaultAddress="SystemEmail01@email.com" defaultName="John Doe 01" />
<email name="SystemEmail02" source="UserName" username="Username02" defaultAddress="SystemEmail02@email.com" defaultName="Jane Doe 02" />
<email name="SystemEmail03" defaultAddress="SystemEmail03@email.com" defaultName="John Doe 03" />
<email name="SystemEmail04" source="UserName" username="Username04" />
<email name="SystemEmail05" source="RoleName" rolename="administrators" defaultEmailName="SystemEmail02" />
<email name="SystemEmailGroup01" source="Group">
<email name="SystemEmail06" defaultAddress="SystemEmail06@email.com" defaultName="Jane Doe 06">
<email name="SystemEmail04" source="UserName" username="Username04" />
<email name="SystemEmail05" source="RoleName" rolename="administrators" defaultEmailName="SystemEmail02" />
</email>
<!-- ^^^ LINE 736 ^^^ -->
<email name="SystemEmail07" defaultAddress="SystemEmail07@email.com" defaultName="John Doe 07" />
<email name="SystemEmail08" defaultAddress="SystemEmail08@email.com" defaultName="Jane Doe 08" />
</email>
</emails>

Custom config section with two differentt child collections

You cannot have two default collections. To have configuration classes matching your xml sample, you will have to assign the names "collection1" and "collection2" respectively instead of just "" and set IsDefaultCollection to false in the ConfigurationProperty attributes, and set the AddItemName to "subitem1" and "subitem2" for the ConfigurationCollection attributes. Also fix what @thymine pointed out about the type of the second collection.

Custom section in web.config and getting all items

The full answer to my question can be found in the following SO Question:

Correct implementation of a custom config section with nested collections?

The problem is that I can't create multiple sections, but I can create multiple elements of that section via collection. With that I have to configure the classes to support my layout. Please see the SO Answer posted above for full example.

How to implement a ConfigurationSection with a ConfigurationElementCollection

The previous answer is correct but I'll give you all the code as well.

Your app.config should look like this:

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

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


Related Topics



Leave a reply



Submit