SharePoint Page Model: Digressioni post Corso
Ieri ed oggi in aula, durante un corso ultraconcentrato sullo sviluppo con SharePoint 2007, ho illustrato nel (purtroppo) poco tempo a disposizione il page model che SharePoint adotta all'atto di servire pagine aspx all'utente.
Il "dove" risiede fisicamente una pagina ospitata da una web application estesa da WSS è una di quelle domande che si porta dietro la risposta più gettonata - nonchè odiata: dipende :-)
Provo a schematizzare in poche [udpate: mica tanto poche ... ] righe il succo del discorso, sperando che sia utile (a) come riassunto per chi ha seguito il corso e (b) in generale come spiegazione semistrutturata e pseudocolloquiale.
Una prima distinzione, molto netta e con diversi corollari, è quella che intercorre fra Application Pages e Site Pages.
Le Application Pages sono fisicamente memorizzate sui file system dei front end server che compongono la nostra farm.
Ne troviamo decine semplicemente andando a spulciare in [12]\Template\Layout.
Con un po' di curiosità e di spirito di iniziativa possiamo localizzare e, magari, aprire ed ispezionare le varie create.aspx, settings.aspx, people.aspx, mngctype.aspx, che probabilmente, se conosciamo un poco SharePoint anche solo come PowerUser++, abbiamo invocato almeno una volta per configurare i parametri di un web o di una site collection.
Le Application Pages *non vanno midificate*. Ripetete con me: le appl.....
Nulla ci vieta, però, di scrivere le nostre application pages. Per estendere le funzionalità di configurazione di SharePoint esposte via browser. O per creare una visualizzazione di dettaglio su alcune proprietà nascoste dall'interfaccia web ma disponibili (quello sì) via Object Model al volenteroso programmatore ASP.NET.
Scrivere una pagina applicativa non richiede accorgenze particolari (cosa intendo per "accorgenze particolari" spero sia più chiaro nelle prossime righe :-).
Scrivere una pagina applicativa *può* essere estremamente conveniente dal punto di vista meramente prestazionale.
La pagina è caricata dal runtime di ASP.NET direttamente dal filesystem ed è sottoposta al classico meccanismo di compilazione, con evidenti vantaggi dal punto di vista delle performance. E con ulteriori vantaggi, se volete, nella riduzione del footprint sulla memoria: una "istanza" di pagina potenzialmente serve tutti i web site che abbiamo creato :-)
Ovviamente le Application Pages non sono (non possono essere) la soluzione per tutte le esigenze.
Le application pages non supportano la personalizzazione (ricordate ? risiedono esclusivamente sul filesystem e non vanno modificate).
Di fatto hanno uno scope "globale". E in quanto tali si prestano ad usi "globali" e non sono per nulla (proprio per nulla) adatte ad ospitare contenuto custom definito o, anche semplicemente, caricato dall'utente.
No problem, in fondo. Abbiamo sempre la strada numero due dal bivio di cui sopra. Scriviamo una Site Page.
Qui, generalmente, nascono le confusioni.
Innanzitutto, volete un esempio di site page ? Il più classico, default.aspx, per ora si presta allo scopo.
Il codice ASPX della pagina è, anche in questo caso, fisicamente memorizzato su file system.
Default.aspx, ad esempio, fa parte tipicamente della Site Definition del sito che la ospita, quindi nel caso di un Team Site la troviamo in [12]\SiteTemplates\STS.
Fermi tutti, direte voi. Ma io default.aspx la posso personalizzare !
Giusto. Infatti non ci stupiremo se:
- aprendo il web con, che so, SharePoint designer e
- facendo doppio click, che so, proprio su default.aspx e
- modificando, che so, il colore di sfondo di un div e
- salvando, che so, il file e
- facendo click, che so, sulla message box che ci avverte di un "lieve" cambiamento ...
troveremo le modifiche effettive apportate sulla pagina default.aspx del sito.
Sì ok, ma quale sito ?
Se mi rispondete "che domanda, quello che ho aperto con SPD" avete ragione ma vi dovete sorbire la mia contro-obiezione.
Che recita: "Giusto, ma non si era detto che default .aspx risiedeva sul file system ? No, perchè se ci andiamo ad aprire *quella* default.aspx sotto la [12] mi sa che il div ce lo ritroviamo così com'è di ... default (gioco di parole decisamente cercato).
Ok, va là, proviamo a fare ordine.
1) Default.aspx, quello che troviamo sul file system, è un "template di pagina" (leggi Page Template). Che non ha senso di esistere in se ma solo come modello per il provisioning (leggi: creazione) di un'istanza di pagina (leggi: Page Instance) a livello di web site. Il provisioning può avvenire in diversi modi, anche se tipicamente la pagina viene attivata via feature.
2) In seguito al provisioning di un'istanza di pagina, all'interno del database di contenuto che ospita i dati del web site relativo viene aggiunto, sotto sotto, un record che identifica la pagina. Cosa sta scritto dentro quel record è, di fatto, passibile di modifiche (ricordiamoci che la struttura del db di SP non necessariamente rimarrà tale in seguito a modifiche o aggiornamenti di versione). Ma nulla vieta di andarci a guardare:
select top 1 id, setuppath from dbo.Docs where LeafName=@docName and DirName=@docPath
La query rivela come, in seguito al provisioning di una pagina (potete provare con 'default.aspx' come leafname e il server-relative-url del web come dirname), all'interno del content database venga memorizzato un "riferimento" (che è un percorso relativo alla directory [12]) al page template opportuno.
3) In seguito alla personalizzazione della pagina con uno strumento (SPD ad esempio) che ne effettui il cosiddetto unghosting, il contenuto del template viene copiato "as-is" all'interno del content database. Provate ad eseguire la seguente query:
select @docStream = cast(Content as varbinary(max)) from dbo.DocStreams where Id=@docId
print @docStream
e vedrete stampato a video proprio il markup ASPX che in questo caso *è* la pagina. I cambiamenti effettuati da ora in poi hanno effetto su tale campo blob e *non* sul template di pagina, che è e resta intatto su disco.
4) Provate, con SharePoint Designer una volta ancora, ad effettuare il "Reset to site definition" di una pagina unghosted. Rieseguendo l'ultima query non ci viene restituito alcun record, segno del fatto che lo stream dal quale la pagina viene generata non è (più) memorizzato in db ma viene ricavato, una volta ancora, dal file system.
Vi allego (nel senso che lo metto inline) un semplice script SQL che accetta un paio di parametri (nome della pagina e web-location) e scrive a video lo stato della pagina in questione accompagnato, se è il caso, dal BLOB che ne contiene la versione unghosted.
declare @docName varchar(128)
declare @docPath varchar(255)
declare @docId uniqueidentifier
-- Change to following according to your input.
set @docName='default.aspx'
set @docPath='CustomAppPages'
print 'Querying information for ' + @docPath + '/' + @docName
print ''
-- Locate the specified page instance.
declare @docFound int
select @docFound=count(Id) from dbo.Docs where LeafName=@docName and DirName=@docPath
if @docFound = 0
print 'No page found'
else begin
-- Find the document identifier and location.
declare @docStream varchar(max)
declare @docPhysicalLocation varchar(255)
select top 1 @docId=id, @docPhysicalLocation=setuppath from dbo.Docs where LeafName=@docName and DirName=@docPath
print 'The page template is stored on the filesystem at :' + @docPhysicalLocation
-- Find the document stream, if any.
select @docFound=count(*) from dbo.docstreams where Id=@docId
if @docFound = 0
print 'The document is ghosted (uncustomized) and has no db stream stored.'
else begin
select @docStream = cast(Content as varbinary(max)) from dbo.docstreams where Id=@docId
print 'The document is unghosted (customized). Contents follow:'
print @docStream
end
end
E' un "giro", questo qui delle pagine (un)ghosted, un po' particolare e che merita parecchia attenzione, non fosse altro che per le accorgenze alle quali accennavo prima.
Quali accorgenze ?
Provare per credere, anche qui.
Create un page template semplice semplice, ma non proprio. Che contenga, ad esempio, un blocco script server-side. Salvate il template e create una feature che ne effettui il provisioning. Non mi dilungo su questo aspetto e mi limito, una volta ancora, a un copia e incolla dalla mia macchina di sviluppo:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Path="Path_Relativo_Alla_Feature" Url="Url_Delle_Pagine">
<File Url="CustomPage.aspx" Type="Ghostable"></File>
</Module>
</Elements>
Aprite la pagina "istanziata" in un browser. Nessun problema (salvo bug :-P).
Ora aprite SPD e "unghostate" la pagina (anche senza modificarla per nulla, vedere sopra).
Refresh della pagina istanziata e ... Bum !
Perchè questo ?
SharePoint si fa personalizzare ma prova anche a proteggersi (e proteggere voi) da codice potenzialmente dannoso. Quale il codice di scripting in pagine customized (unghosted).
Le pagine applicative ed i page template sono modificabili solo da un utente con diritti di scrittura sulla folder di cui sopra (tipicamente un admin del server). E il sistema le considera attendibili perchè tali ritiene gli utenti che le possono editare.
Un contributor (che ha i diritti sufficienti a personalizzare una site page) non è considerato altrettanto affidabile.
Il risultato evidente è che, salvo configurazione differente ed esplicita, i blocchi di script in pagine customizzate non sono consentiti. Da cui l'errore.
Quello che, se mai, colpisce è come lo stesso blocco sia consentito fintanto che la pagina è in stato uncustomized (ghosted). Il che, spero, dovrebbe essrere chiaro se pensiamo alla location dalla quale la pagina è, appunto, caricata: il file system del front-end server.
I page template contenenti blocchi "unsafe", quali script server side o controlli non opportunamente identificati, sono bombe pronte ad esplodere. Il sistema, giustamente, si protegge disinnescandole alla prima occasione di scoppio.
Piccolo suggerimento ?
Evitate i blocchi di scripting nei page template !!
Piccolo suggerimento versione due ?
Se proprio dovete, e se avete i diritti per farlo, vi tocca andare nel web.config ed aggiungere una entry alla sezione PageParserPaths indicando le caratteristiche che, per quel percorso o per quella pagina, sono consentite. Come nel seguente snippet:
<PageParserPath
VirtualPath="/Il_Sito/Url_Delle_Pagine/*"
IncludeSubFolders="true"
CompilationMode="Auto"
AllowServerSideScript="true"
AllowUnsafeControls="false">
</PageParserPath>
Ok. Finish. See you next (magari non dopo altri 2 mesi di apnea, assenza dal blog e mille cose che vi risparmio giacchè vi ho inflitto un altro post chilometrico :-P)