… e ci complichiamo la vita reinventando la ruota.
La gestione dei settings di un’applicazione
VisualStudio ci mette a disposizione un’infrastruttura eccelsa per la gestione dei settings, che va mostruosamente oltre il mero file “ini”, non foss’altro perchè, ad esempio:
- è fortemente tipizzata;
- è “a gratis” user o application scoped;
- offre un designer integrato nell’ambiete di sviluppo, con tutto quello che questo comporta, come ad esempio il pieno supporto per il refactoring… che è tutto tranne che poco ;-)
Quello che succede però è che appena deviamo un attimo da quello che abbiamo di builtin nel framework/ambiente di sviluppo, perchè ad esempio la necessità è di conservare/persistere i settaggi in un database, implicitamente ci mettiamo il paraocchi e facciamo l’ennesima soluzione custom senza neanche dedicare un secondo a capire se quello che abbiamo in mano è in qualche modo customizzabile.
Oggi nell’applicare la mia nuova policy ho riscritto un piccolo componente che uso da qualche “secolo” aggiornandolo a Linq, il componente è un SettingsProvider.
MyDatabaseSettingsProvider
public sealed class MyDatabaseSettingsProvider : SettingsProvider
{
private readonly String connectionString;
public MyDatabaseSettingsProvider()
{
this.connectionString = ConfigurationManager.ConnectionStrings[ "SettingsCN" ].ConnectionString;
}
public override string ApplicationName
{
get;
set;
}
public override void Initialize( string name, NameValueCollection config )
{
if( String.IsNullOrEmpty( name ) )
{
name = "DatabaseSettingsProvider";
}
base.Initialize( name, config );
}
public override SettingsPropertyValueCollection GetPropertyValues( SettingsContext context, SettingsPropertyCollection collection )
{
var valueCollection = new SettingsPropertyValueCollection();
using( var dc = new SettingsDataContext( this.connectionString ) )
{
foreach( SettingsProperty setting in collection )
{
var settingValue = new SettingsPropertyValue( setting );
settingValue.IsDirty = false;
var key = settingValue.Name;
var dbSetting = dc.Settings.Where( s => s.Name == key ).SingleOrDefault();
if( dbSetting != null )
{
settingValue.SerializedValue = dbSetting.SerializedValue;
}
valueCollection.Add( settingValue );
}
}
return valueCollection;
}
public override void SetPropertyValues( SettingsContext context, SettingsPropertyValueCollection collection )
{
using( var dc = new SettingsDataContext( this.connectionString ) )
{
foreach( SettingsPropertyValue propval in collection )
{
if( !propval.UsingDefaultValue && propval.IsDirty )
{
var key = propval.Name;
var dbSetting = dc.Settings.Where( s => s.Name == key ).SingleOrDefault();
if( dbSetting != null )
{
dbSetting.SerializedValue = ( String )propval.SerializedValue;
}
else
{
var setting = new Setting();
setting.Name = key;
setting.SerializedValue = ( String )propval.SerializedValue;
setting.ValueType = propval.Property.PropertyType.ToShortString();
dc.Settings.InsertOnSubmit( setting );
}
dc.SubmitChanges();
}
}
}
}
}
Gli unici 2 metodi degni di nota sono:
- GetPropertyValues: questo metodo viene chiamato dall’infrastruttura dei Settings di Visual Studio la prima volta che accedete all’instanza statica, esposta come singleton, dei settings: Properties.Settings.Default… per intenderci;
L’infrastruttura vi passa un context, che è un dictionary in cui ci potete mettere un sacco di belle cose, e una collection che rappresenta l’elenco dei settings di cui ha bisogno che venga caricato il valore;
- SetPropertyValues: questo metodo viene invece chiamato quando l’infrastruttura dei settings ha bisogno di salvare i settings, attenzione che viene chiamato solo per i settings che hanno user scope, e vi passa il “solito” context e l’elenco dei settings che devono essere salvati.
Nell’esempio mappo i settings, in maniera molto inefficente ma lo scopo del post è la semplicità, su una “tabella” di un db, che è a sua volta mappata su una entity:
Ma come si usa? nulla di più semplice, prendete un qualsiasi file di settings (o quello di default, o uno custom vostro non è importante) e “pigiate” View Code nel designer:
Visual Studio vi aggiunge al progetto una partial class che “estende” la classe dei settings generata dal designer stesso:
[SettingsProvider( typeof( MyDatabaseSettingsProvider ) )]
internal sealed partial class Settings
{
public Settings()
{
this.Context.Add( "MyKey", "MyValue" );
}
}
L’unica cosa che dovete fare è aggiungere in testa alla classe l’attributo SettingsProvider specificando il tipo del vostro provider, Visual Studio provvederà al resto :-).
Che vataggi abbiamo?
- usiamo l’infrastruttura dei settings di Visual Studio senza reinventare la ruota;
- usiamo il designer di Visual Studio, ndr: l’ambiente di sviluppo continuerà a creare il file app.config e continuerà a metterci i valori specificati nel designer, ma il file serve solo a Visual Studio per funzionare non vi serve farne il deploy;
- Abbiamo investito veramente pochissimo tempo (circa 20 minuti) in una soluzione decisamente riutilizzabile;
Nell’esempio di poco sopra utilizzo anche l’istanza del dictionary, che verrà passato al SettingsProvider, sia in fase di get che di set dei settings, per passare dei valori custom; questa cosa è di importanza fondamentale perchè vi permettte ad esempio di avere in file di Settings diversi (che sono classi diverse in namespace diversi per Visual Studio) proprietà con lo stesso nome e poterle tranquillamente persistere nella stessa tabella del db utilizzando ad esempio quella coppia MyKey/MyValue come prefisso per quello specifico file di settings al fine di rendere univoco a livello di solution il nome del setting.
.m
Technorati Tags:
SettingsProvider