views:

3454

answers:

6

I am trying to access settings in my config file, which is a series of xml elements listed as such:

<databases>
<database name="DatabaseOne" Value="[value]" />
<database name="DatabaseTwo" Value="[value]" />
</databases>

Now I want to access it. I have set up classes like so:

Public Class DatabaseConfigurationHandler
    Inherits ConfigurationSection

    <ConfigurationProperty("Databases", IsDefaultCollection:=True)> _
   Public ReadOnly Property Databases() As DatabaseCollection
        Get
            Return CType(Me("Databases"), DatabaseCollection)
        End Get
    End Property
End Class

Public Class DatabaseCollection
    Inherits ConfigurationElementCollection

    Protected Overloads Overrides Function CreateNewElement() As ConfigurationElement
        Return (New Database())
    End Function

    Protected Overloads Overrides Function GetElementKey(ByVal element As ConfigurationElement) As Object
        Return (CType(element, Database).DatabaseName)
    End Function

End Class

Public Class Database
    Inherits ConfigurationElement

    <ConfigurationProperty("name", IsKey:=True, IsRequired:=True)> _
       Public Property DatabaseName() As String
        Get
            Return Me("name").ToString()
        End Get
        Set(ByVal Value As String)
            Me("name") = Value
        End Set
    End Property


    <ConfigurationProperty("value", IsRequired:=True)> _
Public Property DatabaseValue() As String
        Get
            Return Me("value").ToString()
        End Get
        Set(ByVal Value As String)
            Me("value") = Value
        End Set
    End Property

End Class

I want to be able get the element by it's name and return the value but I can't see to do that:

Dim config As New DatabaseConfigurationHandler
                config = System.Configuration.ConfigurationManager.GetSection("databases/database")
                Return config.Databases("DatabaseOne")

Am I missing some code, what am I doing wrong? Any other errors in the above?

Thanks.

+6  A: 

There isn't any good reason to design this kind of stuff by hand anymore. Rather, you should be using the Configuration Section Designer on CodePlex:

http://csd.codeplex.com/

Once installed, you can just add a new item to your project (a configuration section designer) and then add the elements and the constraints. I've found it VERY easy to use, and I will probably never write a piece of code for configuration files again.

casperOne
+1 for the link, but I need a programmatic way to do this for project reasons.
Damien
@Damien: It is a programmatic way to do this. The designer is a way to create the sections for your config file and then access them in a type-safe manner later. You can also tweak the code that the designer produces.
casperOne
I have written a fair bit of Mickey Mouse run about code by hand .... can this little tool be used to integrate with an existing config file and work side by side with with code I already have in place?
IbrarMumtaz
@IbrarMumtaz: It depends on what kind of integration you want with the code you have in place. If you want to create new configuration sections for the config file that have a separate namespace, then yes. If you want the configuration manager to handle partial serialization of the section, then I don't think it will work.
casperOne
+3  A: 

Here's a cut and paste from something very similar I did a few days ago.

Config:

  <ListConfigurations>
    <lists>
      <add Name="blah" EndpointConfigurationName="blah" ListName="blah" ConnectionString="blah" TableName="blah" FieldsCsv="blah" DbFieldsCsv="blah"/>
      <add Name="blah2" EndpointConfigurationName="blah" ListName="blah" ConnectionString="blah" TableName="blah" FieldsCsv="blah" DbFieldsCsv="blah"/>
    </lists>
  </ListConfigurations>

Config section C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;

namespace App
{
    /// <summary>
    /// Individual list configuration
    /// </summary>
    class ListConfiguration : ConfigurationElement
    {
        [ConfigurationProperty("Name", IsKey = true, IsRequired = true)]
        public string Name
        {
            get { return (string)this["Name"]; }
        }

        [ConfigurationProperty("EndpointConfigurationName", IsRequired = true)]
        public string EndpointConfigurationName
        {
            get { return (string)this["EndpointConfigurationName"]; }
        }

        [ConfigurationProperty("ListName", IsRequired = true)]
        public string ListName
        {
            get { return (string)this["ListName"]; }
        }

        [ConfigurationProperty("ConnectionString", IsRequired = true)]
        public string ConnectionString
        {
            get { return (string)this["ConnectionString"]; }
        }

        [ConfigurationProperty("TableName", IsRequired = true)]
        public string TableName
        {
            get { return (string)this["TableName"]; }
        }

        [ConfigurationProperty("FieldsCsv", IsRequired = true)]
        public string FieldsCsv
        {
            get { return (string)this["FieldsCsv"]; }
        }

        [ConfigurationProperty("DbFieldsCsv", IsRequired = true)]
        public string DbFieldsCsv
        {
            get { return (string)this["DbFieldsCsv"]; }
        }
    }

    /// <summary>
    /// Collection of list configs
    /// </summary>
    class ListConfigurationCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new ListConfiguration();
        }

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

    /// <summary>
    /// Config section
    /// </summary>
    class ListConfigurationSection : ConfigurationSection
    {
        [ConfigurationProperty("lists")]
        public ListConfigurationCollection Lists
        {
            get { return (ListConfigurationCollection)this["lists"]; }
        }
    }
}

And the code to pick it up from the main app:

ListConfigurationSection configSection = null;
try
{
    configSection = ConfigurationManager.GetSection("ListConfigurations") as ListConfigurationSection;
}
catch (System.Configuration.ConfigurationErrorsException)
{
}
Steven Robbins
Looks similar, albeit in c#, how did you access a single element by it's name?
Damien
Good question, I didn't need that for this particular piece, but you could do it with LINQ: ListConfiguration li = configSection.Lists.OfType<ListConfiguration>().Where(l => l.Name == "blah").FirstOrDefault();
Steven Robbins
+2  A: 

You can use this configuration handler instead.. It will work for ALL custom configuration sections

   public class XmlConfigurator : IConfigurationSectionHandler
    {
        public object Create(object parent, 
                   object configContext, XmlNode section)
        {
            if (section == null) 
                throw new ArgumentNullException("section",
                    "Invalid or missing configuration section " + 
                    "provided to XmlConfigurator");
            XPathNavigator xNav = section.CreateNavigator();
            if (xNav == null) 
                throw new ApplicationException(
                    "Unable to create XPath Navigator"); 
            Type sectionType = Type.GetType((string)
                (xNav).Evaluate("string(@configType)"));
            XmlSerializer xs = new XmlSerializer(sectionType);
            return xs.Deserialize(new XmlNodeReader(section));
        }
    }

Your config file then has to include a reference to the type that the represents the root element

<ConnectionConfig 
   configType="MyNamespace.ConnectionConfig, MyNamespace.AssmblyName" >

A sample config file might look like this:

<?xml version="1.0" encoding="utf-8" ?>

<ConnectionConfig 
   configType="MyNamespace.ConnectionConfig, MyNamespace.AssmblyName" >

  <ConnCompanys>
    <ConnCompany companyName="BPA">
      <ConnApps>
        <ConnApp applicationName="Athena" vendorName="Oracle" >
          <ConnSpecs>
            <ConnSpec environments="DEV"
              serverName="Athena"
              port="1521"
              catalog="DatabaseName"
              logon="MyUserName"
              password="%%552355%8234^kNfllceHGp55X5g==" />
     <!--  etc...

And you will need to define the classes that each xml element maps to... using the appropriate XmlSerialization attributes ...

[XmlRoot("ConnectionConfig")]
public class ConnectionConfig
{
    private ConnCompanys comps;

    [XmlArrayItem(ElementName = "ConnCompany")]
    public ConnCompanys ConnCompanys
    {
        get { return comps; }
        set { comps = value; }
    }
    public ConnApp this[string CompanyName, string AppName]
    { get { return ConnCompanys[CompanyName][AppName]; } }

    public ConnSpec this[string CompanyName, string AppName, APPENV env]
    {
        get
        {
            return ConnCompanys[CompanyName][AppName, env];
        }
    }
}

public class ConnCompanys : List<ConnCompany>
{
    public ConnCompany this[string companyName]
    {
        get
        {
            foreach (ConnCompany comp in this)
                if (comp.CompanyName == companyName)
                    return comp;
            return null;
        }
    }
    public bool Contains(string companyName)
    {
        foreach (ConnCompany comp in this)
            if (comp.CompanyName == companyName)
                return true;
        return false;
    }
}

public class ConnCompany
{
    #region private state fields
    private string compNm;
    private ConnApps apps;
    #endregion private state fields

    #region public properties
    [XmlAttribute(DataType = "string", AttributeName = "companyName")]
    public string CompanyName
    {
        get { return compNm; }
        set { compNm = value; }
    }

    [XmlArrayItem(ElementName = "ConnApp")]
    public ConnApps ConnApps
    {
        get { return apps; }
        set { apps = value; }
    }
    #endregion public properties

    #region indexers
    public ConnApp this[string applicationName]
    { get { return ConnApps[applicationName]; } }
    public ConnSpec this[string applicationName, APPENV environment]
    {
        get
        {
            foreach (ConnSpec con in this[applicationName].ConnSpecs)
                if (con.Environment == environment)
                    return con;
            return null;
        }
    }
    #endregion indexers
}

etc...

Charles Bretana
Readers should be aware that "ConfigurationSectionHandler is deprecated in .NET Framework 2.0 and above. But, because it is used internally, it has been kept."http://msdn.microsoft.com/en-us/library/system.configuration.iconfigurationsectionhandler.aspx
rohancragg
A: 

My VB isn't up to much sorry but here's how you do it in C#.

C#

public class AppState : IConfigurationSectionHandler
{
    static AppState()
    {
        xmlNode myConfigNode = (XmlNode)ConfigurationManager.GetSection("databases");
    }

    public object Create(object parent, object context, XmlNode configSection) {
        return configSection;
    }
}

App.Config

<configuration>
  <configSections>
    <section name="databases" type="MyAssembly.AppState, MyAssembly" />
  </configSections>
  <databases>
    <database name="DatabaseOne" Value="[value]" />
    <database name="DatabaseTwo" Value="[value]" />
  </databases>
</configuration>
sipwiz
A: 

Hi! You might be interested in using a ConfigurationElementCollection where T is the type of the child ConfigurationElements. You can find sample code in C# at http://devpinoy.org/blogs/jakelite/archive/2009/01/10/iconfigurationsectionhandler-is-dead-long-live-iconfigurationsectionhandler.aspx

Cheers!

jake.stateresa
A: 

There's a nice simple way of doing this demonstrated here also:

codeproject.com/KB/XML/xml_config_section.aspx

rohancragg