views:

1291

answers:

4

I would like to store a collection of custom objects in a user.config file and would like to add and remove items from the collection programmatically and then save the modified list back to the configuration file.

My items are of the following simple form:

class UserInfo
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }        
}

In my app.config I already created a custom section:

<configuration>
  <configSections>
    <section name="userInfo" type="UserInfoConfigurationHandler, MyProgram"/>

  </configSections>
  <userInfo>
    <User firstName="John" lastName="Doe" email="[email protected]" />
    <User firstName="Jane" lastName="Doe" email="[email protected]" />
  </userInfo>

</configuration>

I am also able to read in the settings by implementing IConfigurationSectionHandler:

class UserInfoConfigurationHandler : IConfigurationSectionHandler
{
    public UserInfoConfigurationHandler() { }

    public object Create(object parent, object configContext, System.Xml.XmlNode section)
    {
        List<UserInfo> items = new List<UserInfo>();
        System.Xml.XmlNodeList processesNodes = section.SelectNodes("User");

        foreach (XmlNode processNode in processesNodes)
        {
            UserInfo item = new UserInfo();
            item.FirstName = processNode.Attributes["firstName"].InnerText;
            item.LastName = processNode.Attributes["lastName"].InnerText;
            item.Email = processNode.Attributes["email"].InnerText;
            items.Add(item);
        }
        return items;
    }
}

I did all this following this article. However, using this approach I'm only able to read the settings from app.config into a List<UserInfo> collection, but I would also need to write a modified list back.

I was searching the documentation without success and now I'm kind of stuck. What am I missing?

+4  A: 

I wouldn't store that kind of data in an app.config, at least not if it's meant to be updated programatically. Conceptually, it's for configuration settings, not application data so perhaps you want to store your username and password info in a separate XML file (assuming you can't or don't want to use a database)?

Having said that, then I think your best bet is to read in the app.config as a standard XML file, parse it, add the nodes you want and write it back. The built in ConfigurationManager API doesn't offer a way to write back new settings (which I suppose gives a hint as to Microsoft's intended use).

Dana
I agree with you that it would be better to store the settings in a user.config file, however isn't that the exact same API? In my case the data is actually configuration data that might be updated by an admin.
0xA3
With simple name-value settings saving is no problem, however it seems to get overly complicated with custom types. Anyway I think I will follow your proposal and simply forget about .NET's config mechanism and use my own custom XML config.
0xA3
+1  A: 

Use the new System.Configuration API from .Net Framework 2. (Assembly: System.Configuration) IConfigurationSectionHandler is obsolete.

You can find a lot of very good samples and descriptions at http://www.codeproject.com/KB/dotnet/mysteriesofconfiguration.aspx

There is also a code sample, about how you can modify and save values.


EDIT: Relevant part of the documentation in codeproject

Saves only modified values if any changes exist

Configuration.Save()

Saves the specified level of changes, if any changes exist

Configuration.Save(ConfigurationSaveMode)

Saves the specified level of changes, forcing a save to take place if the second parameter is true

Configuration.Save(ConfigurationSaveMode, bool)

The ConfigurationSaveMode enumeration has the following values:

  • Full - Saves all configuration properties, whether they have changed or not
  • Modified - Saves properties that have been modified, even if the current value is the same as the original
  • Minimal - Saves only properties that have been modified and have different values than the original
michl86
A: 

you should create a class like:

public class MySettings : ConfigurationSection 
{
    public MySettings Settings = (MySettings)WebConfigurationManager.GetSection("MySettings");

    [ConfigurationProperty("MyConfigSetting1")]
    public string DefaultConnectionStringName
    {
        get { return (string)base["MyConfigSetting1"]; }
        set { base["MyConfigSetting1"] = value; }
    }
}

after that in your web.config use:

<section name="MySettings" type="MyNamespace.MySettings"/>
<MySettings MyConfigSetting1="myValue">

That's the way) If you want to use not attibutes, but properties, just create the class derived from ConfigurationElement and include it to your class defived from ConfigurationSettings.

ifesdjeen
+7  A: 

The way to add custom config (if you require more than just simple types) is to use a ConfigurationSection, within that for the schema you defined you need a ConfigurationElementCollection (set as default collection with no name), which contains a ConfigurationElement, as follows:

public class UserElement : ConfigurationElement
{
    [ConfigurationProperty( "firstName", IsRequired = true )]
    public string FirstName
    {
        get { return (string) base[ "firstName" ]; }
        set { base[ "firstName" ] = value;}
    }

    [ConfigurationProperty( "lastName", IsRequired = true )]
    public string LastName
    {
        get { return (string) base[ "lastName" ]; }
        set { base[ "lastName" ] = value; }
    }

    [ConfigurationProperty( "email", IsRequired = true )]
    public string Email
    {
        get { return (string) base[ "email" ]; }
        set { base[ "email" ] = value; }
    }

    internal string Key
    {
        get { return string.Format( "{0}|{1}|{2}", FirstName, LastName, Email ); }
    }
}

[ConfigurationCollection( typeof(UserElement), AddItemName = "user", CollectionType = ConfigurationElementCollectionType.BasicMap )]
public class UserElementCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new UserElement();
    }

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

    public void Add( UserElement element )
    {
        BaseAdd( element );
    }

    public void Clear()
    {
        BaseClear();
    }

    public int IndexOf( UserElement element )
    {
        return BaseIndexOf( element );
    }

    public void Remove( UserElement element )
    {
        if( BaseIndexOf( element ) >= 0 )
        {
            BaseRemove( element.Key );
        }
    }

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

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

public class UserInfoSection : ConfigurationSection
{
    private static readonly ConfigurationProperty _propUserInfo = new ConfigurationProperty(
            null,
            typeof(UserElementCollection),
            null,
            ConfigurationPropertyOptions.IsDefaultCollection
    );

    private static ConfigurationPropertyCollection _properties = new ConfigurationPropertyCollection();

    static UserInfoSection()
    {
        _properties.Add( _propUserInfo );
    }

    [ConfigurationProperty( "", Options = ConfigurationPropertyOptions.IsDefaultCollection )]
    public UserElementCollection Users
    {
        get { return (UserElementCollection) base[ _propUserInfo ]; }
    }
}

I've kept the UserElement class simple, although it really should follow the pattern of declaring each property fully as described in this excellent CodeProject article. As you can see it represents the "user" elements in your config you provided.

The UserElementCollection class simply supports having more than one "user" element, including the ability to add/remove/clear items from the collection if you want to modify it at run-time.

Lastly there is the UserInfoSection which simply stats that it has a default collection of "user" elements.

Next up is a sample of the App.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
      name="userInfo"
      type="ConsoleApplication1.UserInfoSection, ConsoleApplication1"
      allowDefinition="Everywhere"
      allowExeDefinition="MachineToLocalUser"
    />
    </sectionGroup>
  </configSections>

  <userInfo>
    <user firstName="John" lastName="Doe" email="[email protected]" />
    <user firstName="Jane" lastName="Doe" email="[email protected]" />
  </userInfo>
</configuration>

As you can see, in this example I've included some userInfo/user elements in the App.config. I've also added settings to say they can be defined at machine/app/user/roaming-user levels.

Next we need to know how to update them at run-time, the following code shows an example:

Configuration userConfig = ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.PerUserRoamingAndLocal );

var userInfoSection = userConfig.GetSection( "userInfo" ) as UserInfoSection;

var userElement = new UserElement();

userElement.FirstName = "Sample";
userElement.LastName = "User";
userElement.Email = "[email protected]";

userInfoSection.Users.Add( userElement );

userConfig.Save();

The above code will create a new user.config file if needed, buried deep inside the "Local Settings\Application Data" folder for the user.

If instead you want the new user added to the app.config file simply change the parameter for the OpenExeConfiguration() method to ConfigurationUserLevel.None.

As you can see, it's reasonably simple, although finding this information required a bit of digging.

Timothy Walters
+1 - this solution just works. Very helpful.
Evgeny