.NET Application Settings

Di Corrado Cavalli - Microsoft MVP

In questa pagina

Introduzione Introduzione
Definizione e utilizzo Definizione e utilizzo
Integrazione con il Windows Forms Designer Integrazione con il Windows Forms Designer
Gestione delle impostazioni Gestione delle impostazioni
Gestione avanzata Gestione avanzata
Conclusioni Conclusioni
Links (informazioni in lingua inglese) 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.

 

Esempio di Custom Settings Provider

Custom Validators