Di Daniele Bochicchio - Microsoft MVP
Nel precedente articolo di questa serie si è discusso del Provider Model e di quanto questo influenzi in maniera molto positiva gran parte delle scelte implementative che si devono fare nella creazione di applicazioni web basate su ASP.NET 2.0.
Dal punto di vista dello sviluppatore, l'introduzione di questo modello consente di concentrarsi maggiormente sulla reale funzionalità, piuttosto che sui dettagli interni tipici del meccanismo di storage. Profile API chiude in tal senso il cerchio, rispetto a Membership e Roles API, consentendo con lo stesso approccio di aggiungere anche il supporto per il profilo utente all'interno di applicazioni web, potendo offrire lo stesso identico approccio sia nel caso di sezioni protette, che per tutti gli altri casi possibili.
.gif)
In questa pagina
Cos'è il profilo utente
Profile API sotto la lente
Impostare le proprietà del profilo
Un esempio pratico
Definire le proprietà del profilo su classe
Recuperare il profilo in maniera separata dall'utente
Provider aggiuntivi
Conclusioni
Approfondimenti
Cos'è il profilo utente
Spesso si tende a confondere i dati tipici dell'utente (username, password, email) con quelli del suo profilo. Distinguerli è quanto mai semplice: nel profilo vanno a confluire tutte quelle informazioni che non rientrano nel primo gruppo, che ASP.NET 2.0 gestisce attraverso Membership API.
Il profilo, viceversa, è un concetto proprio di Profile API. Come il nome stesso suggerisce, si tratta di un layer di astrazione necessario a garantire un'uniformità implementativa, grazie al Provider Model. Secondo questo modello sono infatti i provider ad andare a leggere o scrivere nello storage di destinazione, lasciando l'interfaccia di accesso identica (si veda il precedente articolo di questa serie per maggiori approfondimenti).
Generalmente, con ASP.NET 1.x, quando queste informazioni devono essere rese persistenti, si opta per caricarle e salvarle all'interno di un database, mediante l'uso di una classe personalizzata che consenta di accedervi in maniera object oriented e strongly-typed.
L'esempio che segue mostra una tipica implementazione, funzionante e organizzata per essere strongly typed:
C#
namespace ASPItalia.com.Web.Personalization
{
public sealed class Profile
{
private string _firstName;
private string _lastName;
private Color _preferredColor;
public static string FirstName
{
get
{
if (_firstName == null)
_firstName = GetProperty("FirstName");
return _firstName;
}
set
{
_firstName = SetProperty("FirstName", value);
}
}
public static string LastName
{
get
{
if (_lastName == null)
_lastName = GetProperty("LastName");
return _lastName;
}
set
{
_lastName = SetProperty("LastName", value);
}
}
// etc…
// metodo per salvare nel database
private static string SetProperty(string propertyName, string value)
{
// recupero username
string username = HttpContext.Current.User.Identity.Name;
// salvataggio dei dati nel database
//...
return value;
}
// metodo per leggere dal database
private static string GetProperty(string propertyName)
{
// recupero username
string username = HttpContext.Current.User.Identity.Name;
// lettura dal database e salvataggio nella variabile value
return value;
//...
}
}
}
VB
Namespace ASPItalia.com.Web.Personalization
Public Class Profile
Private _firstName As String
Private _lastName As String
Private _preferredColor As Color
Public Shared Property FirstName() As String
Get
If (_firstName = Nothing) Then
_firstName = GetProperty("FirstName")
End If
Return _firstName
End Get
Set(ByVal value As String)
_firstName = SetProperty("FirstName", value)
End Set
End Property
Public Shared Property LastName() As String
Get
If (_lastName = Nothing) Then
_lastName = GetProperty("LastName")
End If
Return _lastName
End Get
Set(ByVal value As String)
_lastName = SetProperty("LastName", value)
End Set
End Property
' etc…
' metodo per salvare nel database
Private Static Function SetProperty(ByVal propertyName As String, ByVal value As String) As String
' recupero username
Dim username As String = HttpContext.Current.User.Identity.Name
' salvataggio dei dati nel database
'...
Return value
End Function
' metodo per leggere dal database
private Shared Function GetProperty(string propertyName) as Object
' recupero username
Dim username As String = HttpContext.Current.User.Identity.Name
' lettura dal database e salvataggio nella variabile value
Return value
'...
End Function
End Class
End Namespace
Come si può intuire, a ciascuna proprietà della classe generalmente si fa corrispondere una colonna nella tabella del database, così da rendere più immediata anche la creazione di statistiche sui dati contenuti.
Questo approccio non ha grossi limiti, nè controindicazioni di massima, ma ovviamente necessita che un po' di codice venga scritto per essere funzionante. Il codice così scritto si presta anche abbastanza bene a essere portato tra più applicazioni, ma ha il limite di essere accoppiato allo storage per cui viene pensato e soprattutto allo sviluppatore che l'ha implementato
L'idea alla base di Profile API di ASP.NET 2.0 è invece quella di slegare lo sviluppatore dallo storage e dai dettagli implementativi, garantendo lo stesso modo di approcciare il problema a prescindere dall'applicazione.
Profile API sotto la lente
Basandosi interamente sul Provider Model, Profile API non si discosta poi troppo da quello che Membership e Roles API consentono di fare nelle rispettiva aree implementative.
Il modello a oggetti utilizzato è molto intuitivo e la classe ProfileBase, contenuta nel namespace System.Web.Profile, è quanto mai semplice da sfruttare.
La configurazione viene fatta, anche in questo caso, sfruttando un meccanismo di Dependency Injection, che si traduce nell'impostare il provider all'interno del file di configurazione dell'applicazione web, il web.config.
Prima di addentrarci maggiormente è necessario comunque cercare di capire come il Provider Model si incastri in questo discorso.
Il contratto, tipico di questo modello, è stabilito attraverso la classe astratta ProfileProvider, su cui tutte le classi concrete dei vari provider si basano. I metodi maggiormente utilizzati sono GetPropertyValues e SetPropertyValues, che si occupano rispettivamente di recuperare e impostare, dato il contesto corrente di esecuzione della richiesta e risposta, i valori da e verso il profilo utente, interagendo direttamente sullo storage di destinazione.
Ogni provider si differenzia dall'altro soprattutto per come questi due metodi sono stati implementati, così da garantire il migliore approccio possibile all'interno dello scenario prestabilito.
Di default ASP.NET 2.0 è distribuito con un solo provider, per SQL Server (da 7 a 2005, passando anche per MSDE o le versioni Express). Questo provider si trova nello stesso namespace prima menzionato e offre una implementazione concreta per questo database, pensata per essere flessibile rispetto alle proprietà del profilo, così che non sia necessario toccare il database per poter aggiungere o rimuovere una proprietà. Avremo modo, comunque, di tornarci con maggior dettaglio tra un attimo.
È interessante invece dare un'occhiata a come venga specificato il provider da utilizzare all'interno del file di configurazione, perchè nel caso di quello di default è necessario inserirne il riferimento all'interno della sezione configuration\system.web\profile.
Analogamente a quanto avviene con Membership e Roles API, il provider di default è disponibile per tutte le applicazioni, facendo riferimento a una user instance di un database di SQL Server 2005 Express, che ha il limite però di non poter essere affatto utilizzato in produzione, date le limitazioni di cui è caratterizzato.
Per questo motivo, è necessario personalizzare la configurazione agendo sul già citato web.config, in questo modo:
<configuration>
<system.web>
<profile enabled="true"
automaticSaveEnabled="false"
enabled="true"
defaultProvider="SqlServerProvider">
<providers>
<add connectionStringName="SqlServer"
applicationName="/"
name="SqlServerProvider"
type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</profile>
</system.web>
</configuration>
Attraverso l'attributo type nella sezione di registrazione del provider si specifica la classe da utilizzare, mentre gli altri sono di facile comprensione. È importante sottolineare, invece, gli attributi enabled e automaticSaveEnabled della sezione profile. Il primo ha l'effetto di rendere attivo il sottosistema di gestione del profilo, mentre il secondo consente di fare in modo che il salvataggio del profilo, quando le proprietà vengono modificate, debba essere fatto esplicitamente. Di default questa proprietà è impostata su true, con la conseguente esecuzione delle istruzioni legate al salvataggio ogni volta che una singola proprietà viene modificata, mentre è ovviamente preferibile concentrare queste ultime in blocco, così da evitare inutile carico sullo storage selezionato.
Impostare le proprietà del profilo
Per comprendere al meglio come Profile API possa essere sfruttato all'interno delle applicazioni web, è necessario fare un passo indietro, dando un'occhiata a come si specificano le proprietà del profilo, che andranno poi a confluire in una classe specifica.
In una tipica applicazione web, infatti, ci sono un insieme finito di proprietà che corrispondono, generalmente, alle informazioni necessarie a profilare un utente, ad esempio per scopi di marketing, piuttosto che per la personalizzazione di alcune funzionalità.
L'esempio più diffuso in questo caso è quello della scelta di un tema personalizzato da applicare, piuttosto che dell'inserimento del nome o della data di nascita, tutte informazioni che, giustamente, non fanno parte di quelle legate alla classe MembershipUser, utilizzata da Membership API.
La modalità più semplice per definire queste proprietà è all'interno del web.config, racchiudendo le stesse nella sezione configuration\system.web\profile\properties, in questo modo:
<configuration>
<system.web>
…
<properties>
<add name="FirstName" type="string"/>
<add name="BirthDate" type="DateTime"/>
</properties>
</system.web>
</configuration>
Con questa modifica diventa possibile accedere a queste proprietà, sia in lettura che in scrittura. Come queste due operazioni avvengano a un livello più basso è in realtà un problema del provider selezionato: nel nostro codice ci basterà fare riferimento alla classe ProfileBase menzionata qualche paragrafo in alto perchè sfruttando il provider per SQL Server vada a scrivere nel database che è stato specificato attraverso la stringa di connessione.
Utilizzando VS 2005, in realtà, nella pagina è sufficiente fare riferimento alla classe Profile. Dando un'occhiata con Reflector a quello che System.Web.UI.Page ci mette a disposizione, si scopre ben presto che questa classe non esiste, ma è iniettata attraverso il Page Parser, direttamente in fase di compilazione della pagina, aggiungendo automaticamente le proprietà specificate nel web.config.
Per certi versi VS 2005, in questo caso, compie un'operazione abbastanza interessante: prevede che il compilatore a un certo punto creerà una classe a cui saranno aggiunte queste proprietà specificate, aggiungendo le stesse all'Intellisense, come si può vedere nell'immagine che segue.
.jpg)
Immagine 1: L'Intellisense di VS 2005 all'opera sulle proprietà del profilo
La cosa interessante di questo meccanismo è che viene preservato il tipo della proprietà, specificato nel web.config attraverso la proprietà type, così che l'accesso sia type safe e strongly typed.
In realtà quando si ha la necessità di accedere al profilo da una class library è necessario utilizzare i metodi GetPropertyValue e SetPropertyValue, che altro non fanno che leggere dalla collection dei valori, poichè ProfileBase eredita dalla classe SettingsBase.
I metodi Create e Save svolgono dunque la funzione di creare, se necessario, e aggiornare esplicitamente il profilo utente, così da chiudere il cerchio e rendere il tutto utilizzabile nelle nostre applicazioni Web.
Un esempio pratico
Profile API può essere utile in tutti quei casi in cui sia necessario personalizzare l'utente, dunque un esempio base può essere un'area ad accesso riservato, con la possibilità per l'utente di specificare il proprio nome e cognome. La parte interessante è senza dubbio quella legata alla lettura e successiva modifica dei dati, che viene riportata di seguito:
C#
protected override OnLoad(EventArgs e)
{
If (!IsPostBack)
{
FirstName.Text = Profile.FirstName;
LastName.Text = Profile.LastName;
}
}
protected SaveProfile(Object o, EventArgs e)
{
Profile.FirstName = FirstName.Text;
Profile.LastName = LastName.Text;
// forzo il salvataggio, perchè ho impostato automaticSaveEnabled = false
Profile.Save();
}
VB
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
If Not IsPostBack Then
FirstName.Text = Profile.FirstName
LastName.Text = Profile.LastName
End If
End Sub
Protected Sub SaveProfile(o as Object, e as EventArgs)
Profile.FirstName = FirstName.Text
Profile.LastName = LastName.Text
' forzo il salvataggio, perchè ho impostato automaticSaveEnabled = false
Profile.Save()
End Sub
Come si può notare, il codice è molto semplice da comprendere, poichè l'introduzione delle Profile API è pensato proprio per semplificare la vita dell'utente, demandando per questo il più possibile il lavoro vero e proprio al provider.
Definire le proprietà del profilo su classe
Se l'approccio dichiarativo delle proprietà all'interno del web.config va sostanzialmente bene per gran parte delle applicazioni, con l'introduzione di Web Application Projects, incluso anche con il Service Pack di VS 2005, è stato restituito ad ASP.NET 2.0 il supporto per i progetti web, inizialmente rimosso dalla versione finale.
Proprio perchè diverso nell'approccio, l'uso di WAP pregiudica l'approccio appena analizzato, che specifica le proprietà all'interno del web.config, perchè VS 2005 non è più in grado di trovare in fase di compilazione le proprietà, generando un errore.
L'alternativa è semplicemente quella di utilizzare i già menzionati metodi GetPropertyValue e SetPropertyValue, perdendo un po' di intuitività.
Anche se non semplicemente limitato a questa casistica, la possibilità di definire le proprietà all'interno di una classe vera e propria ritorna utile in diversi scenari. Per prima cosa, può essere facilmente condivisa a prescindere dalla GUI, così da fare da contenitore, anche serializzabile e quindi trasportabile, indipendentemente dalle Profile API.
In realtà questa possibilità si rivela comoda soprattutto perchè consente di preservare un approccio Object Oriented nella fase di defizione del profilo, potendo dunque anche associare comportamenti personalizzati alle singole proprietà, cosa non possibile sfruttando direttamente la generazione automatica del codice da parte del web.config.
Poichè tutto funzioni a dovere è necessario prima di tutto definire una classe che erediti da ProfileBase. Per semplicità possiamo usare come esempio quello precedente, aggiungendo la nuova classe che contiene questo codice:
C#
using System.Web;
using System.Web.Profile;
namespace ASPItalia.com.Web.Profile
{
public class MyProfile: ProfileBase
{
public string FirstName
{
get { return this["FirstName"] as string; }
set { this["FirstName"] = value; }
}
public string LastName
{
get { return this["LastName"] as string; }
set { this["LastName"] = value; }
}
}
}
VB
Imports System.Web
Imports System.Web.Profile
Namespace ASPItalia.com.Web.Profile
Public Class MyProfile
Inherits ProfileBase
Public Property FirstName() As String
Get
Return CType(Me("FirstName"), String)
End Get
Set(ByVal value As String)
Me("FirstName") = value
End Set
End Property
Public Property LastName() As String
Get
Return CType(Me("LastName"), String)
End Get
Set(ByVal value As String)
Me("LastName") = value
End Set
End Property
End Class
End Namespace
A questo punto l'unica differenza, a livello di codice, consiste nell'accesso diretto alla classe MyProfile, che diventa paragonabile a quello che faremmo con una normale class library:
C#
FirstName.Text = MyProfile.FirstName;
VB
FirstName.Text = MyProfile.FirstName
Questo approccio generalmente è da preferire sempre quando si ha la necessità, come già detto, di personalizzare i comportamenti associati alla lettura o salvataggio delle proprietà, oltre che in presenza di Web Application Projects.
Recuperare il profilo in maniera separata dall'utente
Se spesso l'accesso al profilo è contestuale rispetto all'utente che richiede la pagina, ci sono moltissimi casi in cui è invece necessario farlo senza questa caratteristica. È il caso, ad esempio, delle aree di amministrazione, piuttosto che in tutti quei casi in cui si vogliano leggere informazioni senza per forza limitarsi all'utente autenticato.
In tutti questi casi è necessario fare riferimento alla classe ProfileManager, che ha una serie di metodi il cui uso è comodo proprio in questi scenari, riportati di seguito:
-
DeleteInactiveProfiles: elimina i profili non più attivi, impostando una data di filtro;
-
DeleteProfile: elimina un profilo specifico, dato lo username dell'utente;
-
FindProfilesByUserName: restituisce una collection di profili, dato uno username;
-
GetAllProfiles: restituisce tutti i profili;
-
GetNumberOfProfiles: restituisce il numero di profili attualmente presenti nello storage del provider.
Provider aggiuntivi
Il provider per SQL Server distribuito con ASP.NET 2.0 è comodo, senza ombra di dubbio, ma ha il grande limite di prevedere il salvataggio dei dati all'intero di una sola colonna di una tabella, posizionando le informazioni serializzate, con l'uso puntatori che rendono possibile il recupero delle informazioni ed il successivo ripristino all'interno della classe ProfileBase.
Se questo garantisce una grandissima flessibilità, poichè rende possibile l'aggiunta di una proprietà senza toccare il database, ha lo svantaggio di non rendere possibile operazioni di data mining, ad esempio a fini di marketing.
.jpg)
Immagine 2: Il formato utilizzato dal provider per SQL Server
Per questo motivo la cosa migliore è utilizzare un provider aggiuntivo, rilasciato da Microsoft e disponibile a questo indirizzo, che consente di avere per ogni proprietà una colonna nel database.
In alternativa, qualora si volesse utilizzare uno storage di tipo diverso, ecco una lista dei provider più diffusi, che in genere comprendono anche quelli per Membership e Roles API:
Conclusioni
Profile API offre un sottosistema molto semplice da configurare e utilizzare, ma altrettanto potente da estendere e adattare alle proprie necessità. Così come per Membership e Roles API, anche Profile API trae beneficio dell'uso del Provider Model.
È importante sottolineare, ancora una volta, come attraverso questa tecnica sia possibile cambiare totalmente, anche successivamente, il modo in cui la nostra applicazione recupera le informazione dallo storage, semplicemente agendo dal web.config e senza necessità di toccare il codice scritto. Ed è questo, con buona probabilità, che rende Profile API così comodo da utilizzare per personalizzare le applicazioni web basate su ASP.NET 2.0.
Approfondimenti