Percorso personalizzato di user.config

Gestisco le mie impostazioni dell’applicazione utilizzando il designer delle impostazioni in VS2008.

“Il percorso esatto dei file user.config è simile a questo:”

\\ __\\user.config 

C’è un modo per personalizzare questo percorso? Preferirei qualcosa del genere:

 \\ \\user.config 

Ho notato che gli spazi bianchi sono stati sostituiti da caratteri di sottolineatura nel “Nome società” nella nuova cartella creata (“Test Company” -> “Test_Company”). Vorrei davvero distriggersre questo comportamento.

Sai, potrei scrivere un nuovo gestore delle impostazioni basato su XML, ma mi piacerebbe usare il designer delle impostazioni.

Non è stato facile trovare buone informazioni sull’implementazione di un fornitore di impostazioni personalizzate, quindi includerò un’implementazione completa di seguito (in basso). Il formato del file user.config viene mantenuto, così come la funzionalità all’interno del progettista .settings . Sono sicuro che ci sono parti che possono essere ripulite un po ‘, quindi non mi seccate 🙂

Come altri, volevo cambiare la posizione del file user.config e ottenere il divertimento e la fantasia di lavorare con i file .settings nella finestra di progettazione, inclusa la creazione di valori predefiniti per le nuove installazioni. È importante sottolineare che la nostra app ha già altri oggetti delle impostazioni salvate su un percorso (appData \ local \ etc) in cui abbiamo già deciso e non volevamo artefatti in più posizioni.

Il codice è molto più lungo di quanto mi piacerebbe, ma non ho trovato una risposta BREVE che ho trovato. Anche se sembra alquanto doloroso solo per essere in grado di controllare il percorso, la creazione di un fornitore di impostazioni personalizzate è ancora piuttosto potente. Si potrebbe modificare l’implementazione follwing per archiviare i dati praticamente ovunque includendo un file crittografato personalizzato, un database o interagire con un servizio web.

Da quello che ho letto, Microsoft non intende rendere configurabile il percorso del file di configurazione. Prenderò la loro parola per questo quando dicono che permetterebbe che sarebbe spaventoso. Vedi ( questo ) post. Ahimè, se vuoi farlo tu stesso devi implementare il tuo SettingsProvider .

Ecco qui..

Aggiungi un riferimento nel tuo progetto a System.Configuration , ti servirà per implementare SettingsProvider.

Easy bit … Crea una class che implementa SettingsProvider, usa ctrl +. per aiutarti.

 class CustomSettingsProvider : SettingsProvider 

Un altro po ‘facile … Vai al codice retrostante del tuo file .settings ( c’è un pulsante nella finestra di progettazione ) e decora la class per indirizzarla alla tua implementazione. Questo deve essere fatto per sovrascrivere la funzionalità integrata, ma non cambia il modo in cui il designer lavora. (Scusa se la formattazione è strana)

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

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

Ottenere il percorso: C’è una proprietà chiamata “SettingsKey” (ad esempio Properties.Settings.Default.SettingsKey) L’ho usata per memorizzare il percorso. Ho fatto la seguente proprietà.

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

Memorizzare i valori delle impostazioni. Ho scelto di usare un dizionario. Questo verrà ampiamente utilizzato in un po ‘. Ho creato una struttura come aiuto.

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

La magia. È necessario eseguire l’override di 2 metodi, GetPropertyValues e SetPropertyValues . GetPropertyValues ​​riceve come parametro ciò che viene visualizzato nel designer, è necessario aggiornare i valori e restituire una nuova raccolta . SetPropertyValues ​​viene chiamato quando l’utente salva eventuali modifiche ai valori effettuati in fase di esecuzione, questo è dove aggiorno il dizionario e scrivo il file .

 ///  /// 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(); } 

Soluzione completa Quindi ecco l’intera class che include i metodi di costruzione, inizializzazione e helper. Sentiti libero di tagliare / incollare fette e dadi.

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

Dovresti implementare il tuo SettingsProvider per personalizzare il percorso.

Vedi queste FAQ sulle impostazioni del client

D: Perché il percorso è così oscuro? C’è un modo per cambiare / personalizzarlo?

A: L’algoritmo di costruzione del percorso deve soddisfare determinati requisiti rigorosi in termini di sicurezza, isolamento e robustezza. Mentre cercavamo di rendere il percorso il più facilmente rilevabile utilizzando le stringhe amichevoli fornite dall’applicazione, non è ansible mantenere il percorso completamente semplice senza incorrere in problemi come collisioni con altre app, spoofing, ecc.

LocalFileSettingsProvider non fornisce un modo per modificare i file in cui sono memorizzate le impostazioni. Si noti che il provider stesso non determina i percorsi dei file di configurazione in primo luogo – è il sistema di configurazione. Se è necessario archiviare le impostazioni in una posizione diversa per qualche motivo, il modo consigliato è scrivere il proprio SettingsProvider. Questo è abbastanza semplice da implementare e puoi trovare esempi nell’SDK .NET 2.0 che mostra come farlo. Tieni presente tuttavia che potresti incontrare gli stessi problemi di isolamento sopra menzionati.

Costruire sulla risposta eccellente Chucks:

Implementa una nuova class parziale basata su Settings.Designer.cs , quindi la Progettazione impostazioni non cancella l’attributo quando vengono apportate modifiche:

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

Ho creato la seguente class che può essere inserita in un progetto esterno. Permette alla config di essere project.dll.config . Inherite e sovrascrive ConfigPath consente qualsiasi percorso che ti piace. Ho anche aggiunto il supporto per la lettura di System.Collections.Specialized.StringCollection . Questa versione è per le impostazioni dell’applicazione.

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

Assicurati che i progetti dipendenti includano il file project.dll.config aggiungendo un evento post-build:

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

Ecco un’alternativa più semplice e più breve alla creazione di una class di impostazioni personalizzate: modifica le prove dell’app in modo che la parte “url” sia una costante anziché essere basata sulla posizione dell’eseguibile. Per fare ciò, è necessario modificare l’AppDomain predefinito all’avvio del programma. Esistono due parti: l’impostazione di app.config per utilizzare AppDomainManager e la creazione di un AppDomainManager e HostSecurityManager per personalizzare l’evidenza Url. Sembra complicato ma è molto più semplice della creazione di una class di impostazioni personalizzate. Questo si applica solo agli assembly non firmati. Se si dispone di un assembly firmato, utilizzerà tale prova al posto dell’URL. Ma la buona notizia è che il tuo percorso sarà sempre costante (purché la chiave di firma non cambi).

Puoi copiare il codice qui sotto e solo sostituire i bit 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: