… 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:
image
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:
image
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