In questo post riporto "as is" l'articolo che ho pubblicato su Visual Basic Journal di alcuni mesi fa, a proposito della programmazione di SharePoint tramite il suo Object Model.
_____
Microsoft Office SharePoint Server 2007 (MOSS2007) è un framework applicativo principalmente pensato per la realizzazione di soluzioni web di collaborazione, gestione documentale e publishing. Come probabilmente saprete esiste una versione gratuita di SharePoint, chiamata Windows SharePoint Services 3.0 (WSS3), che sta alla base anche di MOSS2007, che semplicemente lo estende in termini di funzionalità e servizi accessori. Ad esempio MOSS2007, nelle sue varie versioni e licenze, offre un motore di ricerca evoluto e di livello enterprise, fornisce un motore di Single Sign On, consente il collegamento a sistemi e software gestionali esterni tramite il Business Data Catalog, consente di gestire integrazioni con Microsoft Office Form Server e Microsoft Office Excel Services, ecc.
Tutti questi servizi e funzionalità sono utilizzabili “out of the box” e gestibili con strumenti già pronti per l’uso, come Microsoft Office SharePoint Designer 2007 e Microsoft Office 2007 in generale.
Sviluppare soluzioni SharePoint
Molto spesso però non è sufficiente appoggiarsi a questi strumenti e occorre sviluppare delle personalizzazioni, per integrare le proprie soluzioni con il motore di WSS3/MOSS2007.
Di seguito sono elencati alcuni dei più comuni ambiti di personalizzazione e sviluppo di soluzioni SharePoint:
- WebPart: controlli web pensati per arricchire la classica interfaccia utente di SharePoint, definibili e configurabili anche dall’utente finale, che si integrano con l’interfaccia web di SharePoint. Richiamano in tutto e per tutto il concetto di WebPart di ASP.NET 2.0, in quanto ne rappresentano un’estensione.
- EventHandler: sono classi che intercettano azioni eseguite dagli utenti (inserimento di un file, cancellazione di un item da una lista, ecc.) e consentono di svolgere azioni correttive, se invocati pre-evento, o consequenziali, se invocati post-evento.
- List template: descrivono modelli di liste di documenti o elementi (contatti, appuntamenti, task, ecc.) utilizzabili per creare N diverse istanze delle stesse, tutte con la stessa struttura e comportamento, in base a quanto definito nel modello.
- Site template: spesso si realizzano soluzioni basate su N siti web tutti con lo stesso modello grafico e la stessa struttura di contenuti (liste, cartelle documenti, ecc.). Si pensi alla gestione dei clienti e alla realizzazione di un mini-portale web, per ciascun cliente o area commerciale, con le cartelle per le fatture attive, passive, gli indirizzi, gli appuntamenti, ecc. Creare un site template corrisponde a formalizzare uno schema dei contenuti, delle personalizzazioni e del layout grafico di base, da applicare poi a tutte le singole istanze di sito web, costruite secondo quel modello.
- Workflow: sono processi di business realizzati con Windows Workflow Foundation e che possono arricchire di funzionalità e comportamenti le liste di file e contenuti o i modelli di documento. Ad esempio è possibile associare un workflow di approvazione ad ogni documento di tipo “offerta”, oppure collegare un workflow a qualsiasi documento inserito in una specifica cartella, oppure ancora definire un flusso da eseguire ogni volta che viene inserito un appuntamento in un’agenda on-line.
Vi sono molti altri ambiti in cui può essere necessario sviluppare porzioni di codice per SharePoint, ma quelli appena visti sono sicuramente tra i più diffusi. Ciò che conta maggiormente però è il fatto che qualunque tipo di soluzione si vada ad implementare, uno degli strumenti principali da utilizzare è l’Object Model di SharePoint.
SharePoint Object Model
Dal momento che SharePoint persiste sia la propria configurazione che la configurazione e i contenuti dei suoi siti su dei database applicativi, basati su Microsoft SQL Server, è necessario appoggiarsi ad un modello ad oggetti per poter operare in modo sicuro e corretto.
Lo SharePoint Object Model è un framework di classi, realizzato con .NET Framework e parzialmente offuscato, che consente di lavorare e interagire con tutti gli strumenti e gli oggetti definiti all’interno di una server farm SharePoint.
È una libreria di classi che è disponibile su qualsiasi server sul quale siano configurati i moduli di SharePoint. Non è pensato per essere referenziato ed utilizzato da applicazioni o servizi che girano su server diversi da quelli preposti ad ospitare SharePoint. Esistono infatti dei web service, pubblicati da SharePoint, pensati per tutte quelle situazioni in cui sia necessario utilizzare i server MOSS2007/WSS3 da server o applicazioni remote.
Esistono poco più di una ventina di namespace .NET che raggruppano diverse migliaia di classi. Il namespace radice si chiama Microsoft.SharePoint e contiene numerosi altri namespace, alcuni dei più importanti sono:
- Microsoft.SharePoint.Administration: contiene le classi per l’amministrazione e la gestione della topologia di soluzioni SharePoint.
- Microsoft.SharePoint.WebControls: racchiude i controlli e le classi per la realizzazione di WebPart e WebControl.
- Microsoft.SharePoint.Workflow: rappresenta il namespace preposto a definire le classi relative all’integrazione tra SharePoint e Windows Workflow Foundation.
- Microsoft.SharePoint.Portal.Search: contiene le classi relative al motore di ricerca, presente solo in MOSS2007 e non in WSS3.
- Microsoft.SharePoint.Portal.WebControls: controlli web specifici di MOSS2007.
- Microsoft.SharePoint.Portal.SingleSignOn: le classi che afferiscono al motore di Single Sign On di MOSS2007.
La topologia di una soluzione SharePoint prevede la definizione di una SharePoint Server Farm, rappresentata da oggetti di tipo SPFarm. Una SPFarm consente di gestire la configurazione della farm, importare/esportare utenti definiti nella farm stessa e svolgere operazioni di backup e restore dei servizi configurati. Di seguito un esempio di codice per accedere ad una SPFarm, in base alla stringa di connessione del suo database di configurazione:
SPFarm farm = SPFarm.Open("server=localhost;database=SharePoint_Config;integrated security=SSPI;");
Console.WriteLine("The current SPFarm version is: {0}", farm.BuildVersion);
Console.WriteLine("The SPFarm name is: {0}", farm.DisplayName);
Una SPFarm è costituita da una o più Web Application, che in SharePoint Object Model si chiamano SPWebApplication. Queste ultime corrispondo ai siti IIS configurati sui server web di pubblicazione. Da una SPWebApplication si gestiscono le configurazioni della singola applicazione. Ad esempio di seguito si utilizza un oggetto SPWebApplication per configurare l’accesso anonimo all’applicazione:
SPWebApplication webApplication = SPWebApplication.Lookup(new Uri(http://moss2007dev:9501/));
webApplication.IisSettings[SPUrlZone.Default].AllowAnonymous = true;
webApplication.Update();
Come si vede già da questo primo esempio, le modifiche agli oggetti devono sempre essere confermate invocando l’apposito metodo Update(). Infatti mentre si lavora con gli oggetti si modifica solo una copia in memoria di quello che deve poi essere persistito nel database di configurazione o applicativo, a seconda dei casi.
All’interno di ciascuna Web Application trovano posto le Site Collection, oggetti di tipo SPSite, che non sono altro che insiemi di Siti Web, corrispondenti ad oggetti di tipo SPWeb. Gli SPSite raggruppano diversi SPWeb, consentendo di condividere il database di contenuti, gli utenti e i gruppi SharePoint, oltre ad alcune impostazioni generali.
Ecco un esempio di codice che scorre tutti gli SPSite configurati su una SPWebApplication, per andare poi a mostrare le singole istanze di SPWeb in essi cotenute.
SPWebApplication webApplication = SPWebApplication.Lookup(new Uri("http://moss2007dev:9501/"));
Console.WriteLine("\nHere are the Sites in the SPWebApplication:");
foreach (SPSite site in webApplication.Sites)
{
Console.WriteLine("\tPortal Name: {0}\n\tURL: {1}",
site.PortalName, site.Url);
Console.WriteLine("\n\tHere are the Webs in the SPSite:");
foreach (SPWeb web in site.AllWebs)
{
Console.WriteLine("\tTitle: {0}\n\tURL: {1}\n\tTemplate: {2}",
web.Title, web.Url, web.WebTemplate);
}
}
Il codice di esempio appena visto è definito all’interno di un’applicazione esterna, in questo caso specifico si tratta di un’applicazione Console, che interagisce con SharePoint. Molto spesso però il codice che scriviamo gira all’interno di pagine ASP.NET o WebPart, che in realtà sono ospitate all’interno di istanze di SPWeb. Per questo motivo gli oggetti SPWeb e SPSite possono essere costruiti anche sfruttando direttamente il contesto ASP.NET delle richieste che si stanno evadendo, per ricavare automaticamente il contesto SharePoint.
SPSite site = SPControl.GetContextSite(Context);
SPWeb web = SPControl.GetContextWeb(Context);
L’esempio appena riportato descrive come accedere al SPSite e/o SPWeb corrente da una Web Part configurata in MOSS2007/WSS3. L’oggetto SPControl utilizzato, si trova nel namespace Microsoft.SharePoint.WebControls, proprio ad indicare che è di uso ricorrente da parte dei Web Control ASP.NET creati per girare in SharePoint.
Lavorare con le liste
A prescindere dal fatto che si lavori con applicazioni esterne (servizi del sistema operativo, moduli batch da Console, programmi Windows Forms, ecc.) referenziando gli SPSite e SPWeb tramite la loro URI, oppure con pagine ASP.NET e WebPart, utilizzando il contesto ASP.NET, tipicamente questi oggetti sono utilizzati non tanto o non solo per variare la configurazione dell’ambiente, ma soprattutto per accedere in lettura e scrittura ai contenuti del portale.
Attraverso un’istanza di SPWeb si può avere accesso, da codice, alle liste di contenuti e documenti presenti all’interno di un portale SharePoint, sia esso MOSS2007 o WSS3. Infatti ogni SPWeb espone la proprietà Lists, di tipo SPListCollection, tramite la quale si possono scorrere e ricercare le singole liste di contenuti. Pensiamo allora ad un classico portale SharePoint di collaborazione (detto “Team Site”), dove sia presente una lista di “Contacts”. Il seguente codice mostra come ottenere e sfogliare l’elenco dei singoli contatti in essa contenuti:
SPWeb web = SPControl.GetContextWeb(Context);
SPList listContacts = web.Lists["Contacts"];
foreach (SPListItem contact in listContacts.Items)
{
Respone.Write(String.Format("Last Name: {0} - EMail: {1}",
contact["Title"], contact["Email"]));
}
Come si vede dall’esempio appena riportato, ogni item di una lista SharePoint è descritto da un oggetto di tipo SPListItem, il quale espone un dictionary di proprietà che corrispondono ai campi informativi dell’elemento. Si noti che le chiavi utilizzate per accedere i singoli campi non corrispondo necessariamente ai nomi dei campi stessi (“Last Name” diventa “Title”). Inoltre ogni campo di un SPListItem ha un nome pubblico e descrittivo, chiamato Title, e un nome interno, chiamato InternalName. Nel nostro esempio il campo con Title “Last Name” ha un InternalName pari a “Title”. La ricerca dei campi su un SPListItem avviene sulla base dell’InternalName, che è case sensitivi. Per avere un elenco preciso dei nomi dei campi disponibili per gli SPListItem di un’istanza di SPList possiamo utilizzare la proprietà Fields dell’oggetto che descrive la lista.
SPWeb web = SPControl.GetContextWeb(Context);
SPList listContacts = web.Lists["Contacts"];
SPWeb web = new SPSite("http://moss2007dev/").OpenWeb();
foreach (SPField field in listContacts.Fields)
{
Respone.Write(String.Format("Title: {0} - Internal Name: {1} - Type: {2}",
field.Title, field.InternalName, field.TypeAsString));
}
In alternativa possiamo utilizzare la proprietà SchemaXml che caratterizza ogni istanza di SPList, per avere la sua struttura in un solo passaggio, in formato XML.
Un’operazione ricorrente, oltre allo sfogliare una lista, è creare nuovi elementi al suo interno ovvero creare liste ex-novo. Di seguito è riportato un esempio di codice che aggiunge un contatto alla lista appena sfogliata:
SPWeb web = new SPSite("http://moss2007dev/").OpenWeb();
SPList listContacts = web.Lists["Contacts"];
SPListItem newContact = listContacts.Items.Add();
newContact["Title"] = "Pialorsi";
newContact["First Name"] = "Paolo";
newContact["Email"] = "paolo@devleap.it";
newContact.Update();
In questo caso l’accesso all’istanza di SPWeb avviene tramite il metodo OpenWeb() dell’oggetto SPSite. Di fatto questo metodo, se invocato nel suo overload senza parametri, restituisce l’istanza di SPWeb corrispondente al sito web radice della Site Collection corrente. Sottolineo il fatto che l’elemento deve prima di tutto essere aggiunto alla lista, tramite il metodo Add() della collezione di Items, quindi solo dopo aver valorizzato i suoi campi può essere salvato, chiamandone il metodo Update(), già visto in precedenza.
Nel prossimo estratto di codice invece si crea, completamente da zero, una lista di clienti, con titoli “Clienti”, in un portale di collaborazione SharePoint:
SPWeb web = SPControl.GetContextWeb(Context);
Guid newListId = web.Lists.Add("Clienti", "Lista Clienti", SPListTemplateType.Contacts);
Response.Write(String.Format("List {0} created.", web.Lists[newListId].Title));
La lista viene aggiunta, come accadeva per gli SPListItem, alla collezione delle Lists del SPWeb. Il risultato del metodo di Add(...) è il GUID che identifica univocamente in SharePoint la lista appena creata. Come si vede il tipo di lista è ottenuto tramite una enumerazione di tipologie predefinite (SPListTemplateType). In realtà è anche possibile utilizzare template di liste personalizzati, per creare da codice N istanze di liste secondo modelli proprietari. Non soffermiamoci oltre su aspetti di dettaglio in un articolo introduttivo come questo.
Gestire file e documenti
Uno degli aspetti più interessanti e rivoluzionari di SharePoint è il fatto che è possibile utilizzare portali e soluzioni SharePoint come valide alternative a sistemi di file sharing tradizionali. Le soluzioni di archiviazione documentale basate su WSS3/MOSS2007 sono gestibili non solo attraverso l’interfaccia utente di SharePoint, Microsoft Office e Windows Explorer, ma anche attraverso l’Object Model. Ogni lista di documenti, dal punto di vista di SharePoint, è innanzitutto una SPList, specializzata nel tipo SPDocumentLibrary. In pratica ogni SPDocumentLibrary non è altro che una classe, derivata da SPList, che specializza alcuni dei suoi comportamenti e funzionalità. Mentre gli elementi di una SPList tradizionale sono oggetti di tipo SPListItem, i documenti all’interno di una SPDocumentLibrary sono oggetti di tipo SPFile. A ciascun SPFile corrisponde però anche un SPListItem che rappresenta il contenitore di meta-informazioni legate al singolo file (autore, data di upload, versione, ecc. e campi personalizzati).
Di seguito è riportato un esempio di codice per leggere i file presenti in una document library:
SPWeb web = new SPSite("http://moss2007dev/").OpenWeb();
SPDocumentLibrary doclib = web.Lists["Shared Documents"] as SPDocumentLibrary;
Console.WriteLine("\n\tHere are the Files in the SPDocumentLibrary:");
foreach (SPFile file in doclib.RootFolder.Files)
{
Console.WriteLine("\tTitle: {0}\n\tUrl: {1}\n\tAuthor: {2}",
file.Title, file.Url, file.Author.Name);
}
Qualora si voglia estrarre da SharePoint il contenuto binario di un file, è possibile accedervi tramite un oggetto di tipo System.IO.Stream, nel modo seguente:
SPWeb web = new SPSite("http://moss2007dev/").OpenWeb();
SPDocumentLibrary doclib = web.Lists["Shared Documents"] as SPDocumentLibrary;
SPFile fileInstance = doclib.RootFolder.Files["Notes.txt"];
Stream fileStream = fileInstance.OpenBinaryStream();
using (StreamReader sr = new StreamReader(fileStream))
{
String data = sr.ReadToEnd();
Console.WriteLine(data);
}
Anche il salvataggio di un file in una cartella documenti è un’operazione abbastanza banale:
SPWeb web = new SPSite("http://moss2007dev/").OpenWeb();
SPDocumentLibrary doclib = web.Lists["Shared Documents"] as SPDocumentLibrary;
String fileContentAsText = "Sample document content!";
Byte[] fileContent = Encoding.Unicode.GetBytes(fileContentAsText);
SPFile fileUploaded = doclib.RootFolder.Files.Add("sample.txt", fileContent);
fileUploaded.Item["Title"] = "Sample Document";
fileUploaded.Item.Update();
fileUploaded.Update();
Come si vede possiamo scrivere il contenuto del file sotto forma di array di Byte, oppure anche come Stream sfruttando un overload differente del metodo Add(...) della lista di Files corrente. Ogni SPFile prevede poi una proprietà di nome Item che corrisponde al SPListItem sottostante. Ogni lista di documenti infatti è prima di tutto una lista di SPListItem, che hanno però una serie di caratteristiche in più finalizzate a descrivere il file rappresentato. Nuovamente si sottolinea l’importanza dell’invocazione del metodo Update() sul SPFile e sul SPListItem sottostante.
Un’altra attività abbastanza semplice e ricorrente è il check-in/check-out dei documenti da modello ad oggetti. SharePoint consente infatti di gestire politiche di accesso concorrente ai documenti, sfruttando regole di check-out esclusivo dei file e di check-in al termine delle attività di modifica. Ecco un esempio di codice in grado di prendere un documento, farne il check-out e successivamente il check-in, il tutto da Object Model.
SPWeb web = new SPSite("http://moss2007dev/").OpenWeb();
SPDocumentLibrary doclib = web.Lists["Shared Documents"] as SPDocumentLibrary;
String fileContentAsText = "Sample document content!";
Byte[] fileContent = Encoding.Unicode.GetBytes(fileContentAsText);
SPFile fileUploaded = doclib.RootFolder.Files.Add("sample.txt", fileContent);
fileUploaded.Item["Title"] = "Sample Document";
fileUploaded.Item.Update();
fileUploaded.Update();
fileUploaded.CheckOut();
Stream fileStream = fileUploaded.OpenBinaryStream();
using (StreamReader sr = new StreamReader(fileStream))
{
String data = sr.ReadToEnd();
data = data.ToUpper();
fileUploaded.SaveBinary(Encoding.Unicode.GetBytes(data));
}
fileUploaded.Update();
fileUploaded.CheckIn("Upper case done.");
I metodi più importanti di questo esempio sono CheckOut() e CheckIn(...), dove il nome indica già in modo inequivocabile lo scopo di ciascuno dei due. Volendo è anche possibile annullare un’operazione di check-out, invocando il metodo di UndoCheckOut().
Conclusioni
In questo articolo abbiamo “assaggiato” alcune delle potenzialità del modello ad oggetti di SharePoint. È importante ricordare che lo SharePoint Object Model è fruibile da qualsiasi postazione abbia SharePoint installato. Inoltre è fondamentale sottolineare che il codice che usa l’Object Model dall’interno di Web Part o controlli ASP.NET, deve essere autorizzato a farlo, tramite apposite configurazioni di Code Access Security (CAS), altrimenti il codice non sarà autorizzato a girare e non produrrà alcun effetto, anzi in alcune circostanze determinerà errori.
In articoli futuri approfondiremo alcuni di questi e altri temi relativi allo sviluppo per SharePoint, come per esempio la creazione di EventHandler e di Workflow personalizzati, l’utilizzo dei Web Service di SharePoint, per la gestione dei contenuti da remoto, e la configurazione della Code Access Security.
Riferimenti
[1] http://msdn.microsoft.com/sharepoint/
[2] http://msdn2.microsoft.com/en-us/library/bb530302.aspx
[3] http://msdn2.microsoft.com/en-us/library/bb530301.aspx
[4] http://msdn2.microsoft.com/en-us/library/bb153523.aspx