Пользовательский путь для user.config

Я управляю настройками приложения с помощью установщика-дизайнера в VS2008.

«Точный путь к файлам user.config выглядит примерно так:

\\ __\\user.config 

Есть ли способ настроить этот путь? Я бы предпочел что-то вроде этого:

 \\ \\user.config 

Я заметил, что белые пробелы были заменены символами подчеркивания в «Название компании» в новой созданной папке («Test Company» -> «Test_Company»). Я действительно хочу отключить это поведение.

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

Нелегко найти хорошую информацию о реализации поставщика настраиваемых параметров, поэтому я включаю в себя полную реализацию ниже (внизу.) Сохраняется формат файла user.config, а также функциональность в дизайнере настроек. , Я уверен, что есть части, которые можно немного почистить, так что не стесняйтесь меня 🙂

Как и другие, я хотел изменить местоположение файла user.config и по-прежнему получать удовольствие и причудливость работы с файлами .settings в дизайнере, включая создание значений по умолчанию для новых установок. Важно отметить, что наше приложение также имеет другие сохраненные объекты настроек по пути (appData \ local \ etc), в котором мы уже решили, и мы не хотели, чтобы артефакты находились в нескольких местах.

Код намного длиннее, чем я хотел бы, но ответа SHORT нет. Хотя кажется несколько болезненным, чтобы иметь возможность контролировать путь, создание собственного поставщика настроек по-прежнему остается довольно мощным. Можно было бы изменить реализацию follwing для хранения данных практически в любом месте, включая пользовательский зашифрованный файл, базу данных или взаимодействие с веб-serivice.

Из того, что я прочитал, Microsoft не намерена настраивать путь к конфигурационному файлу. Я возьму их слово, когда они скажут, что это будет страшно. См. ( Это ) сообщение. Увы, если вы хотите сделать это самостоятельно, вы должны реализовать свой собственный SettingsProvider .

Вот оно..

Добавьте ссылку в свой проект в System.Configuration , вам понадобится его для реализации ConfigurationProvider.

Легкий бит … Создайте class, который реализует SettingsProvider, используйте ctrl +. чтобы помочь вам.

 class CustomSettingsProvider : SettingsProvider 

Еще один простой бит … Перейдите к коду вашего файла .settings ( есть кнопка в дизайнере ) и украсите class, чтобы указать его на вашу реализацию. Это должно быть сделано, чтобы переопределить встроенные функции, но это не меняет работу дизайнера (извините, форматирование здесь странно)

[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; } } 

Сохранение значений настроек. Я решил использовать словарь. Это будет активно использоваться немного. Я создал структуру в качестве помощника.

 ///  /// 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 метода: GetPropertyValues и SetPropertyValues . 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(); } 

Полное решение. Итак, вот весь class, который включает в себя методы конструктора, инициализации и помощника. Не стесняйтесь вырезать / вставлять кусочки и кусочки.

 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 .

См. Часто задаваемые вопросы по настройкам клиента

В: Почему путь настолько неясен? Есть ли способ изменить / настроить его?

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

LocalFileSettingsProvider не предоставляет способ изменения файлов, в которых хранятся настройки. Обратите внимание, что сам поставщик не определяет местоположение файла конфигурации в первую очередь – это система конфигурации. Если вам необходимо сохранить настройки в другом месте по какой-либо причине, рекомендуемым способом является создание собственного SettingsProvider. Это довольно просто реализовать, и вы можете найти образцы в SDK .NET 2.0, которые показывают, как это сделать. Однако имейте в виду, что вы можете столкнуться с теми же проблемами изоляции, о которых говорилось выше.

На основе патронов отличный ответ:

Внедрите новый частичный class на основе Settings.Designer.cs , поэтому Designer Design не уничтожает атрибут при внесении изменений:

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

Я создал следующий class, который можно поместить во внешний проект. Он позволяет config быть project.dll.config . Наследуя и переопределяя 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; } } 

Убедитесь, что зависимые проекты include файл project.dll.config , добавив событие post-build:

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

Вот более простая и более кратковременная альтернатива созданию пользовательского classа настроек: измените доказательства своего приложения, чтобы часть «url» была константой, а не была основана на местоположении исполняемого файла. Для этого вам необходимо изменить стандартную AppDomain при запуске программы. Есть две части: настройка app.config для использования вашего AppDomainManager и создание AppDomainManager и HostSecurityManager для настройки доказательств Url. Звучит сложно, но это намного проще, чем создание собственного classа настроек. Это относится только к неподписанным assemblyм. Если у вас подписанная assembly, она будет использовать эти данные вместо 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:

     
  • Как сохранить / сериализовать пользовательский class в файл настроек?
  • Сохранение и загрузка Java Swing
  • Android: создание пользовательских настроек
  • VIM Отключить автоматическую новую строку в конце файла
  • избегайте строки, напечатанной на консоли, усеченной (в RStudio)
  • Локальные настройки Django
  • Сохранить настройки в приложении .NET Winforms
  • jackson: как добавить настраиваемое свойство в JSON без изменения POJO
  • Эквивалентен «app.config» для библиотеки (DLL)
  • Интерфейс Android Alarm Clock
  • Пользовательские заголовки предпочтений
  • Давайте будем гением компьютера.