No ha sido fácil encontrar una buena información sobre la implementación de un proveedor de configuraciones personalizadas, por lo que incluyo una implementación completa debajo (abajo). Se conserva el formato del archivo user.config, así como funcionalidad dentro del diseñador .settings. Estoy seguro de que hay partes que se pueden limpiar un poco, así que no me moleste :)
Al igual que en otros, quería cambiar la ubicación del archivo user.config y seguir obteniendo la diversión y la imaginación de trabajar con los archivos .settings en el diseñador, incluida la creación de valores predeterminados para nuevas instalaciones. Es importante destacar que nuestra aplicación también ya tiene otros objetos de configuración guardados en una ruta (appData \ local \ etc) en la que ya hemos decidido, y no queríamos artefactos en múltiples ubicaciones.
El código es mucho más largo de lo que me gustaría, pero no hay una respuesta CORTA que pueda encontrar. Aunque parece un tanto doloroso solo poder controlar el camino, la creación de un proveedor de configuraciones personalizadas en sí misma todavía es bastante poderosa. Se podría modificar la implementación siguiente para almacenar datos en cualquier lugar, incluido un archivo encriptado personalizado, una base de datos o interactuar con un serivicio web.
Por lo que he leído, Microsoft no tiene la intención de configurar la ruta del archivo de configuración configurable. Tomaré su palabra cuando digan que permitir eso sería aterrador. Ver (this) publicación. Por desgracia, si quieres hacerlo tú mismo, debes implementar tu propio SettingsProvider.
Aquí va ..
agregar una referencia en su proyecto para System.Configuration, lo necesitará para implementar el SettingsProvider.
Easy bit ... Cree una clase que implemente SettingsProvider, use ctrl+. para ayudarlo.
class CustomSettingsProvider : SettingsProvider
Otra parte fácil ... Ir al código detrás de su archivo .settings (hay un botón en el diseñador) y decorar la clase de señalar a su aplicación. Esto se debe hacer para anular el construido en la funcionalidad, pero no cambia la forma en que funciona el diseñador (lo siento el formato aquí es raro)
[System.Configuration.SettingsProvider(typeof(YourCompany.YourProduct.CustomSettingsProvider))]
public sealed partial class Settings
{
//bla bla bla
}
Conseguir el camino:. Hay una propiedad llamada "SettingsKey" (por ejemplo, Properties.Settings.Default.SettingsKey) Utilicé esto para almacenar la ruta. Hice la siguiente propiedad.
/// <summary>
/// The key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
get
{
return Properties.Settings.Default.SettingsKey;
}
}
Almacenamiento de los valores de los ajustes. Elegí usar un diccionario. Esto se usará ampliamente en un momento. Creé una estructura como ayudante.
/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
The magic. Debe anular 2 métodos, GetPropertyValues y SetPropertyValues . GetPropertyValues recibe como parámetro lo que ve en el diseñador, tiene que oportunidad de actualizar los valores y devolver una nueva colección. Se llama a SetPropertyValues cuando el usuario guarda cualquier cambio en los valores realizados en tiempo de ejecución, aquí es donde yo actualizo el diccionario y escribo el archivo.
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
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();
}
solución completa. Así que aquí está toda la clase que incluye los métodos constructor, Initialize y helper. Siéntase libre de cortar/pegar rebanadas y dados.
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";
/// <summary>
/// Loads the file into memory.
/// </summary>
public CustomSettingsProvider()
{
SettingsDictionary = new Dictionary<string, SettingStruct>();
}
/// <summary>
/// Override.
/// </summary>
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(ApplicationName, config);
}
/// <summary>
/// Override.
/// </summary>
public override string ApplicationName
{
get
{
return System.Reflection.Assembly.GetExecutingAssembly().ManifestModule.Name;
}
set
{
//do nothing
}
}
/// <summary>
/// Must override this, this is the bit that matches up the designer properties to the dictionary values
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Must override this, this is the bit that does the saving to file. Called when Settings.Save() is called
/// </summary>
/// <param name="context"></param>
/// <param name="collection"></param>
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();
}
/// <summary>
/// Loads the values of the file into memory.
/// </summary>
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 <setting name="..." serializeAs="..."> 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);
}
}
/// <summary>
/// Creates an empty user.config file...looks like the one MS creates.
/// This could be overkill a simple key/value pairing would probably do.
/// </summary>
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);
}
/// <summary>
/// Saves the in memory dictionary to the user config file
/// </summary>
private void SaveValuesToFile()
{
//load the current xml from the file.
var import = XDocument.Load(UserConfigPath);
//get the settings group (e.g. <Company.Project.Desktop.Settings>)
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);
}
/// <summary>
/// The setting key this is returning must set before the settings are used.
/// e.g. <c>Properties.Settings.Default.SettingsKey = @"C:\temp\user.config";</c>
/// </summary>
private string UserConfigPath
{
get
{
return Properties.Settings.Default.SettingsKey;
}
}
/// <summary>
/// In memory storage of the settings values
/// </summary>
private Dictionary<string, SettingStruct> SettingsDictionary { get; set; }
/// <summary>
/// Helper struct.
/// </summary>
internal struct SettingStruct
{
internal string name;
internal string serializeAs;
internal string value;
}
bool _loaded;
}
}
Gracias por esto, que fue una gran ayuda para empezar. Sin embargo, una cosa que encontré es que las funciones Type.GetType() y Convert.ChangeType() no funcionan con System.Drawing.Point y System.Drawing.Size y probablemente con algunas otras. Como solución alternativa, solo estoy usando pares de entradas, pero siguen siendo opciones en el diseñador de configuraciones. – Troyen
También tenía un problema con GetType y ChangeType. Estoy intentando almacenar/cargar un [ListSortDirection] (https://msdn.microsoft.com/en-us/library/system.componentmodel.listsortdirection (v = vs.110) .aspx), pero GetType devuelve null y ChangeType arroja. La cadena se serializó como "Ascendente", pero no se pudo deserializar. Como solución temporal, traté de usar [este método GetType] (http://stackoverflow.com/a/11811046/466011), que me da el tipo correcto de ListSortDirection, pero ChangeType aún arroja, diciendo 'Cast no válido de 'System.String 'a' System.ComponentModel.ListSortDirection''. – epalm
Obteniendo errores nulos si uso una clase personalizada y comprobando que el archivo muestra solo mi ruta de clase como: 'nombre_espacio.nombreClase' en la etiqueta de propiedad. – Matheus