我想要实现的目标非常简单:我有一个使用路径读取信息的 Windows 窗体 (.NET 3.5) 应用程序。用户可以使用我提供的选项表单来修改此路径。
现在,我想将路径值保存到文件中以备后用。这将是保存到此文件的众多设置之一。该文件将直接位于应用程序文件夹中。
我了解三个选项可用:
配置设置文件 (appname.exe.config)
登记处
自定义 XML 文件
我读到 .NET 配置文件无法将值保存回它。至于注册表,我想离它越远越好。
这是否意味着我应该使用自定义 XML 文件来保存配置设置?
如果是这样,我想看看那个(C#)的代码示例。
我看过其他关于这个主题的讨论,但我仍然不清楚。
如果您使用 Visual Studio,那么很容易获得可持久化的设置。右键单击解决方案资源管理器中的项目,然后选择属性。如果设置不存在,请选择“设置”选项卡并单击超链接。
使用设置选项卡创建应用程序设置。 Visual Studio 创建包含从 ApplicationSettingsBase 继承的单例类 Settings
的文件 Settings.settings
和 Settings.Designer.settings
。您可以从您的代码访问此类以读取/写入应用程序设置:
Properties.Settings.Default["SomeProperty"] = "Some Value";
Properties.Settings.Default.Save(); // Saves settings in application configuration file
此技术适用于控制台、Windows 窗体和其他项目类型。
请注意,您需要设置设置的范围属性。如果您选择应用程序范围,则 Settings.Default.
参考:How To: Write User Settings at Run Time with C# - Microsoft Docs
如果您打算保存到与可执行文件位于同一目录中的文件,这里有一个使用 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;
}
}
}
DEFAULT_FILENAME
,只需调用settings.Save(theFileToSaveTo)
;由于全部大写,DEFAULT_FILENAME
应该是一个常量。如果您想要一个读写属性,请创建一个并让构造函数将其设置为 DEFAULT_FILENAME
。然后将默认参数值设为 null
,对此进行测试并将您的属性用作默认值。它是多一点打字,但给你一个更标准的界面。
System.Web.Extensions.dll
。
注册表是不行的。您不确定使用您的应用程序的用户是否有足够的权限写入注册表。
您可以使用 app.config
文件保存应用程序级设置(对于使用您的应用程序的每个用户都是相同的)。
我会将用户特定的设置存储在一个 XML 文件中,该文件将保存在 Isolated Storage 或 SpecialFolder.ApplicationData 目录中。
除此之外,从 .NET 2.0 开始,可以将值存储回 app.config
文件。
ApplicationSettings
类不支持将设置保存到 app.config 文件。这在很大程度上是设计使然。使用适当保护的用户帐户(想想 Vista UAC)运行的应用程序没有对程序安装文件夹的写入权限。
您可以使用 ConfigurationManager
类来对抗系统。但简单的解决方法是进入设置设计器并将设置的范围更改为用户。如果这导致困难(例如,该设置与每个用户相关),您应该将您的选项功能放在一个单独的程序中,以便您可以请求权限提升提示。或者放弃使用设置。
registry/configurationSettings/XML 参数似乎仍然非常活跃。随着技术的进步,我已经全部使用了它们,但我最喜欢的是基于 Threed's system 和 Isolated Storage 的组合。
以下示例允许将名为属性的对象存储到独立存储中的文件中。如:
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) { }
}
}
}
我想分享一个为此构建的库。这是一个很小的库,但对 .settings 文件有很大的改进(恕我直言)。
该库称为 Jot (GitHub)。这是我写的旧The Code Project article。
以下是您如何使用它来跟踪窗口的大小和位置:
public MainWindow()
{
InitializeComponent();
_stateTracker.Configure(this)
.IdentifyAs("MyMainWindow")
.AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
.RegisterPersistTrigger(nameof(Closed))
.Apply();
}
与 .settings 文件相比的好处:代码少得多,而且出错的可能性要小得多,因为您只需要提及每个属性一次。
对于设置文件,您需要提及每个属性五次:一次是在显式创建属性时,另外四次是在代码中来回复制值。
存储、序列化等是完全可配置的。当目标对象由 IoC 容器创建时,您可以 [hook it][] 以便它自动将跟踪应用到它解析的所有对象,因此您需要做的就是使属性持久化Trackable] 属性就可以了。
它是高度可配置的,您可以配置: - 何时全局或为每个跟踪对象持久化和应用数据 - 如何序列化 - 存储位置(例如文件、数据库、在线、隔离存储、注册表) - 可以取消应用的规则/为属性持久化数据
相信我,图书馆是一流的!
一种简单的方法是使用配置数据对象,将其保存为 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>
c:\program files\my application
文件夹,因此保存设置会引发错误。我正在考虑将 xml 文件保存在 AppData 中,但我只是想知道是否有解决此问题的明显方法,因为这种方法似乎对您有用。
是的,可以保存配置 - 但这在很大程度上取决于您选择的方式。让我描述一下技术差异,以便您了解您拥有的选项:
首先,您需要区分是要在您的 *.exe.config
(在 Visual Studio 中也称为 App.config
)文件中使用 applicationSettings 还是 AppSettings - 有 根本区别, being described here。
两者都提供了不同的保存更改的方式:
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 在他的回答中提到的,这是因为用户通常对 Program Files 具有受限的权限,并且在不调用 UAC 提示的情况下无法对其进行写入。一个缺点是,如果您将来要添加配置密钥,则需要将它们与每个用户配置文件同步。
但是还有其他几个替代选项:
由于 .NET Core(以及 .NET 5 和 6),第三个选项是使用 Microsoft 配置抽象的 appsettings.json 文件(以及存储在您的用户配置文件而不是程序集目录中的 secrets.json 文件)。但通常 WinForms 不使用它,所以我提到它只是为了完整性。但是,这里有一些如何读取和写入值的参考。或者,您可以使用 Newtonsoft JSON 读取和写入 appsettings.json 文件,但不限于此:您还可以使用该方法创建自己的 json 文件。
正如问题中提到的,还有第 4 个选项:如果您将配置文件视为 XML 文档,则可以使用 System.Xml.Linq.XDocument 类加载、修改和保存它。不需要使用自定义的 XML 文件,可以读取已有的配置文件;对于查询元素,您甚至可以使用 Linq 查询。我在这里给出了一个例子,在答案中查看函数 GetApplicationSetting 。
第五个选项是将设置存储在注册表中。此处描述了如何做到这一点。
最后同样重要的是,还有第 6 个选项:您可以将值存储在环境中(系统环境或帐户环境)。在 Windows 设置(Windows 菜单中的齿轮)中,在搜索栏中输入“环境”并在那里添加或编辑它们。要阅读它们,请使用 var myValue = Environment.GetEnvironmentVariable("MyVariable");。请注意,您的应用程序通常需要重新启动才能获得更新的环境设置。
如果您需要加密来保护您的值,请查看 this 答案。它描述了如何使用 Microsoft 的 DPAPI 来存储加密的值。
如果您想支持自己的文件,无论是 XML 还是 JSON,了解运行的程序集的目录可能会很有用:
var assemblyDLL = System.Reflection.Assembly.GetExecutingAssembly();
var assemblyDirectory = System.IO.Path.GetDirectoryName(assemblyDLL.Location);
您可以使用 assemblyDirectory
作为基本目录来存储您的文件。
“这是否意味着我应该使用自定义 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
其他选项,而不是使用自定义 XML 文件,我们可以使用更用户友好的文件格式:JSON 或 YAML 文件。
如果你使用.NET 4.0动态,这个库真的很容易使用(序列化、反序列化、嵌套对象支持和排序输出随心所欲+将多个设置合并为一个)JsonConfig(用法相当于ApplicationSettingsBase)
对于 .NET YAML 配置库...我还没有找到一个像 JsonConfig 一样好用的
您可以将设置文件存储在此处列出的多个特殊文件夹(所有用户和每个用户)Environment.SpecialFolder Enumeration 和多个文件(默认只读、每个角色、每个用户等)
获取特殊文件夹路径示例:C#获取%AppData%路径
如果您选择使用多个设置,则可以合并这些设置:例如,合并 default + BasicUser + AdminUser 的设置。您可以使用自己的规则:最后一个覆盖值等。
据我所知,.NET 确实支持使用内置应用程序设置工具的持久设置:
Windows 窗体的应用程序设置功能可以轻松地在客户端计算机上创建、存储和维护自定义应用程序和用户首选项。使用 Windows 窗体应用程序设置,您不仅可以存储数据库连接字符串等应用程序数据,还可以存储用户特定数据,例如用户应用程序首选项。使用 Visual Studio 或自定义托管代码,您可以创建新设置,从磁盘读取它们并将它们写入磁盘,将它们绑定到表单上的属性,并在加载和保存之前验证设置数据。 - http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx
有时您想摆脱保留在传统 web.config 或 app.config 文件中的那些设置。您希望对设置条目的部署和分离的数据设计进行更细粒度的控制。或者要求是在运行时启用添加新条目。
我可以想象两个不错的选择:
强类型版本和
面向对象的版本。
强类型版本的优点是强类型设置名称和值。不存在混合名称或数据类型的风险。缺点是需要编写更多设置,无法在运行时添加。
使用面向对象版本的优点是可以在运行时添加新设置。但是您没有强类型的名称和值。必须小心字符串标识符。获取值时必须知道之前保存的数据类型。
您可以找到两个全功能实现的代码 HERE。
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();
}
}
Settings.Default.SomeProperty = 'value'; Settings.Default.Save();
就像一个魅力。还是因为我有用户设置?Settings.Default.Save()
什么都不做的说法是不正确的。正如@aku 在答案中所说,应用程序范围设置是只读的:保存对他们无效。使用该自定义 PortableSettingsProvider 将用户范围设置保存到 exe 所在的 app.config,而不是用户 AppData 文件夹中的那个。不,通常不是很好,但我在开发过程中使用它来使用从编译到编译的相同设置(没有它,每次编译它们都会进入新的唯一用户文件夹)。