.NET 应用程序中存储用户设置的最佳方法是什么?

65
我有一个.NET 2.0的Windows Forms应用程序。根据Windows指南,哪里是存储用户设置的最佳位置?
一些人指向Application.LocalUserAppDataPath。然而,这会创建一个文件夹结构,如下所示:
C:\Documents and Settings\user_name\Local Settings\Application Data\company_name\product_name\product_version\
如果我发布应用程序的版本1并在那里存储XML文件,然后发布版本2,那么该文件夹会更改为不同的文件夹,对吧?我希望有一个单独的文件夹,每个用户都可以存储设置,而不考虑应用程序版本。

我认为这篇文章涵盖了解决方案。 - Jorge Córdoba
从Jorges评论中更新的链接:https://blog.kowalczyk.info/article/10b/Getting-user-specific-application-data-directory.html - tm1
8个回答

90

我喜欢使用内置的应用程序设置。这样,您就可以在设计时使用设置设计器提供内置支持,或在运行时使用:

// read setting
string setting1 = (string)Settings.Default["MySetting1"];
// save setting
Settings.Default["MySetting2"] = "My Setting Value";

// you can force a save with
Properties.Settings.Default.Save();

它确实以类似的文件夹结构(路径中包含版本号)存储设置。但是,只需简单调用:

Properties.Settings.Default.Upgrade(); 
该应用程序将提取所有先前版本的设置以进行保存。

4
关于 Settings.Default 的唯一让人烦恼的事情是,需要事先手动创建它们,而 Cocoa 中的 NSUserDefaults 如果设置不存在则返回 nil。 - Ali
3
这是哪个命名空间?我的.NET 4 winforms应用程序似乎无法访问设置对象。 - NickG
2
Properties.Settings.Default - Trent Seed
1
请在您的答案中添加 Properties.Settings.Default.Save(); 以节省用户的时间。 - alexkovelsky
这肯定很好,但如果我要保存类似任务列表中的任务,我喜欢为每个任务创建一个带有其值的文件。事先设置设置使得使用存储列表的设置变得很烦人。我总是为此而苦恼,这就是为什么我对这种事情使用基于文件的方法。我也可以只创建一个非常长的字符串并将其保存为设置,但是为此我必须使用一些分隔符,这总是一个问题,因为用户可能在输入中使用它(如果您不防止它)。 - martin_jaehn
显示剩余4条评论

9
.NET应用程序有一个内置的设置机制,非常易于使用。但是,在我看来,它的问题在于它将这些设置存储到一个相当晦涩的目录中,最终用户将无法找到它。此外,仅从调试切换到发布构建就会更改此目录的位置,这意味着在另一个配置中保存的任何设置都将丢失。
因此,出于这些和其他原因,我想出了自己的Windows窗体设置代码。它不如.NET提供的那个那么流畅,但更加灵活,我一直在使用它。

5

或者将您的设置写入xml文件并使用隔离存储保存。根据您使用的存储方式,它会保存在应用程序数据文件夹中。您还可以选择启用漫游的存储方式,这意味着当用户登录到另一台计算机时,设置会随之移动。


2
过去我使用过的一种方法是创建一个设置类,并使用XML序列化将其写入文件系统。你可以通过创建设置对象集合并对其进行序列化,来扩展这个概念。这样,你就可以在一个地方拥有所有用户的所有设置,而不必担心管理文件系统。
在任何人责备我部分重新发明轮子之前,请允许我说几句话。首先,只需几行代码即可序列化和写入文件。其次,如果你有一个包含你的设置的对象,你就不必在加载应用程序时多次调用appSettings对象。最后,非常容易添加表示应用程序状态的项目,从而允许你在下次加载应用程序时恢复长时间运行的任务。

如果你愿意构建自定义XML序列化,为什么不将其序列化为单个应用程序设置呢?这样您就可以使用应用设置基础结构。创建一个可序列化的类或类型转换器,然后使用应用程序设置对话框导入类。 - Don Kirkby

1

我尝试了一些方法将我的设置存储到简单的文本文件中,我发现最好的方法是:

文件存储在应用程序文件夹中,使用时,settings.txt: (在设置文件中可以添加注释,尝试使用//comment)

//获取设置值

Settings.Get("name", "Ivan");

//设置设置值

Settings.Set("name", "John");

使用中:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

//您可以使用部分名称存储,以便使用只需添加名称部分 Set(section_name,name,value) 和 Get(section_name,name,value)

public static class Settings
{
    private static string SECTION =  typeof(Settings).Namespace;//"SETTINGS";
    private static string settingsPath = Application.StartupPath.ToString() + "\\settings.txt";
    [DllImport("kernel32")]
    private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
    [DllImport("kernel32")]
    private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
    public static String GetString(String name)
    {
        StringBuilder temp = new StringBuilder(255);
        int i = GetPrivateProfileString(SECTION,name,"",temp,255,settingsPath);
        return temp.ToString();
    }
    public static String Get(String name, String defVal)
    {
        return Get(SECTION,name,defVal);
    }
    public static String Get(string _SECTION, String name, String defVal)
    {
        StringBuilder temp = new StringBuilder(255);
        int i = GetPrivateProfileString(_SECTION, name, "", temp, 255, settingsPath);
        return temp.ToString();
    }
    public static Boolean Get(String name, Boolean defVal)
    {
        return Get(SECTION, name, defVal);
    }
    public static Boolean Get(string _SECTION, String name, Boolean defVal)
    {
        StringBuilder temp = new StringBuilder(255);
        int i = GetPrivateProfileString(_SECTION,name,"",temp,255,settingsPath);
        bool retval=false;
        if (bool.TryParse(temp.ToString(),out retval))
        {
            return retval;
        } else
        {
            return retval;
        }
    }
    public static int Get(String name, int defVal)
    {
        return Get(SECTION, name, defVal);
    }
    public static int Get(string _SECTION, String name, int defVal)
    {
        StringBuilder temp = new StringBuilder(255);
        int i = GetPrivateProfileString(SECTION,name,"",temp,255,settingsPath);
        int retval=0;
        if (int.TryParse(temp.ToString(),out retval))
        {
            return retval;
        } else
        {
            return retval;
        }
    }
    public static void Set(String name, String val)
    {
        Set(SECTION, name,val);
    }
    public static void Set(string _SECTION, String name, String val)
    {
        WritePrivateProfileString(_SECTION, name, val, settingsPath);
    }
    public static void Set(String name, Boolean val)
    {
        Set(SECTION, name, val);
    }
    public static void Set(string _SECTION, String name, Boolean val)
    {
        WritePrivateProfileString(_SECTION, name, val.ToString(), settingsPath);
    }
    public static void Set(String name, int val)
    {
        Set(SECTION, name, val);
    }
    public static void Set(string _SECTION,String name, int val)
    {
        WritePrivateProfileString(SECTION, name, val.ToString(), settingsPath);
    }
}

0

设置是标准的键值对(字符串-字符串)。如果有帮助的话,我可以将它们包装在XML文件中。

我更愿意使用文件系统而不是注册表。这似乎更容易维护。在支持场景中,如果用户需要手动打开/更改设置,则如果它在文件系统中,那将更容易。


0

我会按照你发布的文件夹列表进行操作,除了产品版本。你不希望在发布更新后重置设置。

我实际上正在摆脱注册表中的用户设置,因为调试/占用空间因素。目前,我只在注册表中存储一些基本设置(窗口大小、位置、数据文件的版本),如果更新出现问题或用户失去第二个监视器并且应用程序正在打开该监视器,则会遇到更多问题。其中一些人足够精通regedit,但对于其他人,他们必须重新安装,这很快,但我认为他们会抱怨一下。使用基于文件的版本,我所要做的就是让他们在记事本中打开一个XML文件并进行快速调整。

此外,我希望使我的应用程序可以从USB闪存驱动器运行,并且将设置与文件绑定似乎更加友好。我相信我可以编写一些代码来检查/清理注册表,但我认为我们大多数人已经厌倦了似乎吞噬我们机器的注册表杂乱无章。

我知道这样做存在一些安全方面的权衡,但我所处理的数据都不那么关键,而且由于应用程序的大小,我也没有遇到任何性能问题。


0

独立存储主要用于使用ClickOnce分发的应用程序,并在安全沙箱中运行。基本路径由您决定,您无法在代码中推断它。路径将类似于“\LocalSettings\ApplicationData\IsolatedStorage\ejwnwe.302\kfiwemqi.owx\url.asdaiojwejoieajae....”,并非那么友好。您的存储空间也有限。

Ryan Farley说得对


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接