Con questo post si conclude la serie dedicata alla creazione di
un provider per Monad. In realtà è molto probabile che torni ulteriormente su
Monad che ha su di me un fascino irresistibile, ma per ora mi appresto a
concludere la serie che mi ha visto affrontare questo interessante
argomento.
Nel precedente post ci siamo soffermati su come fare ad
inizializzare il provider per rispondere ad un drive di default, tuttavia devo
segnalare che esiste un interessante cmdlet che consente di creare dei "drive
virtuali" mappati su un qualsiasi percorso gestito da un provider. Si tratta di
new-drive della quale riporto un esempio:
new-drive K FileSystem d:\downloads
Questo semplice comando crea un drive K mappato su una
cartella del filesystem. Questa cmdlet è in effetti supportata anche dal
provider di cui sto parlando. A mio parere si tratta di una feature
interessante e utile che rispetto ai mapped drives di windows ha certamente una
marcia in più.
La prima operazione che ci aspetteremo di poter compiere su
un provider è quella con la quale siamo tipicamente abituati a navigare
all'interno del filesystem. Mi riferisco al comando "cd" che in Monad
corrisponde a set-location, che però annovera tra i suoi alias anche lo stesso
"cd". Il metodo che corrisponde alla set-location di Monad è ItemExists(), il
quale si aspetta di verificare se un percorso specificato esiste oppure no. Il
seguente comando ad esempio ha l'effetto di spostare il percorso corrente sul
database locale:
set-location
"sql:\(local)"
I doppi apici sono necessari in quanto le parentesi sono dei
caratteri che per Monad hanno un preciso significato. Questo comando, invocato
da console, ha l'effetto di invocare il seguente metodo:
protected override bool ItemExists(string path)
{
SqlPathInfo pathInfo = new SqlPathInfo(path);
if (pathInfo.Level == PathLevel.Root)
return true;
else if (pathInfo.Level == PathLevel.Server)
return ServerExists(pathInfo);
else if (pathInfo.Level == PathLevel.Database)
return ServerExists(pathInfo);
else if (pathInfo.Level == PathLevel.DbObject)
return DbObjectExists(pathInfo);
return false;
}
Si tratta dell'ennesimo overload di un metodo virtuale del
provider che stiamo creando. ItemExists(), si distingue da IsValidPath() per lo
scopo cui è preposto; esso infatti ha l'obbiettivo di verificare che
un path, la cui correttezza formale è stata precedentemente verificata da
IsValidPath(), corrisponda ad un elemento che esiste effettivamente.
Infatti, il path che passiamo a set-location, pur se formalmente corretto, cioè
composto dalla corretta sequenza di caratteri e backslash, potrebbe inviarci ad
una risorsa che non è presente. In questo casi ItemExists() deve ritornare
false. Il metodo che ho riportato fa uso di una classe appositamente realizzata
che dato un path lo scompone nelle sue parti fondamentali usando una regular
expression e ne ritorna le informazioni. In particolare ad ogni possibile
livello (root>server>database>tabella) corrisponde una verifica di
esistenza specifica.
L'altra operazione che a rigor di logica ci aspetteremo di
poter compiere nella navigazione è la comunissima "dir", ovvero l'elenco del
contenuto di una "cartella". Nel gergo di Monad, questa operazione si chiama
get-childitem e risponde al metodo GetChildItems() del provider. Tale metodo è
implementato in modo analogo al precedente:
protected override void GetChildItems(string path, bool recurse)
{
SqlPathInfo pathInfo = new SqlPathInfo(path);
if (pathInfo.Level == PathLevel.Root)
GetAvailableServers(pathInfo, path);
else if (pathInfo.Level == PathLevel.Server)
GetServerDatabases(pathInfo, path);
else if (pathInfo.Level == PathLevel.Database)
GetDatabaseTables(pathInfo, path);
else if (pathInfo.Level == PathLevel.DbObject)
GetTableFields(pathInfo, path);
}
Tutta la logica è contenuta nei metodi GetAvailableServers,
GetServerDatabases, GetDatabaseTables e GetTableFields che fanno esattamente
quello che ci si aspetta a prima vista. Essi interrogano lo schema del database
per ottenere la visualizzazione richiesta. Questo vale per tutti tranne che per
il primo che invece fa uso di una classe che cerca sulla rete i server sql
disponibili.
E'interessante approfondire almeno uno di questi
metodi, perchè dimostrano come si debbano restituire le informazioni a Monad. Se
ricordate infatti, nel primo post della serie avevo introdotto come i risultati
che leggiamo sulla console altro non siano che la rappresentazone testuale di
una serie di oggetti .NET per precisi. In questo caso i metodi restituiscono una
collection di oggetti che incapsulano queste informazioni e le espongono per
mezzo di proprietà.
private void GetAvailableServers(SqlPathInfo pathInfo, string path)
{
List<SqlServerInfo> servers = new List<SqlServerInfo>();
SQLInfoEnumerator enumerator = new SQLInfoEnumerator();
foreach (string sqlServer in enumerator.EnumerateSQLServers())
servers.Add(new SqlServerInfo(sqlServer));
WriteItemObject(servers, path, true);
}
La collection di oggetti di tipo SqlServerInfo, restituita a
Monad fa si che venga creata una tabella le cui colonne sono costituite
dalle proprietà pubbliche di questo oggetto. Non solo, se ad esempio nella
classe SqlServerInfo implementassimo un metodo Start() che avvia l'istanza in
questione saremmo in grado di eseguire questo metodo per ogni istanza di oggetto
nella collection e quindi indirettamente potremmo intervenire sullo stato del
database.
Tutto questo a mio parere è di una eleganza che non ha pari.
Una shell così concepita ha delle potenzialità eccezionali, dato che consente
una manipolazione molto sottile dell'ambiente in cui viene eseguita. Se mettete
in conto che la shell dispone di molti dei comandi che ci aspetteremo da un
linguaggio di programmazione evoluto ecco che diventa subito evidente il perchè
riferendosi a Monad si usi a ragione la locuzione "di nuova
generazione".