Пользовательский путь к user.config

Я управляю настройками приложения с помощью конструктора настроек в VS2008.

"Точный путь к файлам user.config выглядит примерно так:"

<Profile Directory>\<Company Name>\
 <App Name>_<Evidence Type>_<Evidence Hash>\<Version>\user.config

Есть ли способ настроить этот путь? Я бы предпочел что-то вроде этого:

<Profile Directory>\<Company Name>\
 <App Name>\<Version>\user.config

Я заметил, что пробелы были заменены символами подчеркивания в «Имя компании» в новой созданной папке («Тестовая компания» -> «Тестовая_компания»). Я действительно хочу отключить это поведение.

Вы знаете, я мог бы написать новый обработчик настроек на основе XML, но я хотел бы использовать конструктор настроек.


person Community    schedule 15.02.2010    source источник


Ответы (4)


Было нелегко найти хорошую информацию о реализации поставщика пользовательских настроек, поэтому я включаю полную реализацию ниже (внизу). Формат файла user.config сохранен, а также функциональность в дизайнере .settings. . Я уверен, что есть части, которые можно немного почистить, так что не беспокойте меня :)

Как и другие, я хотел изменить расположение файла user.config и по-прежнему получать удовольствие от работы с файлами .settings в дизайнере, включая создание значений по умолчанию для новых установок. Важно отметить, что в нашем приложении также уже есть другие сохраненные объекты настроек по пути (appData\local\etc), в котором мы уже определились, и нам не нужны артефакты в нескольких местах.

Код намного длиннее, чем мне хотелось бы, но КОРОТКОГО ответа я не нашел. Хотя сама по себе возможность контролировать путь кажется несколько болезненной, само по себе создание поставщика настраиваемых параметров все еще является довольно мощным средством. Можно изменить следующую реализацию, чтобы хранить данные практически в любом месте, включая пользовательский зашифрованный файл, базу данных или взаимодействие с веб-сервисом.

Из того, что я читал, Microsoft не собирается делать путь к конфигурационному файлу настраиваемым. Я поверю им на слово, когда они скажут, что допустить это было бы страшно. См. (это) Почта. Увы, если вы хотите сделать это самостоятельно, вы должны реализовать свой собственный SettingsProvider.

Вот оно..

Добавьте ссылку в свой проект на System.Configuration, она понадобится вам для реализации SettingsProvider.

Легко... Создайте класс, который реализует SettingsProvider, используйте ctrl+. чтобы помочь вам.

class CustomSettingsProvider : SettingsProvider

Еще один простой момент... Перейдите к коду вашего файла .settings (в дизайнере есть кнопка) и украсьте класс, чтобы он указывал на вашу реализацию. Это необходимо сделать, чтобы переопределить встроенную функциональность, но это не меняет работу дизайнера (извините, форматирование здесь странное).

[System.Configuration.SettingsProvider(typeof(YourCompany.YourProduct.CustomSettingsProvider))]

public sealed partial class Settings
{
    //bla bla bla
}

Получение пути. Существует свойство под названием "SettingsKey" (например, Properties.Settings.Default.SettingsKey). Я использовал его для хранения пути. Я сделал следующее свойство.

/// <summary>
/// The key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
    get
    {
        return Properties.Settings.Default.SettingsKey;
    }

}

Сохранение значений настроек. Я решил использовать словарь. Это будет использоваться экстенсивно в немного. Я создал структуру в качестве помощника.

/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
    internal string name;
    internal string serializeAs;
    internal string value;
}

Магия. Вы должны переопределить 2 метода: GetPropertyValues и SetPropertyValues. GetPropertyValues ​​получает в качестве параметра то, что вы видите в дизайнере, у вас есть возможность обновить значения и вернуть новую коллекцию. SetPropertyValues ​​вызывается, когда пользователь сохраняет любые изменения значений, сделанные во время выполнения, именно здесь я обновляю словарь и записываю файл.

/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
    //load the file
    if (!_loaded)
    {
         _loaded = true;
         LoadValuesFromFile();
    }

    //collection that will be returned.
    SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

    //iterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
    foreach (SettingsProperty setting in collection)
    {
        SettingsPropertyValue value = new SettingsPropertyValue(setting);
        value.IsDirty = false;

        //need the type of the value for the strong typing
        var t = Type.GetType(setting.PropertyType.FullName);

        if (SettingsDictionary.ContainsKey(setting.Name))
        {
            value.SerializedValue = SettingsDictionary[setting.Name].value;
            value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
        }
        else //use defaults in the case where there are no settings yet
        {
            value.SerializedValue = setting.DefaultValue;
            value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
        }

            values.Add(value);
    }
    return values;
}

/// <summary>
/// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
    //grab the values from the collection parameter and update the values in our dictionary.
    foreach (SettingsPropertyValue value in collection)
    {
        var setting = new SettingStruct()
        {
            value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
            name = value.Name,
            serializeAs = value.Property.SerializeAs.ToString()
        };

        if (!SettingsDictionary.ContainsKey(value.Name))
        {
            SettingsDictionary.Add(value.Name, setting);
        }
        else
        {
            SettingsDictionary[value.Name] = setting;
        }
    }

    //now that our local dictionary is up-to-date, save it to disk.
    SaveValuesToFile();
}

Полное решение. Вот весь класс, который включает в себя конструктор, Initialize и вспомогательные методы. Не стесняйтесь вырезать / вставлять кусочки и кости.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;

namespace YourCompany.YourProduct
{
    class CustomSettingsProvider : SettingsProvider
    {
        const string NAME = "name";
        const string SERIALIZE_AS = "serializeAs";
        const string CONFIG = "configuration";
        const string USER_SETTINGS = "userSettings";
        const string SETTING = "setting";

        /// <summary>
        /// Loads the file into memory.
        /// </summary>
        public CustomSettingsProvider()
        {
            SettingsDictionary = new Dictionary<string, SettingStruct>();

        }

        /// <summary>
        /// Override.
        /// </summary>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(ApplicationName, config);
        }

        /// <summary>
        /// Override.
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
            }
            set
            {
                //do nothing
            }
        }

        /// <summary>
        /// Must override this, this is the bit that matches up the designer properties to the dictionary values
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            //load the file
            if (!_loaded)
            {
                _loaded = true;
                LoadValuesFromFile();
            }

            //collection that will be returned.
            SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

            //itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
            foreach (SettingsProperty setting in collection)
            {
                SettingsPropertyValue value = new SettingsPropertyValue(setting);
                value.IsDirty = false;

                //need the type of the value for the strong typing
                var t = Type.GetType(setting.PropertyType.FullName);

                if (SettingsDictionary.ContainsKey(setting.Name))
                {
                    value.SerializedValue = SettingsDictionary[setting.Name].value;
                    value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
                }
                else //use defaults in the case where there are no settings yet
                {
                    value.SerializedValue = setting.DefaultValue;
                    value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
                }

                values.Add(value);
            }
            return values;
        }

        /// <summary>
        /// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            //grab the values from the collection parameter and update the values in our dictionary.
            foreach (SettingsPropertyValue value in collection)
            {
                var setting = new SettingStruct()
                {
                    value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
                    name = value.Name,
                    serializeAs = value.Property.SerializeAs.ToString()
                };

                if (!SettingsDictionary.ContainsKey(value.Name))
                {
                    SettingsDictionary.Add(value.Name, setting);
                }
                else
                {
                    SettingsDictionary[value.Name] = setting;
                }
            }

            //now that our local dictionary is up-to-date, save it to disk.
            SaveValuesToFile();
        }

        /// <summary>
        /// Loads the values of the file into memory.
        /// </summary>
        private void LoadValuesFromFile()
        {
            if (!File.Exists(UserConfigPath))
            {
                //if the config file is not where it's supposed to be create a new one.
                CreateEmptyConfig();
            }

            //load the xml
            var configXml = XDocument.Load(UserConfigPath);

            //get all of the <setting name="..." serializeAs="..."> elements.
            var settingElements = configXml.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName).Elements(SETTING);

            //iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
            //using "String" as default serializeAs...just in case, no real good reason.
            foreach (var element in settingElements)
            {
                var newSetting = new SettingStruct()
                {
                    name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
                    serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value,
                    value = element.Value ?? String.Empty
                };
                SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
            }
        }

        /// <summary>
        /// Creates an empty user.config file...looks like the one MS creates.  
        /// This could be overkill a simple key/value pairing would probably do.
        /// </summary>
        private void CreateEmptyConfig()
        {
            var doc = new XDocument();
            var declaration = new XDeclaration("1.0", "utf-8", "true");
            var config = new XElement(CONFIG);
            var userSettings = new XElement(USER_SETTINGS);
            var group = new XElement(typeof(Properties.Settings).FullName);
            userSettings.Add(group);
            config.Add(userSettings);
            doc.Add(config);
            doc.Declaration = declaration;
            doc.Save(UserConfigPath); 
        }

        /// <summary>
        /// Saves the in memory dictionary to the user config file
        /// </summary>
        private void SaveValuesToFile()
        {
            //load the current xml from the file.
            var import = XDocument.Load(UserConfigPath);

            //get the settings group (e.g. <Company.Project.Desktop.Settings>)
            var settingsSection = import.Element(CONFIG).Element(USER_SETTINGS).Element(typeof(Properties.Settings).FullName);

            //iterate though the dictionary, either updating the value or adding the new setting.
            foreach (var entry in SettingsDictionary)
            {
                var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
                if (setting == null) //this can happen if a new setting is added via the .settings designer.
                {
                    var newSetting = new XElement(SETTING);
                    newSetting.Add(new XAttribute(NAME, entry.Value.name));
                    newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
                    newSetting.Value = (entry.Value.value ?? String.Empty);
                    settingsSection.Add(newSetting);
                }
                else //update the value if it exists.
                {
                    setting.Value = (entry.Value.value ?? String.Empty);
                }
            }
            import.Save(UserConfigPath);
        }

        /// <summary>
        /// The setting key this is returning must set before the settings are used.
        /// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
        /// </summary>
        private string UserConfigPath
        {
            get
            {
                return Properties.Settings.Default.SettingsKey;
            }

        }

        /// <summary>
        /// In memory storage of the settings values
        /// </summary>
        private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

        /// <summary>
        /// Helper struct.
        /// </summary>
        internal struct SettingStruct
        {
            internal string name;
            internal string serializeAs;
            internal string value;
        }

        bool _loaded;
    }
}    
person Chuck Rostance    schedule 09.07.2012
comment
Спасибо за это, это было большой помощью, чтобы начать. Однако я обнаружил, что функции Type.GetType() и Convert.ChangeType() не работают с System.Drawing.Point и System.Drawing.Size и, возможно, с некоторыми другими. В качестве обходного пути я просто использую пары целых чисел, но они по-прежнему доступны в дизайнере настроек. - person Troyen; 27.08.2012
comment
Также была проблема с GetType и ChangeType. Я пытаюсь сохранить/загрузить ListSortDirection, но GetType возвращает null, а ChangeType выбрасывает. Строка была сериализована как Восходящая, но не могла быть десериализована. В качестве обходного пути я попытался использовать этот метод GetType, который дает мне правильный тип ListSortDirection, но ChangeType по-прежнему выдает ошибку, говоря Invalid cast from 'System.String' to 'System.ComponentModel.ListSortDirection' . - person epalm; 03.05.2016
comment
Получение нулевых ошибок, если я использую пользовательский класс и проверка файла показывает только мой путь к классу, например: name_space.ClassName в теге свойства. - person Matheus; 13.06.2016
comment
Также, похоже, не работает с Enums и типами классов, в остальном хорошее начало - person George Mauer; 13.07.2016
comment
Также есть проблема с GUID, но этот ответ предлагает решение или, по крайней мере, подход: stackoverflow.com/questions/393731/ - person Sentry; 10.11.2016
comment
Стоит отметить: решение не обрабатывает userSettings и applicationSettings. Это объединит все настройки (независимо от области действия) в файл для сохранения. - person techvice; 05.01.2018
comment
Я бы также добавил, что вы должны убедиться, что папка существует, прежде чем создавать конфигурацию: string userFilePath = Path.GetDirectoryName(UserConfigPath); if (!Directory.Exists(userFilePath)) Directory.CreateDirectory(userFilePath);` - person Rachel Martin; 02.11.2018
comment
Также возникает ошибка сохранения и чтения настроек, сериализуемых как XML (например, StringCollection). Вы должны вручную сериализовать его, используя XmlSerializer. - person kv1dr; 10.07.2019

Вам придется реализовать собственный SettingsProvider, чтобы настроить путь.

См. этот Часто задаваемые вопросы о настройках клиента.

В: Почему путь такой неясный? Есть ли способ изменить/настроить его?

A: Алгоритм построения пути должен соответствовать определенным строгим требованиям с точки зрения безопасности, изоляции и надежности. Хотя мы пытались сделать путь как можно более легко обнаруживаемым, используя понятные строки, предоставляемые приложением, невозможно сохранить путь полностью простым, не сталкиваясь с такими проблемами, как конфликты с другими приложениями, спуфинг и т. д.

LocalFileSettingsProvider не позволяет изменять файлы, в которых хранятся настройки. Обратите внимание, что сам провайдер не определяет расположение файла конфигурации в первую очередь — это система конфигурации. Если по какой-то причине вам нужно сохранить настройки в другом месте, рекомендуется написать собственный SettingsProvider. Это довольно просто реализовать, и вы можете найти примеры в пакете SDK для .NET 2.0, которые показывают, как это сделать. Имейте в виду, однако, что вы можете столкнуться с теми же проблемами изоляции, упомянутыми выше.

person Igor Lankin    schedule 15.02.2010

Вот более простая и краткая альтернатива созданию класса пользовательских настроек: измените свидетельство вашего приложения, чтобы часть «url» была константой, а не основывалась на местоположении исполняемого файла. Для этого вам нужно изменить AppDomain по умолчанию при запуске программы. Есть две части: настройка app.config для использования вашего AppDomainManager и создание AppDomainManager и HostSecurityManager для настройки свидетельства URL. Звучит сложно, но это намного проще, чем создание собственного класса настроек. Это относится только к неподписанным сборкам. Если у вас есть подписанная сборка, она будет использовать это свидетельство вместо URL-адреса. Но хорошая новость заключается в том, что ваш путь в любом случае всегда будет постоянным (пока ключ подписи не изменится).

Вы можете скопировать приведенный ниже код и просто заменить биты YourAppName.

DefaultAppDomainManager.cs:

using System;
using System.Security;
using System.Security.Policy;

namespace YourAppName
{
    /// <summary>
    /// A least-evil (?) way of customizing the default location of the application's user.config files.
    /// </summary>
    public class CustomEvidenceHostSecurityManager : HostSecurityManager
    {
        public override HostSecurityManagerOptions Flags
        {
            get
            {
                return HostSecurityManagerOptions.HostAssemblyEvidence;
            }
        }

        public override Evidence ProvideAssemblyEvidence(System.Reflection.Assembly loadedAssembly, Evidence inputEvidence)
        {
            if (!loadedAssembly.Location.EndsWith("YourAppName.exe"))
                return base.ProvideAssemblyEvidence(loadedAssembly, inputEvidence);

            // override the full Url used in Evidence to just "YourAppName.exe" so it remains the same no matter where the exe is located
            var zoneEvidence = inputEvidence.GetHostEvidence<Zone>();
            return new Evidence(new EvidenceBase[] { zoneEvidence, new Url("YourAppName.exe") }, null);
        }
    }

    public class DefaultAppDomainManager : AppDomainManager
    {
        private CustomEvidenceHostSecurityManager hostSecurityManager;
        public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
        {
            base.InitializeNewDomain(appDomainInfo);

            hostSecurityManager = new CustomEvidenceHostSecurityManager();
        }

        public override HostSecurityManager HostSecurityManager
        {
            get
            {
                return hostSecurityManager;
            }
        }
    }
}

app.config excerpt:

<runtime>
    <appDomainManagerType value="YourAppName.DefaultAppDomainManager" />
    <appDomainManagerAssembly value="DefaultAppDomainManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</runtime>
person Matt Chambers    schedule 07.08.2017
comment
Это интересно. Любая идея, есть ли потенциальные побочные эффекты с изменением способа генерации доказательств? - person Rev; 16.09.2019

Опираясь на отличный ответ Чака:

Реализуйте новый разделяемый класс на основе Settings.Designer.cs, чтобы Конструктор настроек не удалял атрибут при внесении изменений:

namespace Worker.Properties
{
    [System.Configuration.SettingsProvider(
        typeof(SettingsProviders.DllFileSettingsProvider<Settings>))]
    internal sealed partial class Settings
    {

    }
}

Я создал следующий класс, который можно поместить во внешний проект. Это позволяет использовать конфигурацию project.dll.config. Путем наследования и переопределения ConfigPath разрешается любой путь, который вам нравится. Я также добавил поддержку чтения System.Collections.Specialized.StringCollection. Эта версия предназначена для настроек приложения.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Xml.Linq;
using System.IO;


//http://stackoverflow.com/questions/2265271/custom-path-of-the-user-config
namespace SettingsProviders
{
    public  class DllFileSettingsProvider<Properties_Settings> : SettingsProvider where Properties_Settings : new() 
    {
        const string NAME = "name";
        const string SERIALIZE_AS = "serializeAs";
        const string CONFIG = "configuration";
        const string APPLICATION_SETTINGS = "applicationSettings";
        const string SETTING = "setting";

        /// <summary>
        /// Loads the file into memory.
        /// </summary>
        public DllFileSettingsProvider()
        {
            SettingsDictionary = new Dictionary<string, SettingStruct>();

        }

        /// <summary>
        /// Override.
        /// </summary>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(ApplicationName, config);
        }

        /// <summary>
        /// Override.
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
            }
            set
            {
                //do nothing
            }
        }

        /// <summary>
        /// Must override this, this is the bit that matches up the designer properties to the dictionary values
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            //load the file
            if (!_loaded)
            {
                _loaded = true;
                LoadValuesFromFile();
            }

            //collection that will be returned.
            SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();

            //itterate thought the properties we get from the designer, checking to see if the setting is in the dictionary
            foreach (SettingsProperty setting in collection)
            {
                SettingsPropertyValue value = new SettingsPropertyValue(setting);
                value.IsDirty = false;

                //need the type of the value for the strong typing
                var t = Type.GetType(setting.PropertyType.FullName);

                if (setting.PropertyType == typeof(System.Collections.Specialized.StringCollection))
                {
                    var xml = SettingsDictionary[setting.Name].value;
                    var stringReader = new System.IO.StringReader(xml);
                    var xmlreader = System.Xml.XmlReader.Create(stringReader);
                    var ser = new System.Xml.Serialization.XmlSerializer(typeof(System.Collections.Specialized.StringCollection));
                    var obj = ser.Deserialize(xmlreader);
                    var col = (System.Collections.Specialized.StringCollection)obj;
                    value.PropertyValue = col;
                }
                else if (SettingsDictionary.ContainsKey(setting.Name))
                {
                    value.SerializedValue = SettingsDictionary[setting.Name].value;
                    value.PropertyValue = Convert.ChangeType(SettingsDictionary[setting.Name].value, t);
                }
                else //use defaults in the case where there are no settings yet
                {
                    value.SerializedValue = setting.DefaultValue;
                    value.PropertyValue = Convert.ChangeType(setting.DefaultValue, t);
                }

                values.Add(value);
            }
            return values;
        }

        /// <summary>
        /// Must override this, this is the bit that does the saving to file.  Called when Settings.Save() is called
        /// </summary>
        /// <param name="context"></param>
        /// <param name="collection"></param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            //grab the values from the collection parameter and update the values in our dictionary.
            foreach (SettingsPropertyValue value in collection)
            {
                var setting = new SettingStruct()
                {
                    value = (value.PropertyValue == null ? String.Empty : value.PropertyValue.ToString()),
                    name = value.Name,
                    serializeAs = value.Property.SerializeAs.ToString()
                };

                if (!SettingsDictionary.ContainsKey(value.Name))
                {
                    SettingsDictionary.Add(value.Name, setting);
                }
                else
                {
                    SettingsDictionary[value.Name] = setting;
                }
            }

            //now that our local dictionary is up-to-date, save it to disk.
            SaveValuesToFile();
        }

        /// <summary>
        /// Loads the values of the file into memory.
        /// </summary>
        private void LoadValuesFromFile()
        {
            if (!File.Exists(ConfigPath))
            {
                //if the config file is not where it's supposed to be create a new one.
                throw new Exception("Config file not found: " + ConfigPath);
            }

            //load the xml
            var configXml = XDocument.Load(ConfigPath);

            //get all of the <setting name="..." serializeAs="..."> elements.
            var settingElements = configXml.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName).Elements(SETTING);

            //iterate through, adding them to the dictionary, (checking for nulls, xml no likey nulls)
            //using "String" as default serializeAs...just in case, no real good reason.
            foreach (var element in settingElements)
            {

                var newSetting = new SettingStruct()
                {
                    name = element.Attribute(NAME) == null ? String.Empty : element.Attribute(NAME).Value,
                    serializeAs = element.Attribute(SERIALIZE_AS) == null ? "String" : element.Attribute(SERIALIZE_AS).Value ,
                    value = element.Value ?? String.Empty
                };

                if (newSetting.serializeAs == "Xml")
                {
                    var e = (XElement)element.Nodes().First();
                    newSetting.value = e.LastNode.ToString() ?? String.Empty;
                };

                SettingsDictionary.Add(element.Attribute(NAME).Value, newSetting);
            }
        }

        /// <summary>
        /// Creates an empty user.config file...looks like the one MS creates.  
        /// This could be overkill a simple key/value pairing would probably do.
        /// </summary>
        private void CreateEmptyConfig()
        {
            var doc = new XDocument();
            var declaration = new XDeclaration("1.0", "utf-8", "true");
            var config = new XElement(CONFIG);
            var userSettings = new XElement(APPLICATION_SETTINGS);
            var group = new XElement(typeof(Properties_Settings).FullName);
            userSettings.Add(group);
            config.Add(userSettings);
            doc.Add(config);
            doc.Declaration = declaration;
            doc.Save(ConfigPath);
        }

        /// <summary>
        /// Saves the in memory dictionary to the user config file
        /// </summary>
        private void SaveValuesToFile()
        {
            //load the current xml from the file.
            var import = XDocument.Load(ConfigPath);

            //get the settings group (e.g. <Company.Project.Desktop.Settings>)
            var settingsSection = import.Element(CONFIG).Element(APPLICATION_SETTINGS).Element(typeof(Properties_Settings).FullName);

            //iterate though the dictionary, either updating the value or adding the new setting.
            foreach (var entry in SettingsDictionary)
            {
                var setting = settingsSection.Elements().FirstOrDefault(e => e.Attribute(NAME).Value == entry.Key);
                if (setting == null) //this can happen if a new setting is added via the .settings designer.
                {
                    var newSetting = new XElement(SETTING);
                    newSetting.Add(new XAttribute(NAME, entry.Value.name));
                    newSetting.Add(new XAttribute(SERIALIZE_AS, entry.Value.serializeAs));
                    newSetting.Value = (entry.Value.value ?? String.Empty);
                    settingsSection.Add(newSetting);
                }
                else //update the value if it exists.
                {
                    setting.Value = (entry.Value.value ?? String.Empty);
                }
            }
            import.Save(ConfigPath);
        }

        /// <summary>
        /// The setting key this is returning must set before the settings are used.
        /// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
        /// </summary>
        public virtual string ConfigPath
        {
            get
            {
                var name = new Properties_Settings().GetType().Module.Name + ".config";
                if (System.IO.File.Exists(name)==false)
                {
                    System.Diagnostics.Trace.WriteLine("config file NOT found:" + name);
                }
                System.Diagnostics.Trace.WriteLine("config file found:" + name);

                return name;
               // return Properties.Settings.Default.SettingsKey;
            }

        }

        /// <summary>
        /// In memory storage of the settings values
        /// </summary>
        internal Dictionary<string, SettingStruct> SettingsDictionary { get; set; }

        /// <summary>
        /// Helper struct.
        /// </summary>
        internal struct SettingStruct
        {
            internal string name;
            internal string serializeAs;
            internal string value;
        }

        bool _loaded;
    }
}

Убедитесь, что зависимые проекты включают файл project.dll.config, добавив событие после сборки:

copy $(SolutionDir)Worker\$(OutDir)Worker.dll.config $(TargetDir) /y
person jlo-gmail    schedule 16.12.2016