Как сохранить коллекцию пользовательских объектов в файле user.config?

Я хотел бы сохранить коллекцию пользовательских объектов в файле user.config и хотел бы программно добавлять и удалять элементы из коллекции, а затем сохранять измененный список обратно в файл конфигурации.

Мои элементы имеют следующую простую форму:

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

В моем app.config я уже создал пользовательский раздел:

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

Я также могу читать настройки, реализуя 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;
    }
}

Все это я сделал после этой статьи. Однако, используя этот подход, я могу только читать настройки из app.config в коллекцию List<UserInfo>, но мне также нужно будет записать измененный список обратно.

Я безуспешно искал документацию, и теперь я застрял. Что мне не хватает?


person Dirk Vollmar    schedule 09.04.2009    source источник
comment
+1, чтобы опровергнуть -1, оставленный кем-то другим, нет необходимости отмечать искренние вопросы, подобные этому.   -  person Patrick McDonald    schedule 09.04.2009
comment
Спасибо, Патрик, здесь много отрицательных голосов без причины, так что мне в принципе все равно ;-)   -  person Dirk Vollmar    schedule 09.04.2009
comment
лол, ну, я думал, что к вопросу можно отнестись более серьезно с неотрицательной оценкой :)   -  person Patrick McDonald    schedule 09.04.2009
comment
очень разумный вопрос. У меня был аналогичный вопрос. +1   -  person AJ.    schedule 23.10.2009


Ответы (4)


Я бы не стал хранить такие данные в app.config, по крайней мере, если он предназначен для программного обновления. Концептуально это для настроек конфигурации, а не для данных приложения, поэтому, возможно, вы хотите сохранить информацию об имени пользователя и пароле в отдельном файле XML (при условии, что вы не можете или не хотите использовать базу данных)?

Сказав это, я думаю, что вам лучше всего прочитать app.config как стандартный файл XML, проанализировать его, добавить нужные узлы и записать его обратно. Встроенный API-интерфейс ConfigurationManager не предлагает способа обратной записи новых настроек (что, я полагаю, дает подсказку относительно предполагаемого использования Microsoft).

person Dana    schedule 09.04.2009
comment
Я согласен с вами, что было бы лучше хранить настройки в файле user.config, но разве это не тот же самый API? В моем случае данные на самом деле являются данными конфигурации, которые могут быть обновлены администратором. - person Dirk Vollmar; 09.04.2009
comment
С простыми настройками имя-значение сохранение не проблема, однако с пользовательскими типами это кажется слишком сложным. В любом случае, я думаю, что последую вашему предложению и просто забуду о механизме конфигурации .NET и буду использовать свою собственную XML-конфигурацию. - person Dirk Vollmar; 09.04.2009

Способ добавления пользовательской конфигурации (если вам требуется больше, чем просто типы) заключается в использовании ConfigurationSection, в котором для определенной вами схемы вам нужен ConfigurationElementCollection (установленный как коллекция по умолчанию без имени), которая содержит ConfigurationElement, как показано ниже. :

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

Я сохранил класс UserElement простым, хотя на самом деле он должен следовать шаблону полного объявления каждого свойства, как описано в отличная статья CodeProject. Как вы можете видеть, он представляет элементы «пользователя» в вашей конфигурации, которую вы предоставили.

Класс UserElementCollection просто поддерживает наличие более одного «пользовательского» элемента, включая возможность добавлять/удалять/очищать элементы из коллекции, если вы хотите изменить ее во время выполнения.

Наконец, есть UserInfoSection, который просто показывает, что у него есть коллекция «пользовательских» элементов по умолчанию.

Далее приведен пример файла App.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup>
      <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>

Как видите, в этом примере я включил некоторые элементы userInfo/user в файл App.config. Я также добавил настройки, чтобы сказать, что они могут быть определены на уровне машины/приложения/пользователя/роуминг-пользователя.

Далее нам нужно знать, как обновлять их во время выполнения, следующий код показывает пример:

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

Вышеприведенный код при необходимости создаст новый файл user.config, спрятанный глубоко внутри папки «Local Settings\Application Data» для пользователя.

Если вместо этого вы хотите добавить нового пользователя в файл app.config, просто измените параметр метода OpenExeConfiguration() на ConfigurationUserLevel.None.

Как видите, это довольно просто, хотя для поиска этой информации потребовалось немного покопаться.

person Timothy Walters    schedule 16.04.2009
comment
Геттер Key — это то, чего мне не хватало. Большое спасибо! - person Matt Davis; 03.12.2010
comment
+1: хорошая работа - это значительно улучшит мой проект пользовательских конфигураций. - person IAbstract; 20.05.2012
comment
@TimothyWalters: кажется, вам может не хватать открывающего элемента для <sectionGroup> (в вашем фрагменте app.config) ... у вас есть закрывающий элемент, но не открывающий. - person IAbstract; 21.05.2012
comment
Только что наткнулся на ваш комментарий @IAbstract, исправил, спасибо за улов. - person Timothy Walters; 13.03.2013

Используйте новый API System.Configuration из .Net Framework 2. (Assembly: System.Configuration) IConfigurationSectionHandler устарел.

Вы можете найти множество очень хороших примеров и описаний по адресу http://www.codeproject.com/KB/dotnet/mysteriesofconfiguration.aspx

Существует также пример кода о том, как вы можете изменять и сохранять значения.


EDIT: Соответствующая часть документации в codeproject

Сохраняет только измененные значения, если существуют какие-либо изменения

Configuration.Save() 

Сохраняет указанный уровень изменений, если какие-либо изменения существуют

Configuration.Save(ConfigurationSaveMode) 

Сохраняет указанный уровень изменений, принудительно сохраняя, если второй параметр имеет значение true

Configuration.Save(ConfigurationSaveMode, bool)

Перечисление ConfigurationSaveMode имеет следующие значения:

  • Полный – сохраняет все свойства конфигурации независимо от того, были ли они изменены или нет.
  • Изменено: сохраняет измененные свойства, даже если текущее значение совпадает с исходным.
  • Минимальный: сохраняются только те свойства, которые были изменены и имеют значения, отличные от исходных.
person Michael Piendl    schedule 09.04.2009

вы должны создать класс, например:

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

после этого в вашем web.config используйте:

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

Вот так) Если вы хотите использовать не атрибуты, а свойства, просто создайте класс, производный от ConfigurationElement, и включите его в свой класс, определенный из ConfigurationSettings.

person 0100110010101    schedule 09.04.2009