Лучшая практика сохранения настроек приложения в приложении Windows Forms

То, чего я хочу достичь, очень просто: у меня есть приложение Windows Forms (.NET 3.5), которое использует путь для чтения информации. Этот путь может быть изменен пользователем с помощью формы параметров, которую я предоставляю.

Теперь я хочу сохранить значение пути в файл для дальнейшего использования. Это будет одна из многих настроек, сохраненных в этом файле. Этот файл будет находиться прямо в папке приложения.

Я понимаю, что доступны три варианта:

  • Файл настроек конфигурации (appname.exe.config)
  • реестр
  • Пользовательский файл XML

Я прочитал, что файл конфигурации.NET не предусмотрен для сохранения значений обратно в него. Что касается реестра, я хотел бы получить как можно дальше от него.

Означает ли это, что я должен использовать пользовательский XML-файл для сохранения настроек конфигурации? Если это так, я хотел бы увидеть пример кода (C#).

Я видел другие дискуссии на эту тему, но мне все еще не ясно.

13 ответов

Решение

Если вы работаете с Visual Studio, то получить постоянные настройки довольно просто. Щелкните правой кнопкой мыши проект в обозревателе решений, выберите "Свойства". Выберите вкладку "Настройки", нажмите на гиперссылку, если настройки не существуют. Используйте вкладку "Настройки" для создания настроек приложения. Visual Studio создает файлы Settings.settings а также Settings.Designer.settings которые содержат класс синглтона Settings наследуется от ApplicationSettingsBase. Вы можете получить доступ к этому классу из своего кода для чтения / записи настроек приложения:

Properties.Settings.Default["SomeProperty"] = "Some Value";
Properties.Settings.Default.Save(); // Saves settings in application configuration file

Этот метод применим как для консоли, Windows Forms и других типов проектов.

Обратите внимание, что вам нужно установить свойство scope ваших настроек. Если вы выберите Область применения, тогда Settings.Default.<Ваше свойство> будет доступно только для чтения.

Если вы планируете сохранить файл в том же каталоге, что и ваш исполняемый файл, вот хорошее решение, которое использует формат JSON:

using System;
using System.IO;
using System.Web.Script.Serialization;

namespace MiscConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            MySettings settings = MySettings.Load();
            Console.WriteLine("Current value of 'myInteger': " + settings.myInteger);
            Console.WriteLine("Incrementing 'myInteger'...");
            settings.myInteger++;
            Console.WriteLine("Saving settings...");
            settings.Save();
            Console.WriteLine("Done.");
            Console.ReadKey();
        }

        class MySettings : AppSettings<MySettings>
        {
            public string myString = "Hello World";
            public int myInteger = 1;
        }
    }

    public class AppSettings<T> where T : new()
    {
        private const string DEFAULT_FILENAME = "settings.json";

        public void Save(string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(this));
        }

        public static void Save(T pSettings, string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(pSettings));
        }

        public static T Load(string fileName = DEFAULT_FILENAME)
        {
            T t = new T();
            if(File.Exists(fileName))
                t = (new JavaScriptSerializer()).Deserialize<T>(File.ReadAllText(fileName));
            return t;
        }
    }
}

Реестр запрещён. Вы не уверены, имеет ли пользователь, который использует ваше приложение, достаточные права для записи в реестр.

Вы можете использовать app.config файл для сохранения настроек уровня приложения (которые одинаковы для каждого пользователя, использующего ваше приложение).

Я бы сохранял пользовательские настройки в файле XML, который сохранялся бы в изолированном хранилище или в каталоге SpecialFolder.ApplicationData.

Кроме того, начиная с.NET 2.0 можно сохранять значения обратно в app.config файл.

ApplicationSettings Класс не поддерживает сохранение настроек в файл app.config. Это очень задумано, приложения, которые запускаются с должным образом защищенной учетной записью пользователя (например, Vista UAC), не имеют доступа для записи в папку установки программы.

Вы можете бороться с системой с ConfigurationManager учебный класс. Но тривиальный обходной путь заключается в том, чтобы зайти в конструктор настроек и изменить область настройки на "Пользователь". Если это вызывает затруднения (скажем, настройка важна для каждого пользователя), вам следует поместить функцию "Параметры" в отдельную программу, чтобы вы могли запросить запрос на повышение привилегий. Или отказаться от использования настроек.

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

Библиотека называется Jot (GitHub), вот старая статья The Code Project, которую я написал об этом.

Вот как вы можете использовать его, чтобы отслеживать размер и местоположение окна:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

Преимущество по сравнению с файлами.settings: кода значительно меньше, и он гораздо менее подвержен ошибкам, поскольку вам нужно упомянуть каждое свойство только один раз.

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

Хранение, сериализация и т. Д. Полностью настраиваются. При использовании инверсии управления вы можете подключить его так, чтобы он автоматически применял отслеживание ко всем объектам, которые он разрешает, так что все, что вам нужно сделать, чтобы сделать свойство постоянным, - это присвоить ему атрибут [Trackable].

Я пишу все это, потому что я думаю, что библиотека на высшем уровне, и я хотел бы популяризировать это:)

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

Во-первых, вам нужно различать, хотите ли вы использовать applicationSettings или AppSettings в своем*.exe.config(он же App.configв Visual Studio) - принципиальные отличия описаны здесь.

Оба предоставляют разные способы сохранения изменений:

  • В AppSettings позволяют читать и записывать непосредственно в конфигурационный файл (черезconfig.Save(ConfigurationSaveMode.Modified);, где config определяется как config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);).

  • В applicationSettings позволяет читать, но если вы напишете изменения (черезProperties.Settings.Default.Save();) он будет написан для каждого пользователя, сохранен в специальном месте (например, C:\Documents and Settings\USERID\Local Settings\Application Data\FIRMNAME\WindowsFormsTestApplicati_Url_tdq2oylz33rzq00sxhvxucu5edw2oghw\1.0.0.0). Как упомянул Ханс Пассан в своем ответе, это связано с тем, что пользователь обычно имеет ограниченные права на программные файлы и не может писать в них, не вызывая приглашение UAC. Недостатком является то, что если вы добавляете конфигурационные ключи в будущем, вам необходимо синхронизировать их с каждым профилем пользователя.

Примечание. Как упоминалось в вопросе, существует третий вариант: если вы обрабатываете файл конфигурации как XML-документ, вы можете загружать, изменять и сохранять его с помощью System.Xml.Linq.XDocument учебный класс. Не обязательно использовать собственный XML-файл, вы можете прочитать существующий файл конфигурации; для запроса элементов вы даже можете использовать запросы Linq. Я привел здесь пример, посмотрите функциюGetApplicationSetting там в ответ.

Если вам требуется шифрование для защиты ваших ценностей, посмотрите этот ответ.

Аргумент registry/configurationSettings/XML по-прежнему выглядит очень активным. Я использовал их все по мере развития технологии, но мой любимый основан на системе Threed в сочетании с изолированным хранилищем.

Следующий пример позволяет хранить объекты с именами свойств в файле в изолированном хранилище. Такие как:

AppSettings.Save(myobject, "Prop1,Prop2", "myFile.jsn");

Свойства могут быть восстановлены с использованием:

AppSettings.Load(myobject, "myFile.jsn");

Это просто образец, не наводящий на мысль о передовой практике.

internal static class AppSettings
{
    internal static void Save(object src, string targ, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = src.GetType();

        string[] paramList = targ.Split(new char[] { ',' });
        foreach (string paramName in paramList)
            items.Add(paramName, type.GetProperty(paramName.Trim()).GetValue(src, null));

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify.
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Create, storage))
            using (StreamWriter writer = new StreamWriter(stream))
            {
                writer.Write((new JavaScriptSerializer()).Serialize(items));
            }

        }
        catch (Exception) { }   // If fails - just don't use preferences
    }

    internal static void Load(object tar, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = tar.GetType();

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Open, storage))
            using (StreamReader reader = new StreamReader(stream))
            {
                items = (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(reader.ReadToEnd());
            }
        }
        catch (Exception) { return; }   // If fails - just don't use preferences.

        foreach (KeyValuePair<string, object> obj in items)
        {
            try
            {
                tar.GetType().GetProperty(obj.Key).SetValue(tar, obj.Value, null);
            }
            catch (Exception) { }
        }
    }
}

Простой способ - использовать объект данных конфигурации, сохранить его в виде файла XML с именем приложения в локальной папке и при запуске прочитать его обратно.

Вот пример для хранения позиции и размера формы.

Объект данных конфигурации строго типизирован и прост в использовании:

[Serializable()]
public class CConfigDO
{
    private System.Drawing.Point m_oStartPos;
    private System.Drawing.Size m_oStartSize;

    public System.Drawing.Point StartPos
    {
        get { return m_oStartPos; }
        set { m_oStartPos = value; }
    }

    public System.Drawing.Size StartSize
    {
        get { return m_oStartSize; }
        set { m_oStartSize = value; }
    }
}

Класс менеджера для сохранения и загрузки:

public class CConfigMng
{
    private string m_sConfigFileName = System.IO.Path.GetFileNameWithoutExtension(System.Windows.Forms.Application.ExecutablePath) + ".xml";
    private CConfigDO m_oConfig = new CConfigDO();

    public CConfigDO Config
    {
        get { return m_oConfig; }
        set { m_oConfig = value; }
    }

    // Load configuration file
    public void LoadConfig()
    {
        if (System.IO.File.Exists(m_sConfigFileName))
        {
            System.IO.StreamReader srReader = System.IO.File.OpenText(m_sConfigFileName);
            Type tType = m_oConfig.GetType();
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            object oData = xsSerializer.Deserialize(srReader);
            m_oConfig = (CConfigDO)oData;
            srReader.Close();
        }
    }

    // Save configuration file
    public void SaveConfig()
    {
        System.IO.StreamWriter swWriter = System.IO.File.CreateText(m_sConfigFileName);
        Type tType = m_oConfig.GetType();
        if (tType.IsSerializable)
        {
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            xsSerializer.Serialize(swWriter, m_oConfig);
            swWriter.Close();
        }
    }
}

Теперь вы можете создать экземпляр и использовать в событиях загрузки и закрытия формы:

    private CConfigMng oConfigMng = new CConfigMng();

    private void Form1_Load(object sender, EventArgs e)
    {
        // Load configuration
        oConfigMng.LoadConfig();
        if (oConfigMng.Config.StartPos.X != 0 || oConfigMng.Config.StartPos.Y != 0)
        {
            Location = oConfigMng.Config.StartPos;
            Size = oConfigMng.Config.StartSize;
        }
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        // Save configuration
        oConfigMng.Config.StartPos = Location;
        oConfigMng.Config.StartSize = Size;
        oConfigMng.SaveConfig();
    }

И созданный XML-файл также доступен для чтения:

<?xml version="1.0" encoding="utf-8"?>
<CConfigDO xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <StartPos>
    <X>70</X>
    <Y>278</Y>
  </StartPos>
  <StartSize>
    <Width>253</Width>
    <Height>229</Height>
  </StartSize>
</CConfigDO>

"Значит ли это, что я должен использовать собственный XML-файл для сохранения настроек конфигурации?" Нет, не обязательно Мы используем SharpConfig для таких операций.

Например, если файл конфигурации такой

[General]
# a comment
SomeString = Hello World!
SomeInteger = 10 # an inline comment

Мы можем получить такие значения

var config = Configuration.LoadFromFile("sample.cfg");
var section = config["General"];

string someString = section["SomeString"].StringValue;
int someInteger = section["SomeInteger"].IntValue;

Он совместим с.Net 2.0 и выше. Мы можем создавать файлы конфигурации на лету и сохранить их позже. Источник: http://sharpconfig.net/ Github: https://github.com/cemdervis/SharpConfig

Я надеюсь, что это помогает.

Мне не нравится предложенное решение использования web.config или же app.config, Попробуйте прочитать свой собственный XML. Взгляните на файлы настроек XML - больше нет web.config.

Другие варианты, вместо использования пользовательского файла XML, мы можем использовать более удобный для пользователя формат файла: файл JSON или YAML.

  • Если вы используете динамический.NET 4.0, эта библиотека действительно проста в использовании (сериализация, десериализация, поддержка вложенных объектов и упорядочение вывода по вашему желанию + объединение нескольких настроек в один) JsonConfig (использование эквивалентно ApplicationSettingsBase)
  • Для библиотеки конфигурации.NET YAML... Я не нашел такой простой в использовании, как JsonConfig

Вы можете сохранить файл настроек в нескольких специальных папках (для всех пользователей и для каждого пользователя), как указано здесь Environment.SpecialFolder Enumeration и несколько файлов (по умолчанию только для чтения, для каждой роли, для пользователя и т. Д.)

Если вы решите использовать несколько настроек, вы можете объединить эти настройки: Например, объединение настроек по умолчанию + BasicUser + AdminUser. Вы можете использовать свои собственные правила: последнее переопределяет значение и т. Д.

Насколько я могу судить, .NET поддерживает сохранение настроек с помощью встроенного средства настройки приложения:

Функция "Параметры приложения" Windows Forms позволяет легко создавать, хранить и поддерживать пользовательские настройки приложений и пользователей на клиентском компьютере. В настройках приложения Windows Forms вы можете хранить не только данные приложения, такие как строки подключения к базе данных, но также данные, относящиеся к конкретному пользователю, такие как настройки приложения пользователя. Используя Visual Studio или пользовательский управляемый код, вы можете создавать новые настройки, считывать их и записывать на диск, связывать их со свойствами в формах и проверять данные настроек перед загрузкой и сохранением. - http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx

Иногда вы хотите избавиться от этих настроек, хранящихся в традиционном файле web.config или app.config. Вы хотите более детальный контроль над развертыванием записей настроек и отдельным дизайном данных. Или необходимо включить добавление новых записей во время выполнения.

Я могу представить два хороших варианта:

  • Сильно типизированная версия и
  • Объектно-ориентированная версия.

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

Преимущество объектно-ориентированной версии заключается в том, что новые параметры могут быть добавлены во время выполнения. Но у вас нет строго типизированных имен и значений. Нужно быть осторожным со строковыми идентификаторами. Должен знать тип данных, сохраненный ранее при получении значения.

Вы можете найти код обеих полнофункциональных реализаций ЗДЕСЬ.

public static class SettingsExtensions
{
    public static bool TryGetValue<T>(this Settings settings, string key, out T value)
    {
        if (settings.Properties[key] != null)
        {
            value = (T) settings[key];
            return true;
        }

        value = default(T);
        return false;
    }

    public static bool ContainsKey(this Settings settings, string key)
    {
        return settings.Properties[key] != null;
    }

    public static void SetValue<T>(this Settings settings, string key, T value)
    {
        if (settings.Properties[key] == null)
        {
            var p = new SettingsProperty(key)
            {
                PropertyType = typeof(T),
                Provider = settings.Providers["LocalFileSettingsProvider"],
                SerializeAs = SettingsSerializeAs.Xml
            };
            p.Attributes.Add(typeof(UserScopedSettingAttribute), new UserScopedSettingAttribute());
            var v = new SettingsPropertyValue(p);
            settings.Properties.Add(p);
            settings.Reload();
        }
        settings[key] = value;
        settings.Save();
    }
}
Другие вопросы по тегам