在Windows窗体应用程序中保存应用程序设置的最佳实践

Fue*_*led 561 c# xml configuration-files application-settings winforms

我想要实现的非常简单:我有一个Windows窗体(.NET 3.5)应用程序,它使用一个路径来读取信息.用户可以使用我提供的选项表单修改此路径.

现在,我想将路径值保存到文件中供以后使用.这将是保存到此文件的众多设置之一.该文件将直接位于应用程序文件夹中.

我理解有三种选择:

  • ConfigurationSettings文件(appname.exe.config)
  • 注册处
  • 自定义XML文件

我读到.NET配置文件没有预见到将值保存回来.至于注册表,我想尽可能远离它.

这是否意味着我应该使用自定义XML文件来保存配置设置?如果是这样,我希望看到代码示例(C#).

我已经看过关于这个问题的其他讨论,但我仍然不清楚.

aku*_*aku 583

如果您使用Visual Studio,那么很容易获得可持久的设置.在Solution Explorer中右键单击项目,选择Properties.选择"设置"选项卡,如果设置不存在,请单击超链接.使用"设置"选项卡创建应用程序设置.Visual Studio创建文件Settings.settingsSettings.Designer.settings包含SettingsApplicationSettingsBase继承 的单例类.您可以从代码中访问此类以读取/写入应用程序设置:

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

此技术适用于控制台,Windows窗体和其他项目类型.

请注意,您需要设置设置的范围属性.如果选择应用程序范围,则Settings.Default.<your property>将是只读的.

  • @Four:我这里有一个.NET 4.0 WinApp项目,我的SomeProperty不是只读的.`Settings.Default.SomeProperty ='value'; Settings.Default.Save();`就像一个魅力.或者是因为我有用户设置? (8认同)
  • 截至目前,使用.NET 3.5,您可以使用Settings.Default.SomeProperty来分配值并获得强大的类型转换.另外,为了节省其他时间(花了一些时间来解决这个问题),您需要键入Properties.Settings.Default,或使用YourProjectNameSpace.Settings添加到文件的顶部.单独的"设置"未定义/找到. (7认同)
  • @Four:当我将设置从User更改为Application-scope并保存文件时,我在生成的代码中看到setter消失了.这也适用于客户端配置文件4.0 ... (4认同)
  • @Four:很棒的链接,虽然你的声明`Settings.Default.Save()`什么都不做是不正确的.正如@aku在答案中所述,app-scope设置是只读的:save对它们无效.使用该自定义[PortableSettingsProvider](http://www.geek-republic.com/2010/11/c-portable-settings-provider/)将用户范围设置保存到位于exe所在的app.config而不是用户的AppData文件夹中的那个.不,通常不是很好,但我在开发过程中使用它从编译到编译使用相同的设置(没有它,它们在每次编译时都会使用新的唯一用户文件夹). (3认同)
  • 如果我有解决方案,这是否适用于整个解决方案或每个项目? (2认同)
  • 默认情况下,`Settings.Default.Save()`方法不执行任何操作.您必须使用不同的提供者.请参阅:http://www.geek-republic.com/2010/11/08/c-portable-settings-provider/ (2认同)
  • 更好/更简单的答案(不,不是反垃圾邮件尝试 - 只是把它放在这里,因为这是这个问题的最流行的版本):http://stackoverflow.com/a/8313682/409856 (2认同)

Tre*_*vor 91

如果您计划保存到与可执行文件相同的目录中的文件,这是一个使用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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你还没有,你需要引用`System.Web.Extensions.dll`. (10认同)
  • 我已经基于这个答案创建了一个完整的库,并进行了许多改进,并在nuget中提供了它:https://github.com/Nucs/JsonSettings (8认同)
  • 不要写入与您的 exe 相同的文件夹,除非您正在开发一个您知道只会安装到 appdata 的应用程序。如果您的 exe 安装到任一程序文件夹,则用户极不可能在其中保存文件。为什么不?事实证明,允许应用程序将文件保存到 dll 和 exe 的存储位置是一个安全漏洞。 (2认同)

Fre*_*els 66

注册表是禁止的.您不确定使用您的应用程序的用户是否有足够的权限写入注册表.

您可以使用该app.config文件来保存应用程序级设置(对于使用您的应用程序的每个用户都是相同的).

我会将用户特定的设置存储在XML文件中,该文件将保存在Isolated StorageSpecialFolder.ApplicationData目录中.

接下来,从.NET 2.0开始,可以将值存储回app.config文件.

  • 注册表是不可移植的 (19认同)
  • @thenonhacker:或者使用Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) (10认同)
  • 但是,如果您需要按登录/用户设置,请使用注册表. (8认同)
  • Registry 的主要缺点是很难将设置导出/复制到其他 PC。但我不同意“您不确定使用您的应用程序的用户是否有足够的权限写入注册表” - 在 HKEY_CURRENT_USER 中,您始终有权写入。它可以被拒绝,但当前用户也可能无法访问文件系统(所有可能的 TEMP 文件夹等)。 (4认同)
  • @thenonhacker - 注册表不是必需的,不应用于存储应用程序设置 - 永远。System.Environment.SpecialFolder.LocalApplicationData 是每个用户的本地文件夹。.ApplicationData 是每个用户的漫游文件夹。请参阅 http://msdn.microsoft.com/en-us/library/system.environment.specialfolder.aspx (3认同)
  • 可以写入用户注册表(许多程序在那里写入信息,用户权限永远不会成为问题).使用注册表而不是使用设置的优点是,如果您有多个应用程序共享同一个文件夹(例如,安装程序和应用程序),它们将不会共享相同的设置. (3认同)

Han*_*ant 20

ApplicationSettings类不支持设置保存到app.config文件.这非常依赖于设计,使用正确安全的用户帐户(想想Vista UAC)运行的应用程序没有对程序安装文件夹的写入权限.

您可以与ConfigurationManager班级对抗系统.但是,简单的解决方法是进入"设置"设计器并将设置的范围更改为"用户".如果这会导致困难(例如,设置与每个用户相关),则应将"选项"功能放在单独的程序中,以便可以请求权限提升提示.或放弃使用设置.

  • 单独的程序需要清单来要求提升.谷歌'asinvoker要求管理员'找到正确的语法.编辑user.config是不实际的,也不是必需的. (2认同)

Mat*_*att 18

是的,可以保存配置 - 但这很大程度上取决于您选择的保存方式。让我描述一下技术差异,以便您了解您拥有的选项:

首先,您需要区分是否要在您的(也称为Visual Studio 中)文件中使用applicationSettingsAppSettings - 存在根本差异,请参见此处*.exe.configApp.config

两者都提供了不同的保存更改的方式:

  • 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)。正如Hans Passant在他的回答中提到的,这是因为用户通常对程序文件具有有限的权限,并且在不调用 UAC 提示符的情况下无法对其进行写入。缺点是如果您将来要添加配置密钥,则需要将它们与每个用户配置文件同步。

但还有其他一些替代选择:

  • 由于 .NET Core(以及 .NET 5 和 6),第三个选项appsettings.json使用 Microsoft 配置抽象的文件(也是secrets.json存储在用户配置文件中而不是程序集目录中的文件)。但通常 WinForms 不使用它,所以我提及它只是为了完整性。但是,这里有一些如何读取写入值的参考。或者,您可以使用Newtonsoft JSON 来读取和写入文件appsettings.json,但不限于此:您还可以使用该方法创建自己的 json 文件。

  • 正如问题中提到的,有第四种选择:如果将配置文件视为XML文档,则可以使用System.Xml.Linq.XDocument类加载、修改和保存它。不需要使用自定义的XML文件,可以读取现有的配置文件;对于查询元素,您甚至可以使用 Linq 查询。我在这里给出了一个例子,请查看GetApplicationSetting答案中的函数。

  • 第五个选项是将设置存储在注册表中。此处描述了如何执行此操作。

  • 最后,还有第六个选项:您可以将值存储在环境中(系统环境或您帐户的环境)。在 Windows 设置(Windows 菜单中的齿轮)中,在搜索栏中输入“环境”,然后在其中添加或编辑它们。要阅读它们,请使用
    var myValue = Environment.GetEnvironmentVariable("MyVariable");.
    请注意,您的应用程序通常需要重新启动才能获取更新的环境设置。

如果您需要加密来保护您的值,请查看答案。它描述了如何使用 Microsoft 的 DPAPI 来存储加密的值。

如果您想支持自己的文件(无论是 XML 还是 JSON),了解运行的程序集的目录可能会很有用:

var assemblyDLL = System.Reflection.Assembly.GetExecutingAssembly();
var assemblyDirectory = System.IO.Path.GetDirectoryName(assemblyDLL.Location);
Run Code Online (Sandbox Code Playgroud)

您可以使用assemblyDirectory作为存储文件的基本目录。


ana*_*kic 17

我想分享一个我为此而建的图书馆.这是一个很小的库,但对.settings文件有很大改进(恕我直言).

该库名为Jot(GitHub),这是我写的关于它的旧代码项目文章.

以下是您如何使用它来跟踪窗口的大小和位置:

public MainWindow()
{
    InitializeComponent();

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

与.settings文件相比的好处:代码要少得多,而且容易出错,因为你只需要提一次每个属性.

使用设置文件,您需要提及五次每个属性:一次是在显式创建属性时,另外四次是在代码中来回复制值.

存储,序列化等是完全可配置的.当目标对象由IOC容器创建时,您可以[挂钩] []以便它自动将跟踪应用于它所解析的所有对象,这样您需要做的就是使属性持久化是一个[Trackable]属性在上面.

它是高度可配置的,您可以配置: - 数据持久化并应用于全局或每个跟踪对象 - 如何序列化 - 存储位置(例如文件,数据库,在线,隔离存储,注册表) - 可以取消应用/持久化的规则财产的数据

相信我,图书馆是一流的!


小智 16

registry/configurationSettings/XML参数似乎仍然非常活跃.随着技术的进步,我已经全部使用了它们,但我最喜欢的是基于Threed的系统隔离存储相结合.

以下示例允许将名为properties的对象存储到隔离存储中的文件中.如:

AppSettings.Save(myobject, "Prop1,Prop2", "myFile.jsn");
Run Code Online (Sandbox Code Playgroud)

可以使用以下方法恢复属性

AppSettings.Load(myobject, "myFile.jsn");
Run Code Online (Sandbox Code Playgroud)

这只是一个样本,不是最佳实践的暗示.

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) { }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Die*_*ken 12

一种简单的方法是使用配置数据对象,将其保存为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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

用于保存和加载的经理类:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以创建一个实例并在表单的加载和关闭事件中使用:

    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();
    }
Run Code Online (Sandbox Code Playgroud)

生成的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>
Run Code Online (Sandbox Code Playgroud)


gat*_*pia 7

我不喜欢使用web.config或提议的解决方案app.config.尝试阅读自己的XML.看看XML设置文件 - 不再需要web.config.


kit*_*ite 5

除了使用自定义XML文件以外,我们还可以使用其他更友好的文件格式:JSON或YAML文件。

  • 如果您使用.NET 4.0 dynamic,那么此库确实非常易于使用(序列化,反序列化,嵌套对象支持和按需排序输出顺序+将多个设置合并为一个)JsonConfig(用法等效于ApplicationSettingsBase)
  • 对于.NET YAML配置库...我还没有找到一个像JsonConfig一样易于使用的库

您可以将设置文件存储在此处列出的多个特殊文件夹(针对所有用户和每个用户)Environment.SpecialFolder枚举和多个文件(默认只读,针对每个角色,针对每个用户等)

如果选择使用多个设置,则可以合并这些设置:例如,合并默认+ BasicUser + AdminUser的设置。您可以使用自己的规则:最后一个覆盖值,依此类推。


Tur*_*ali 5

“这是否意味着我应该使用自定义 XML 文件来保存配置设置?” 不,不一定。我们使用 SharpConfig 进行此类操作。

例如,如果一个配置文件是这样的

[General]
# a comment
SomeString = Hello World!
SomeInteger = 10 # an inline comment
Run Code Online (Sandbox Code Playgroud)

我们可以检索这样的值

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

string someString = section["SomeString"].StringValue;
int someInteger = section["SomeInteger"].IntValue;
Run Code Online (Sandbox Code Playgroud)

它与 .NET 2.0 及更高版本兼容。我们可以动态创建配置文件,稍后我们可以保存它。

来源:http : //sharpconfig.net/
GitHub:https : //github.com/cemdervis/SharpConfig


归档时间:

查看次数:

430039 次

最近记录:

6 年,7 月 前