Press "Enter" to skip to content

1 – Diamoci un Contesto

Il primo di due articoli per introdurre un concetto parte della logica di una applicazione, il concetto di Contesto dell’applicazione,  inizieremo ad immaginare come costruire un contesto partendo da quello più immediato, un contesto per utilizzare SQL Server.

Introduzione

In qualsiasi applicazione non banale, per far funzionare una interfaccia utente abbiamo bisogno di una serie di dati che si muovono trasversalmente a tutte le .dll che la compongono. Si tratta di quello che io chiamo Contesto di business, o se vogliamo usare l’inglese, il Business Context applicativo. Questo contesto, contiene una serie di dati che devono essere visibili ed utilizzabili in ogni punto dell’applicazione, per fare un esempio pratico e comprensibile, se la nostra applicazione dialoga con un database, il suo contesto potrebbe essere semplicemente la Connection String, se si tratta di una applicazione Web il contesto potrebbe essere il Session State, per una applicazione gestionale, il contesto potrebbe essere composto dalla stringa di connessione applicativa al database e dalle credenziali utente che serviranno in seguito al sistema per determinare quali funzionalità attivare per l’utente o quali dati sono a lui accessibili.

Considerato che un Contesto può contenere qualsiasi tipo e numero di informazioni in base alla nostra applicazione, abbiamo deciso di partire da una semplice interfaccia, definirà quello che devono avere in comune le classi di tipo Context, e poi definiremo il nostro primo context, il contesto base per l’accesso a SQL Server. In seguito, sviluppando ulteriormente applicazioni, potremo se necessario implementare ulteriori informazioni in questa interfaccia.

IBusinessContext

L’interfaccia IBusinessContext ha al momento un solo scopo, permettere di creare se necessario una collezione di contesti di tipo diverso che permetta agevolmente di manipolarli. Pertanto è un interfaccia molto semplice.

	public interface IBusinessContext
	{
		string ContextType { get; }
		string ContextName { get; }
	}

Con questa interfaccia, abbiamo deciso che qualunque contesto deve avere un tipo ed un nome, ma dovendo agganciare in modo non troppo forte i nostri contesti, non abbiamo creato un tipo enumerato ma abbiamo lasciato che sia rappresentato da una stringa. Perché non lasciare solo il nome? Perché ad esempio, in una applicazione potremmo aver bisogno di due connessioni a SQL Server e tre connessioni a Webservices, quindi è opportuno avere un identificativo per la tipologia del contesto oltre ad un nome che lo identifichi in modo univoco.

Un contesto per SQL Server

Il primo contesto che creiamo è il contesto per SQL Server, perché visto che abbiamo spiegato come scrivere gli script SQL per generare un nuovo database e tutti gli oggetti che lo compongono, credo che poter lanciare i comandi sul server da un programma .Net sia uno step interessante per chi legge.

Quali sono le informazioni minimali per connettersi a SQL Server?

  • Nome del server (o istanza nominata)
  • Nome del database
  • Uso della trusted connection
  • Nome Utente
  • Password

Oltre alle informazioni minimali possiamo aggiungere anche due informazioni che possono tornare utili in casi specifici

  • ConnectionTimeout
  • CommandTimeout

I due valori dei timeout possono essere utilizzati quando vi fossero ambienti particolari, come ad esempio una connessione ad un server remoto via VPN per cui potrebbe succedere che i tempi di risposta siano lenti.

Le informazioni di connessione possono essere dati che vanno salvati su file o su database, pertanto creiamo un oggetto per contenerle e lo predisponiamo per la serializzazione, negli articoli passati di questo Blog, vi sono molti esempi relativi alla serializzazione di classi in XML, la più comune e la più semplice da ottenere in .NET ma negli ultimi anni, c’è un nuovo formato di serializzazione strutturato che è stato studiato per il web e per le device di tipo mobile molto più compatto del formato XML, il nuovo formato di serializzazione JSON sarà da noi utilizzato per le classi ove necessario, vedremo quindi quanto fornito da .NET 4.0 nel namespace System.Runtime.Serialization.

Visto che lavoriamo in .NET e usiamo l’ OOP (Object oriented programming) prima di creare la classe introduciamo un paio di osservazioni in merito.

  1. La connessione a SQL Server per una applicazione potrebbe non essere una sola, potremmo voler utilizzare dati da più database magari per fare delle analisi o delle elaborazioni.
  2. Potremmo avere la necessità di riconoscere le connessioni in modo univoco.
  3. Visto che le connessioni potrebbero anche essere effettuate a database diversi, è opportuno predisporre delle informazioni comuni a tutti i tipi di connessione.

Partiamo quindi con una classe base che contiene i dati che siamo certi saranno comuni a tutte le connessioni indipendentemente dal tipo, ovvero i dati di identificazione della connessione stessa.

ConnectionInfoBase

Questa classe, essendo comune a tutti i tipi di connessione che nel tempo andremo a sviluppare, la inseriremo all’interno della libreria di base che abbiamo creato a suo tempo per implementare il sistema di Log. Considerato che si tratta di una classe che contiene dati, ed una classe che probabilmente useremo per passare informazioni in varie modalità in futuro, ci da lo spunto per riportare dalle librerie originali create con il framework 2.0 l’interfaccia IEntity, un interfaccia che utilizzeremo per poter collegare trasversalmente tutte le nostre future classi dati.

using System;
using System.ComponentModel;

namespace Dnw.Base.Entities
{
	public interface IEntity :  INotifyPropertyChanged
	{

		bool IsValid {get;}

	}
}

Si tratta di un interfaccia molto semplice, ha una sola property, “IsValid” che come il suo nome dice dovrà essere implementata in modo che dichiari se un oggetto di tipo IEntity è valido, inoltre implementa un ulteriore interfaccia, INotifyPropertyChanged, un interfaccia che stabilisce che la classe implementerà l’evento “OnPropertyChanged” che sarà sollevato ove pertinente alla modifica delle sue property. Questa interfaccia è la più importante per le classi dati quando vogliamo fare in modo che possano essere utilizzate per l’inserimento e la modifica a livello di interfaccia utente, l’OnPropertyChanged è infatti l’evento utilizzato dagli oggetti WPF per rilevare le modifiche a livello di modello e riportarle sull’interfaccia utente.

Vediamo ora il codice della classe ConnectionInfoBase che commenteremo per parti.

using System.ComponentModel;
using System.Runtime.Serialization;

namespace Dnw.Base.Entities
{
	[DataContract(Name="CNBASE")]
	public abstract class ConnectionInfoBase : IEntity
	{

la parte dichiarativa, abbiamo agganciato System.ComponentModel per l’evento PropertyChanged, e System.Runtime.Serialization per le dichiarazioni relative a JSON, la cui prima dichiarazione è l’attributo DataContract, che indica al serializzatore che questa classe è serializzabile in JSON e qual’è il nome che assume all’interno dei dati serializzati. Il fatto di indicare un nome alla classe e, in seguito vedremo che lo indicheremo anche per le property salvate è un modo per evitare problemi di importazione se in futuro decideremo di cambiare i nomi alle proprietà o alla classe per qualsiasi necessità di sviluppo. Serve anche a rendere più compatto il codice JSON utilizzando dei nomi brevi invece di quelli dichiarativi usati in C#. Inoltre abbiamo dichiarato la classe abstract, ovvero non può essere istanziata direttamente ma deve per forza essere derivata, questo perché vi sono due proprietà , ConnectionString e IsValid che dipenderanno sempre dall’implementazione nelle classi derivate.

public const string FLD_ConnectionID = "ConnectionID";
public const string FLD_Description = "Description";
public const string FLD_Position = "Position";

Le costanti che dichiarano i nomi delle property utilizzate a livello di evento PropertyChanged.

private string mConnectionID;
private string mDescription;
private int mPosition;

Le variabili member per le property

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
	if (PropertyChanged != null)
		PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

L’implementazione dell’evento OnPropertyChanged

public abstract string ConnectionString { get; }
public abstract bool IsValid { get; }

[DataMember(Name = "ID", EmitDefaultValue = false)]
public string ConnectionID
{
	get
	{
		return mConnectionID;
	}
	set
	{
		mConnectionID = value;
		OnPropertyChanged(FLD_ConnectionID);
	}
}

[DataMember(Name = "DD", EmitDefaultValue = false)]
public string Description
{
	get
	{
		return mDescription;
	}
	set
	{
		mDescription = value;
		OnPropertyChanged(FLD_Description);
	}
}

[DataMember(Name = "POS", EmitDefaultValue = false)]
public int Position
{
	get
	{
		return mPosition;
	}
	set
	{
		mPosition = value;
		OnPropertyChanged(FLD_Position);
	}
}

}

Le property della classe, notiamo le due proprietà abstract, che obbligheranno ad implementare queste due property nelle classi derivate, inoltre, abbiamo implementato gli attributi DataMember per le property indicando il nome di serializzazione e usando l’opzione EmitDefaultValue = false per evitare che le property siano serializzate se contengono il valore di default per il loro tipo, ovvero null per le stringhe e 0 per gli interi. Un altra delle opzioni di JSON per rendere più compatto il contenuto.

SqlConnectionInfo

Deriviamo dalla classe base, la classe specifica per SQL Server:

using Dnw.Base.Entities;
using System.Runtime.Serialization;

namespace Dnw.Base.Data.SqlServer.Entities
{
[DataContract(Name = "SqlCnI")]
public class SqlConnectionInfo : ConnectionInfoBase
{

Usiamo gli stessi namespace della precedente e implementiamo il DataContract per la serializzazione JSON.

[DataMember(Name = "SRV", EmitDefaultValue = false)]
public string Server
{
	get
	{
		return mServer;
	}
	set
	{
		mServer = value;
		OnPropertyChanged(FLD_Server);
	}
}

[DataMember(Name = "TRS", EmitDefaultValue = false)]
public bool Trusted
{
	get
	{
		return mTrusted;
	}
	set
	{
		mTrusted = value;
		OnPropertyChanged(FLD_Trusted);
	}
}

[DataMember(Name = "CNT", EmitDefaultValue = false)]
public int ConnectionTimeout
{
	get
	{
		return mConnectionTimeout;
	}
	set
	{
		mConnectionTimeout = value;
		OnPropertyChanged(FLD_ConnectionTimeout);
	}
}

[DataMember(Name = "CMT", EmitDefaultValue = false)]
public int CommandsTimeout
{
	get
	{
		return mCommandsTimeout;
	}
	set
	{
		mCommandsTimeout = value;
		OnPropertyChanged(FLD_CommandsTimeout);
	}
}

[DataMember(Name = "DB", EmitDefaultValue = false)]
public string Database
{
	get
	{
		return mDatabase;
	}
	set
	{
		mDatabase = value;
		OnPropertyChanged(FLD_Database);
	}
}

[DataMember(Name = "USR", EmitDefaultValue = false)]
public string Username
{
	get
	{
		return mUsername;
	}
	set
	{
		mUsername = value;
		OnPropertyChanged(FLD_Username);
	}
}

[DataMember(Name = "PWD", EmitDefaultValue = false)]
public string Password
{
	get
	{
		return mPassword;
	}
	set
	{
		mPassword = value;
		OnPropertyChanged(FLD_Password);
	}
}

Abbiamo omesso la dichiarazione delle costanti e delle variabili member che troverete all’interno del progetto ed abbiamo creato le property per tutti gli elementi necessari ad una connessione a SQL Server.

public override bool IsValid
{
	get
	{
		return (!this.ConnectionID.XDwIsNullOrTrimEmpty() &&
			!this.Server.XDwIsNullOrTrimEmpty() &&
			!this.Database.XDwIsNullOrTrimEmpty());
	}
}

L’implementazione della property IsValid richiesta dalla classe base.

public override string ConnectionString
{
	get
	{
		string ret = string.Empty;
		if (Trusted)
		{
			if (ConnectionTimeout > -1)
			{
				ret = string.Format(SqlConstants.FMP_SQL_ConnectionTrustedTimeouted, Server, Database, ConnectionTimeout);
			}
			else
			{
				ret = string.Format(SqlConstants.FMP_SQL_ConnectionTrusted, Server, Database);
			}
		}
		else
		{
			if (ConnectionTimeout > -1)
			{
				ret = string.Format(SqlConstants.FMP_SQL_ConnectionTimeouted, Server, Database, Username, Password, ConnectionTimeout);
			}
			else
			{
				ret = string.Format(SqlConstants.FMP_SQL_Connection, Server, Database, Username, Password);
			}
		}
		return (ret);
	}
}

L’implementazione della property che genera la connection string in modalità Trusted (windows) oppure SQL Security tenendo conto dell’eventuale presenza del Timeout di connessione. Le costanti che forniscono la base per la generazione della stringa di connessione, le trovate in SqlConstants.cs, e hanno la seguente forma:

public const string FMP_SQL_Connection = "data source={0};persist security info=True;initial catalog={1};user id={2};password={3};";
public const string FMP_SQL_ConnectionTimeouted = "data source={0};persist security info=True;initial catalog={1};user id={2};password={3};connection timeout={4};";
public const string FMP_SQL_ConnectionTrusted = "integrated security=SSPI;data source={0};initial catalog={1};";
public const string FMP_SQL_ConnectionTrustedTimeouted = "integrated security=SSPI;data source={0};initial catalog={1};connection timeout={2};";

Conclusioni

Con le due classi e relative interfacce, abbiamo creato i dati di supporto al contesto per la connessione a SQL Server, nonché il necessario per poter acquisire e memorizzare le informazioni di una connessione. Nella seconda parte dell’articolo creeremo la classe Context vera e propria.

Il progetto esempio che contiene il codice per questo articolo ed il successivo è disponibile al link seguente:

La parte di uso comune, e le classi riusabili in più progetti sono disponibili al link seguente:

Per qualsiasi commento, domanda, approfondimento, usate pure il link al modulo di contatto in cima alla pagina.