.NET Application Settings
Di Corrado Cavalli - Microsoft MVP
In questa pagina
Introduzione
Definizione e utilizzo
Integrazione con il Windows Forms Designer
Gestione delle impostazioni
Gestione avanzata
Conclusioni
Links (informazioni in lingua inglese)
Introduzione
Se escludiamo le applicazioni “usa e getta” e, in generale, le applicazioni Console, mi è difficile pensare a un’applicazione Windows che non faccia uso d’impostazioni memorizzate all’esterno dell’applicazione.
L’avere delle informazioni accessibili esternamente offre all’utilizzatore la possibilità di personalizzare l’applicazione a proprio piacimento ed è il miglior modo per cambiarne alcuni aspetti funzionali senza dovere agire sul codice.
Nel corso degli anni la gestione delle impostazioni si è evoluta non poco: Dal classico file di testo si è in seguito passati ai files .ini, al registry e, con l’avvento di .NET, al relativo meccanismo di gestione delle impostazioni basato su files descritti in formato xml.
In realtà la gestione disponibile nel Framework 1.x aveva parecchie lacune e l’utilizzo non era proprio immediato, le cose sono cambiate con l’arrivo del Framework 2.0 e Visual Studio 2005.
Definizione e utilizzo
Immaginiamo di avere bisogno di parametrizzare due aspetti che regolano il funzionamento del nostro applicativo: Il numero massimo di connessioni verso un server e il tipo di utente (Base o Avanzato), in particolare, quest’ultima dovrà essere modificabile a run-time dall’utente.
Andiamo nelle proprietà del progetto e alla voce Settings inseriamo le nostre impostazioni (Figura 1)
Analizziamo nel dettaglio quanto presente nella griglia di configurazione.
La colonna Name contiene il nome che utilizzeremo per accedere da codice all’impostazione, Type è il tipo base associato (le impostazioni sono ora tipizzate),Value rappresenta il valore di default mentre Scope rappresenta il contesto di utilizzo e qui, dobbiamo fare un ulteriore precisazione.
Impostando Scope al valore Application otterremo delle impostazioni in sola lettura e comuni a tutti gli utilizzatori dell’ applicazione, il motivo per cui l’impostazione non è modificabile a runtime è legato al fatto che queste informazioni sono memorizzate nel file .config associato all’applicativo il quale, solitamente, risiede nella cartella <Drive>\Programmi \<Applicazione> la quale, è nella maggior parte dei casi, accedibile in scrittura solo dall’amministratore della macchina.
Se l’impostazione deve essere modificabile a runtime lo scope deve essere impostato a User, in questo caso le impostazioni modificate verranno memorizzate nel profilo dell’utente in posizioni diverse a seconda se l’utente è roaming (e quindi le impostazioni utente viaggiano sulle varie macchine dove l’utente effettua il login) oppure no.
Ad esempio, su Windows Vista con utente non roaming le impostazioni utente che sono state modificate rispetto ai valori predefiniti, sono memorizzate in: <Drive>\Users\<Utente>\AppData\Local\<AppName>\<AppName>.vshost.exe_<HashedPath>\<Versione> nel file user.config.
Il frammento di codice che segue mostra come accedere e modificare le impostazioni create in precedenza
C#
//Leggo le impostazioni int maxConnection = Properties.Settings.Default.MaxConnections; bool isAdvanced = Properties.Settings.Default.AdvancedMode; //Modifico l'impostazione Utente Properties.Settings.Default.AdvancedMode = false; //Memorizzo Properties.Settings.Default.Save();
VB
‘Leggo le impostazioni Dim maxConnection As Integer= Properties.Settings.Default.MaxConnections Dim isAdvanced As Boolean= Properties.Settings.Default.AdvancedMode ‘Modifico l'impostazione Utente My.Settings.AdvancedMode = false ‘Memorizzo My.Settings.Save()
Questo accesso “diretto” è possibile grazie al fatto che dietro le quinte Visual Studio ha generato una classe per accedere in maniera precisa e tipizzata alle impostazioni di applicazione. Questa classe autogenerata è contenuta nel file Settings.Designer.cs (o .vb) ed eredita dalla classe astratta ApplicationSettingsBase il cui compito è fornire i servizi base per la gestione delle impostazioni nelle applicazioni WinForms. All’interno di questa classe autogenerata, Visual Studio ha inserito le proprietà che rappresentano le nostre impostazioni
Nella prossima versione di Visual Studio (“Orcas”) sarà anche possibile decidere il livello di visibilità della classe autogenerata. (Figura 2)
C#
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("10")] public int MaxConnections { get {return ((int)(this["MaxConnections"]));} } [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("False")] public bool AdvancedMode { get {return ((bool)(this["AdvancedMode"])); } set {this["AdvancedMode"] = value;} }
VB
<Global.System.Configuration.UserScopedSettingAttribute(), _ Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _ Global.System.Configuration.DefaultSettingValueAttribute("10")> _ Public Property MaxConnections() As Integer Get Return CType(Me("MaxConnections"),Integer) End Get Set Me("MaxConnections") = value End Set End Property <Global.System.Configuration.UserScopedSettingAttribute(), _ Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _ Global.System.Configuration.DefaultSettingValueAttribute("False")> _ Public Property AdvancedMode() As Boolean Get Return CType(Me("AdvancedMode"),Boolean) End Get Set Me("AdvancedMode") = value End Set End Property
Dall’estratto riportato sopra notate come le proprietà oltre che garantire la tipizzazione siano decorate con degli attributi che determinano il tipo di impostazione (Applicazione/Utente) e quindi la relativa modalità di memorizzazione e il valore di default, quest’ultimo è un dettaglio particolarmente importante perché garantisce la corretta valorizzazione dell’impostazione anche nel caso in cui il file .config o il file user.config vengano inavvertitamente cancellati.
Il tipo utilizzabile nella colonna Type è limitato a quelli che implementano un TypeConverter che permetta la serializzazione da e verso stringa questo significa che, a patto di implementare il relativo TypeConverter possiamo definire come Type anche un tipo proprietario.
Immaginiamo di volere creare un’impostazione di tipo Person la cui definizione è la seguente:
C#
[TypeConverter(typeof(PersonConverter))] public class Person { private string mName; public string Name { get { return mName; } set { mName = value; } } }
la quale dispone di un proprio custom type converter:
public class PersonConverter : TypeConverter { public override bool CanConvertTo (…){…} public override object ConvertTo (…) {…} public override bool CanConvertFrom (…){…} public override object ConvertFrom (…){…} }
VB
<TypeConverter(GetType(PersonConverter))> _ Public Class Person Private mName As String Public Property Name() As String Get Return mName End Get Set(ByVal value As String) mName = value End Set End Property End Class Public Class PersonConverter : Inherits TypeConverter Public Overrides Function CanConvertFrom(…) As Boolean ... End Function Public Overrides Function CanConvertTo(...) As Boolean ... End Function Public Overrides Function ConvertFrom(...) As Object ... End Function Public Overrides Function ConvertTo(...) As Object ... End Function End Class
È perciò possibile definire l’impostazione come tipo Person direttamente nell’editor di Visual Studio 2005 selezionando “Sfoglia…” (Figura 3)
Integrazione con il Windows Forms Designer
Il meccanismo di gestione delle impostazioni è integrato all’interno del Windows Forms designer, questo rende possibile il collegare le proprietà dei controlli direttamente a delle impostazioni. Un esempio classico è quello di associare la posizione del form a un impostazione cosi che alla successiva riapertura sia nella stessa posizione di chiusura.
Per far questo è sufficiente, dopo aver selezionato il form, selezionare nella finestra delle proprietà la voce PropertyBindingcontenuta in ApplicationSettings e, dalla finestra indicata in figura 4, creare una nuova impostazione selezionando “New”.
Non ci rimane che, alla chiusura dell’applicazione, invocare:
C#
Properties.Settings.Default.Save();
VB
My.Settings.Save();
VB offre anche in alternativa, quella di selezionare la voce “Save My Settings on ShutDown” all’interno delle proprietà del progetto (Figura 5) evitando la necessità della riga citata.
Gestione delle impostazioni
Esplorando la classe ApplicationSettingsBase vi accorgerete che espone una serie di metodi ed eventi che permettono di controllare e gestire altri aspetti legati alla gestione delle impostazioni, il primo tra questi è l’Upgrade.
Quando si genera una nuova versione di un’applicazione succede che le eventuali impostazioni utente vengono automaticamente perse e i valori restituiti sono quelli predefiniti, questo perché, come indicato in precedenza, il percorso che determina la posizione del file user.config comprende anche la versione dell’applicazione. Volendo migrare tutte le precedenti impostazioni utente alla nuova versione quello che dobbiamo fare è invocare il metodo Upgrade() e ricaricare le impostazioni.
C#
Properties.Settings.Default.Upgrade(); Properties.Settings.Default.Reload();
VB
My.Settings.Upgrade(); My.Settings.Reload();
Rimane da definire come determinare la necessità di migrare le impostazioni, sebbene esista la possibilità di capire se un’impostazione utente è stata recuperata da un file user.config oppure dal file .config di applicazione la soluzione più veloce consiste nell’inserire un impostazione utente NeedUpdate a segnalare l’eventuale necessità di migrazione.
C#
if (Properties.Settings.Default.NeedUpgrade) { Properties.Settings.Default.Upgrade(); Properties.Settings.Default.Reload(); Properties.Settings.Default.NeedUpgrade=false; Properties.Settings.Default.Save(); }
VB
If My.Settings.NeedUpgrade My.Settings.Upgrade() My.Settings.Reload() My.Settings.NeedUpgrade=False My.Settings.Save() End If
Eventuali impostazioni assenti nella nuova versione non vengono migrate.
Nel caso si voglia gestire la migrazione delle impostazioni in maniera dettagliata è possibile utilizzare il metodo GetPreviousVersion() come mostrato di seguito
C#
bool isAdvanced = (bool)Properties.Settings.Default.GetPreviousVersion("AdvancedMode"); if (isAdvanced) { Properties.Settings.Default.AdvancedMode = false; Properties.Settings.Default.ExpertUser = true; //Setto anche questa nuova impostazione… }
VB
Dim isAdvanced as Boolean= CType(My.Settings.Default.GetPreviousVersion("AdvancedMode"),Boolean) if isAdvanced My.Settings.AdvancedMode = False My.Settings.ExpertUser = True ‘Setto anche questa nuova impostazione… End If
Le eventuali modifiche effettuate sulle impostazioni non vengono memorizzate fino a quando non viene invocato il metodo Save(), volendo annullare tutte le modifiche non salvate e ricaricare le impostazioni memorizzate in precedenza è possibile invocare il metodo Reload().
Nel caso si vogliano azzerare tutte le impostazioni utente ai valori originali il metodo da invocare è Reset() che, fisicamente, azzera il contenuto del file user.config.
La classe ApplicationSettingsBase espone anche una serie di eventi legati alle impostazioni di sistema, l’utilizzo di questi eventi è disponibile selezionando “View Code” dalla finestra indicata in Figura 1.
SettingsLoaded e SettingSaving sono eventi generati al caricamento e al salvataggio delle impostazioni, in particolare SettingsSaving consente, impostando e.Cancel a True, l’annullamento del salvataggio delle impostazioni.
Sicuramente più interessanti sono invece gli eventi SettingsChanging e PropertyChanged: il primo ci permette di validare un impostazione prima che questa venga memorizzata, il secondo ci informa che una determinata proprietà è stata modificata , peccato che venga invocato anche se il nuovo valore coincide con quello precedente.
L’esempio che segue mostra come utilizzare l’evento per validare un’impostazione denominata “Email”
C#
private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { if (e.SettingName == "Email") { string email = e.NewValue.ToString(); if(!email.Contains("@") && !email.Contains(".")) { MessageBox.Show("Invalid email"); e.Cancel = true; } } }
VB
Private Sub SettingChangingEventHandler(sender As Object, e As SettingChangingEventArgs ) if e.SettingName = "Email" Then Dim email as String = e.NewValue.ToString() If (Not email.Contains("@")) AndAlso (Not email.Contains(".")) MessageBox.Show("Invalid email") e.Cancel = True End If End If End Sub
ApplicationSettingsBase non si occupa della memorizzazione dei dati, questo compito è delegato al provider associato che, se non diversamente indicato, è un LocalFileSettingsProvider il quale memorizza i dati su file. Volendo è possibile crearsi un proprio provider e quindi memorizzare le impostazioni su un media diverso, ad esempio un database.
La realizzazione di un custom settings provider va oltre lo scopo di quest’articolo, nella sezione links trovate comunque un riferimento ad un esempio pratico.
Gestione avanzata
Quanto descritto finora copre la maggior parte delle esigenze, ma in alcuni casi quanto esposto da ApplicationSettingsBase non è sufficiente, ad esempio, molti utilizzatori sentono la mancanza di poter modificare a runtime le impostazioni di applicazione, come potrebbe essere la stringa di connessione a un database la quale non cambia da utente a utente.
Premesso che quanto andremo ad analizzare è comunque soggetto a tutti i criteri di sicurezza elencati in precedenza, vediamo come accedere alle impostazioni usando un livello più basso il quale, a fronte di un aumento della complessità offre una totale libertà di utilizzo.
Quanto riportato di seguito è il contenuto di un file app.config che, al momento della compilazione del progetto, diverrà il file <NomeEseguibile>.exe.config
<configuration> <configSections> <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > <section name="DemoSettings.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </sectionGroup> <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > <section name="DemoSettings.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" /> </sectionGroup> </configSections> <applicationSettings> <!--ConfigurationSectionGroup--> <DemoSettings.Properties.Settings> <!--ConfigurationSection--> <setting name="MaxConnections" serializeAs="String"> <!--ConfigurationElement--> <value>10</value> </setting> </DemoSettings.Properties.Settings> </applicationSettings> <userSettings> <!--ConfigurationSectionGroup--> <DemoSettings.Properties.Settings> <!--ConfigurationSection--> <setting name="AdvancedMode" serializeAs="String"> <!--ConfigurationElement--> <value>False</value> </setting> </DemoSettings.Properties.Settings> </userSettings> </configuration>
All’interno ho evidenziato le parti che vengono mappate sulle varie classi (o specializzazioni di esse) che andremo ad affrontare.
La classe che da libero accesso a tutti i dettagli di configurazione è la classe ConfigurationManager presente all’interno di System.Configuration, namespace che è contenuto nella libreria System.Configuration.dll che è quindi necessario referenziare.
L’esempio che segue mostra come attraverso questa nuova modalità di accesso sia possibile modificare l’impostazione “MaxConnections” non accessibile utilizzando la modalità “tradizionale”.
C#
Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); ConfigurationSectionGroup sectionGroup=cfg.GetSectionGroup("applicationSettings"); ClientSettingsSection section = sectionGroup.Sections.Get("DemoSettings.Properties.Settings") as ClientSettingsSection; section.SectionInformation.ForceSave = true; SettingElement element = section.Settings.Get("MaxConnections") as SettingElement; element.Value.ValueXml.InnerText = "100"; cfg.Save();
VB
Dim cfg As Configuration =ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None) Dim sectionGroup As ConfigurationSectionGroup =cfg.GetSectionGroup("applicationSettings") Dim section As ClientSettingsSection = _ TryCast(sectionGroup.Sections.Get("DemoSettings.Properties.Settings"),ClientSettingsSection) section.SectionInformation.ForceSave = True Dim element As SettingElement = TryCast(section.Settings.Get("MaxConnections"),SettingElement) element.Value.ValueXml.InnerText = "100" cfg.Save()
Nota: Se provate questo esempio, ricordate che le modifiche vengono applicate al file <NomeEseguibile>.vshost.exe.config e che questo viene riscritto da Visual Studio con i contenuti del file <NomeEseguibile>.exe.config non appena terminata l’esecuzione del vostro programma.
Il metodo OpenExeConfigurationGroup accetta un parametro di tipo ConfigurationUserLevel attraverso il quale è possibile indicare la modalità di accesso alle impostazioni, usando ConfigurationUserLevel.None abbiamo indicato che siamo interessati alla configurazione condivisa da tutti gli utenti, in alternativa è possibile specificare la configurazione Roaming o Locale utilizzata dall’utente corrente.
Da questo esempio appare evidente come sia possibile accedere attraverso una gerarchia di classi alle varie parti che descrivono le nostre impostazioni per leggerle e/o modificarle, ciò che rende il tutto ancora più interessante è che è possibile arricchire questa gerarchia con le proprie classi e utilizzarle all’interno del file .config.
Supponiamo di voler definire un proprio SectionGroup che ospiterà al proprio interno una custom Section, aggiungendo al nostro progetto le classi riportate di seguito:
C#
namespace DemoSettings { class CustomConfigSectionGroup:ConfigurationSectionGroup { } class CustomConfigSection : ConfigurationSection { [ConfigurationProperty("userName", IsRequired = true)] public string UserName { get { return (string)base["userName"]; } set { base["userName"] = value; } } [ConfigurationProperty("expiresOn")] public string ExpiresOn { get { return (string)base["expiresOn"]; } set { base["expiresOn"] = value; } } } }
VB
namespace DemoSettings Class CustomConfigSectionGroup: Inherits ConfigurationSectionGroup End Class Class CustomConfigSection : Inherits ConfigurationSection <ConfigurationProperty("userName", IsRequired = true)> _ Public Property UserName As String Get Return CType(Me("userName"),String) End Get Set Me("userName") = value End Set End Property <ConfigurationProperty("expiresOn")> _ Public Property ExpiresOn As String Get Return CType(Me("expiresOn "),String) End Get Set Me("expiresOn ") = value End Set End Property End Class
Fatto questo possiamo modificare il file .config aggiungendo le nuove informazioni e specificando all’interno dell’elemento <sectiongroup> verso quali tipi andranno mappate.
<sectionGroup name="customSectionGroup" type="DemoSettings.CustomConfigSectionGroup, DemoSettings"> <section name="customUserSettings" type="DemoSettings.CustomConfigSection, DemoSettings" allowExeDefinition="MachineToLocalUser" /> </sectionGroup> ... ... <customSectionGroup > <customUserSettings userName="Corrado" expiresOn="13/10/2007" /> </customSectionGroup>
A questo punto è possibile accedere alla sezione custom usando il codice che segue:
C#
Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); CustomConfigSectionGroup grp = cfg.GetSectionGroup("customSectionGroup") as CustomConfigSectionGroup; CustomConfigSection section = grp.Sections["customUserSettings"] as CustomConfigSection; Console.WriteLine("username: {0} expiresOn: {1}", section.UserName, section.ExpiresOn);
VB
Dim cfg As Configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None) Dim grp As CustomConfigSectionGroup = TryCast(cfg.GetSectionGroup("customSectionGroup"), CustomConfigSectionGroup) Dim section As CustomConfigSection = _ TryCast(grp.Sections("customUserSettings"), CustomConfigSection) Console.WriteLine("username: {0} expiresOn: {1}", section.UserName, section.ExpiresOn)
Notate come la relazione tra elemento xml e proprietà del tipo corrispondente avvenga decorando la proprietà con l’attributo ConfigurationProperty.
Immaginiamo ora di volere inserire all’interno della sezione un insieme di propri elementi, seguendo lo stesso procedimento, i passi da seguire sono:
1- Definire i tipi CustomConfigElement e CustomConfigElementCollection che rappresentano rispettivamente l’elemento e la collezione che li conterrà:
C#
public class CustomConfigElement : ConfigurationElement { [ConfigurationProperty("stringValue", DefaultValue = "?", IsKey = true, IsRequired = true)] [StringValidator(InvalidCharacters = "*#", MinLength = 1, MaxLength = 10)] public string StringValue { get { return base["stringValue"] as string; } set { base["stringValue"] = value; } } [ConfigurationProperty("intValue", DefaultValue = "-1", IsKey = false, IsRequired = false)] [IntegerValidator(MinValue = -1, MaxValue = 10)] public int IntValue { get { return (int)base["intValue"]; } set { base["intValue"] = value; } } [ConfigurationProperty("background", DefaultValue = "0", IsKey = false, IsRequired = false)] [TypeConverter(typeof(ColorConverter))] public Color Background { get { return (Color)base["background"]; } set { base["background"] = value; } } } [ConfigurationCollection(typeof(CustomConfigElement), AddItemName = "userElement")] public class CustomConfigElementCollection : ConfigurationElementCollection { protected override ConfigurationElement CreateNewElement () { return new CustomConfigElement(); } protected override object GetElementKey (ConfigurationElement element) { CustomConfigElement elem = element as CustomConfigElement; return elem.StringValue; } public void Add (string valueString, int valueInteger) { CustomConfigElement newElement = this.CreateNewElement() as CustomConfigElement; newElement.StringValue = valueString; newElement.IntValue = valueInteger; base.BaseAdd(newElement); } }
VB
Public class CustomConfigElement : Inherits ConfigurationElement <ConfigurationProperty("stringValue", DefaultValue = "?", IsKey := True, IsRequired := True)> _ <StringValidator(InvalidCharacters = "*#", MinLength = 1, MaxLength = 10)> _ Public Property StringValue As String Get Return CType(Me("stringValue "),String) End Get Set Me("stringValue ") = value End Set End Property <ConfigurationProperty("intValue", DefaultValue = "-1", IsKey := False, IsRequired := False)> _ <IntegerValidator(MinValue = -1, MaxValue = 10)> _ Public Property IntValue As Integer Get Return CType(Me("intValue "),Integer) End Get Set Me("intValue ") = value End Set End Property <ConfigurationProperty("background", DefaultValue = "0", IsKey := False, IsRequired := False)> _ <TypeConverter(GetType(ColorConverter))> _ Public Property Background As Color Get Return CType(Me("background "),Color) End Get Set Me("background ") = value End Set End Property End Class <ConfigurationCollection(GetType(CustomConfigElement), AddItemName := "userElement")> _ Public Class CustomConfigElementCollection : Inherits ConfigurationElementCollection protected overrides Function CreateNewElement () as ConfigurationElement return New CustomConfigElement() End Function protected overrides Function GetElementKey (ConfigurationElement element) As Object Dim elem As CustomConfigElement = TryCast(element,CustomConfigElement) Return elem.StringValue End Function public Sub Add (valueString As String, valueInteger As Integer) Dim newElement As CustomConfigElement = TryCast(Me.CreateNewElement(), CustomConfigElement) newElement.StringValue = valueString newElement.IntValue = valueInteger MyBase.BaseAdd(newElement) End Function End Class
2 - Modifichiamo la classe CustomConfigSection affinché “riconosca” il nuovo elemento <customElements> che conterrà i vari CustomConfigElements aggiungendo questo frammento di codice:
C#
[ConfigurationProperty("customElements")] public CustomConfigElementCollection UserElements { get { return base["customElements"] as CustomConfigElementCollection; } }
VB
<ConfigurationProperty("customElements")> _ public Property CustomConfigElementCollection As UserElements Get Return TryCast(Me("customElements"), CustomConfigElementCollection) End Get End Property
3 - Aggiungiamo i custom items nel file app.config
<customSectionGroup > <customUserSettings userName="Corrado" expiresOn="13/10/2007" > <customElements> <userElement stringValue="v1" intValue="5" background="yellow" /> <userElement stringValue="v2" /> <userElement stringValue="v3" intValue="3"/> </customElements> </customUserSettings> </customSectionGroup>
Volendo, avremmo potuto inserire queste informazioni in un file esterno (es: UserSettings.config) e collegarlo al file .config attraverso l’attributo configSource.
<customSectionGroup > <customUserSettings configSource="UserSettings.config" /> </customSectionGroup>
4 -Accedere alle impostazioni attraverso questo codice:
C#
Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal); CustomConfigSectionGroup grp = cfg.GetSectionGroup("customSectionGroup") as CustomConfigSectionGroup; CustomConfigSection section = grp.Sections["customUserSettings"] as CustomConfigSection; CustomConfigElementCollection customElements = section.UserElements as CustomConfigElementCollection; //Enumerate items foreach (CustomConfigElement userElem in customElements) { Console.WriteLine("StringValue:{0} IntValue:{1}", userElem.StringValue, userElem.IntValue); } //Add new item CustomConfigElement newElem = new CustomConfigElement(); customElements.Add("v7", 8); //Save to user profile cfg.Save(ConfigurationSaveMode.Modified);
VB
Dim cfg As Configuration = _ ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal) Dim grp As CustomConfigSectionGroup = _ TryCast(cfg.GetSectionGroup("customSectionGroup"), CustomConfigSectionGroup) Dim section As CustomConfigSection = _ TryCast(grp.Sections("customUserSettings"),CustomConfigSection) Dim customElements As CustomConfigElementCollection = _ TryCast(section.UserElements, CustomConfigElementCollection) ‘Enumerate items For Each userElem As CustomConfigElement in customElements Console.WriteLine("StringValue:{0} IntValue:{1}", userElem.StringValue, userElem.IntValue) Next //Add new item CustomConfigElement newElem = new CustomConfigElement(); customElements.Add("v7", 8); ‘Save to user profile cfg.Save(ConfigurationSaveMode.Modified)
In questo caso, abbiamo aperto la configurazione locale dell’utente e, oltre a leggere le informazioni che abbiamo definito all’interno della nostra sezione personalizzata, abbiamo anche realizzato un’operazione difficile da implementare seguendo la modalità “standard”: quella di creare delle impostazioni dinamicamente.
Alcuni dettagli relativi al codice delle classi riportate al punto 1 della nostra procedura:
A ogni proprietà è stato applicato un attributo di validazione il quale permette di validare i valori contenuti nel file .config rendendo meno criptici eventuali messaggi di errore (vi rimando alla sezione Links per ulteriori dettagli)
La proprietà Background ha associato un TypeConverter affinché sia possibile serializzare/deserializzare correttamente il rispettivo valore.
La classe CustomConfigElementCollection è decorata con un attributo ConfigurationCollection il quale, oltre ad indicare il tipo di elemento contenuto dalla collection, permette mediante l’attributo AddItemName di specificare il nome dell’elemento xml che conterrà i custom elements (“userElements” nel nostro caso)
Conclusioni
La gestione delle impostazioni di un’applicazione è profondamente cambiata (in meglio) rispetto a quanto disponibile nelle versioni 1.X del .NET Framework, la gestione ora è completamente integrata in Visual Studio 2005, i settings sono tipizzati e il loro utilizzo facile e immediato. In alcuni particolari casi può essere necessario agire più a basso livello, ma come si è visto, l’architettura è estendibile e in grado di gestire qualsiasi necessità di configurazione.