user.config的自定义路径

我使用VS2008中的设置设计器管理我的应用程序设置。

“user.config文件的确切路径如下所示:”

\\ __\\user.config 

有没有办法定制这条路径? 我更喜欢这样的东西:

 \\ \\user.config 

我注意到新创建的文件夹(“Test Company” – >“Test_Company”)中的“公司名称”中的下划线替换了空格。 我真的希望关闭这种行为。

你知道,我可以写一个新的基于XML的设置处理程序,但我想使用设置设计器。

要找到实现自定义设置提供程序的好信息并不容易,所以我在下面包含了一个完整的实现(底部)。保留了user.config文件的格式,以及.settings设计器中的function。 。 我确定有些东西可以清理一下,所以不要麻烦我:)

与其他人一样,我想更改user.config文件的位置,并且仍然可以在设计器中使用.settings文件获得乐趣和乐趣,包括为新安装创建默认值。 重要的是,我们的应用程序还已经在我们已经决定的路径(appData \ local \ etc)中有其他已保存的设置对象,并且我们不希望在多个位置存在工件。

代码比我想要的要长得多,但我找不到简短的答案。 虽然能够控制路径似乎有点痛苦,但创建自定义设置提供程序本身仍然非常强大。 人们可以改变以下实现来存储数据,包括自定义加密文件,数据库或与Web服务交互。

根据我的阅读,Microsoft并不打算使配置文件的路径可配置。 当他们说允许那将是可怕的时候,我会接受他们的话。 见( 本 )post。 唉,如果你想自己动手,你必须实现自己的SettingsProvider 。

开始..

项目中的引用添加System.Configuration ,您将需要它来实现SettingsProvider。

Easy bit …创建一个实现SettingsProvider的类,使用ctrl +。 帮助你

 class CustomSettingsProvider : SettingsProvider 

另一个简单的方法 …转到.settings文件后面的代码(设计器中有一个按钮 )并装饰类以指向它的实现。 必须这样做才能覆盖内置function,但它并没有改变设计师的工作方式。(抱歉这里的格式很奇怪)

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

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

获取路径:有一个名为“SettingsKey”的属性(例如Properties.Settings.Default.SettingsKey)我用它来存储路径。 我做了以下财产。

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

存储设置值。 我选择使用字典。 这将被广泛使用。 我创建了一个struct作为帮助器。

 ///  /// In memory storage of the settings values ///  private Dictionary SettingsDictionary { get; set; } ///  /// Helper struct. ///  internal struct SettingStruct { internal string name; internal string serializeAs; internal string value; } 

魔法。 您必须覆盖2个方法, GetPropertyValuesSetPropertyValues 。 GetPropertyValues接收为您在设计器中看到的参数,您必须有机会更新值并返回新集合 。 当用户保存对在运行时创建的值的任何更改时,将调用SetPropertyValues,这是我更新字典并写出文件的位置

 ///  /// Must override this, this is the bit that matches up the designer properties to the dictionary values ///  ///  ///  ///  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; } ///  /// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called ///  ///  ///  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和helper方法。 随意剪切/粘贴切片和骰子。

 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"; ///  /// Loads the file into memory. ///  public CustomSettingsProvider() { SettingsDictionary = new Dictionary(); } ///  /// Override. ///  public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { base.Initialize(ApplicationName, config); } ///  /// Override. ///  public override string ApplicationName { get { return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name; } set { //do nothing } } ///  /// Must override this, this is the bit that matches up the designer properties to the dictionary values ///  ///  ///  ///  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; } ///  /// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called ///  ///  ///  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(); } ///  /// Loads the values of the file into memory. ///  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  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); } } ///  /// Creates an empty user.config file...looks like the one MS creates. /// This could be overkill a simple key/value pairing would probably do. ///  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); } ///  /// Saves the in memory dictionary to the user config file ///  private void SaveValuesToFile() { //load the current xml from the file. var import = XDocument.Load(UserConfigPath); //get the settings group (eg ) 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); } ///  /// The setting key this is returning must set before the settings are used. /// eg Properties.Settings.Default.SettingsKey = @"C:\temp\user.config"; ///  private string UserConfigPath { get { return Properties.Settings.Default.SettingsKey; } } ///  /// In memory storage of the settings values ///  private Dictionary SettingsDictionary { get; set; } ///  /// Helper struct. ///  internal struct SettingStruct { internal string name; internal string serializeAs; internal string value; } bool _loaded; } } 

您必须实现自己的SettingsProvider来自定义路径。

请参阅此客户端设置FAQ

问:为什么路径如此模糊? 有没有办法改变/定制它?

答:路径构建算法必须满足安全性,隔离性和鲁棒性方面的某些严格要求。 虽然我们尝试通过使用友好的,应用程序提供的字符串尽可能容易地发现路径,但是不可能保持路径完全简单而不会遇到与其他应用程序冲突,欺骗等问题。

LocalFileSettingsProvider不提供更改存储设置的文件的方法。 请注意,提供程序本身并不首先确定配置文件位置 – 它是配置系统。 如果由于某种原因需要将设置存储在其他位置,建议的方法是编写自己的SettingsProvider。 这很容易实现,你可以在.NET 2.0 SDK中找到如何执行此操作的示例。 但请记住,您可能遇到上述相同的隔离问题。

以Chucks为基础的优秀答案:

基于Settings.Designer.cs实现新的分部类,因此设置设计器在进行更改时不会清除该属性:

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

我创建了以下可以放在外部项目中的类。 它允许配置为project.dll.config 。 通过inheritance和覆盖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 : 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"; ///  /// Loads the file into memory. ///  public DllFileSettingsProvider() { SettingsDictionary = new Dictionary(); } ///  /// Override. ///  public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { base.Initialize(ApplicationName, config); } ///  /// Override. ///  public override string ApplicationName { get { return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name; } set { //do nothing } } ///  /// Must override this, this is the bit that matches up the designer properties to the dictionary values ///  ///  ///  ///  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; } ///  /// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called ///  ///  ///  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(); } ///  /// Loads the values of the file into memory. ///  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  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); } } ///  /// Creates an empty user.config file...looks like the one MS creates. /// This could be overkill a simple key/value pairing would probably do. ///  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); } ///  /// Saves the in memory dictionary to the user config file ///  private void SaveValuesToFile() { //load the current xml from the file. var import = XDocument.Load(ConfigPath); //get the settings group (eg ) 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); } ///  /// The setting key this is returning must set before the settings are used. /// eg Properties.Settings.Default.SettingsKey = @"C:\temp\user.config"; ///  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; } } ///  /// In memory storage of the settings values ///  internal Dictionary SettingsDictionary { get; set; } ///  /// Helper struct. ///  internal struct SettingStruct { internal string name; internal string serializeAs; internal string value; } bool _loaded; } } 

通过添加post-build事件确保依赖项目包含project.dll.config文件:

 copy $(SolutionDir)Worker\$(OutDir)Worker.dll.config $(TargetDir) /y 

这是创建自定义设置类的更简单,更简洁的替代方法:更改应用程序的证据,以便“url”部分是常量而不是基于可执行文件的位置。 为此,您需要在程序启动时修改默认的AppDomain。 有两个部分:设置app.config以使用AppDomainManager,以及创建AppDomainManager和HostSecurityManager以自定义Url证据。 听起来很复杂,但它比创建自定义设置类要简单得多。 这仅适用于未签名的程序集。 如果您有签名的程序集,它将使用该证据而不是Url。 但好消息是,无论如何,您的路径将始终保持不变(只要签名密钥不会更改)。

您可以复制下面的代码,只需替换YourAppName位。

DefaultAppDomainManager.cs:

 using System; using System.Security; using System.Security.Policy; namespace YourAppName { ///  /// A least-evil (?) way of customizing the default location of the application's user.config files. ///  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(); 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: