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".

powered by IMHO 1.3


per leggere il post originale o inviare un commento visita il seguente indirizzo: Scrivere un provider per Monad (MSH) #3: Navigare lo schema