Role Provider è un servizio di ASP.NET, disponibile dalla versione 2.0 del Framework .Net, che permette la gestione dei ruoli.
I ruoli sono gruppi di utenti che condividono gli stessi privilegi all'interno di un sito internet. Ad esempio, in un blog potrebbero essere definiti i ruoli di amministratore, utente registrato ed utente anonimo per mostrare nelle pagine soltanto le funzioni appropriate al gruppo a cui appartiene il visitatore. L'utente deve quindi essere identificato, compito di cui si occupa un altro servizio di ASP.NET, Membership Provider. Membership e Role Provider sono stati progettati, infatti, per lavorare fianco a fianco.
In questo articolo realizziamo un Role Provider personalizzato basato su database SQLite e come Membership Provider utilizziamo quello presentato nell'articolo
, che è utile consultare per maggiori informazioni. Per un'introduzione a SQLite si veda invece l'articolo
.
I concetti qui esposti possono comunque essere applicati anche ad altri database, come MySql o Oracle, avendo soltanto cura di modificare in modo appropriato il provider ADO.NET utilizzato.
Il web.config
Prima di passare ad analizzare il codice è necessario modificare il file di configurazione dell'applicazione Web aggiungendo la definizione del roleManager
all'interno della sezione <system.web>
.
Listato 1. Modifica al web.config
<system.web>
[...]
<roleManager defaultProvider="SQLiteRoleProvider" enabled="true" cacheRolesInCookie="true"
cookieName=".ASPROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" cookieSlidingExpiration="true" cookieProtection="All">
<providers>
<clear/>
<add name="SQLiteRoleProvider" type="Example.SQLiteRoleProvider"
connectionStringName="SQLiteConnString" applicationName="Example" />
</providers>
</roleManager>
[...]
</system.web>
ASP.NET è ora configurato per ignorare il Role Provider predefinito. Al suo posto verrà caricata la classe Example.SQLiteRoleProvider
, di cui ci occuperemo tra poco. Tutte le altre impostazioni sono quelle predefinite del Role Provider. Per maggiori informazioni vi invito a consultare la documentazione ufficiale su MSDN.
A questo punto è necessario configurare la stringa di connessione al database.
Listato 2. Dichiarare la strina di connessione
<connectionStrings>
<remove name="SQLiteConnString" />
<add connectionString="Data Source=|DataDirectory|Example.db;"
name="SQLiteConnString" providerName="System.Data.SQLite"
/>
</connectionStrings>
Ho volontariamente omesso, per chiarezza e perché esula dagli scopi di questo articolo, la configurazione del Membership Provider. Per saperne di più, consiglio di leggere l'articolo
.
Il database
Il Role Provider ha bisogno di due tabelle: aspnet_Roles
ed aspnet_UsersInRoles
. Nella prima sono memorizzati i ruoli, mentre nella seconda questi sono associati ad ogni singolo utente. Vediamo come sono strutturate le due tabelle.
aspnet_Roles | |
---|---|
Nome | Descrizione |
Rolename | Nome del ruolo |
ApplicationName | Nome dell'applicazione Web a cui il ruolo appartiene |
Listato 3. Dichiarazione SQL per aspnet_Roles
CREATE TABLE aspnet_Roles (
Rolename TEXT NOT NULL,
ApplicationName TEXT NOT NULL
);
aspnet_UsersInRoles | |
---|---|
Nome | Descrizione |
Username | Nome utente |
Rolename | Nome del ruolo |
ApplicationName | Nome dell'applicazione Web a cui il ruolo appartiene |
Listato 4. Dichiarazione SQL per aspnet_UsersInRoles
CREATE TABLE aspnet_UsersInRoles (
Username TEXT NOT NULL,
Rolename TEXT NOT NULL,
ApplicationName TEXT NOT NULL
);
Tutte le colonne sono dichiarate come TEXT
. Per migliorare le prestazioni di accesso ai dati creiamo anche due indici:
CREATE INDEX idxUIR ON aspnet_UsersInroles ('Username', 'Rolename', 'ApplicationName');
CREATE INDEX idxRoles ON aspnet_Roles ('Rolename' , 'ApplicationName');
Creazione della classe ed analisi del codice
Ogni Role Provider deve ereditare dalla classe astratta System.Web.Security.RoleProvider
, quindi anche il nostro (chiamato SQLiteRoleProvider
) seguirà questa regola.

I nomi dei metodi che devono essere sovrascritti (in corsivo) riflettono molto bene la loro funzione. Ad esempio il metodo GetAllRoles()
restituisce un array contenente tutti i ruoli, IsUserInRole()
controlla se un utente appartiene o meno ad un determinato ruolo e così via.
Tutti i metodi sono abbastanza simili e la loro logica di funzionamento può essere riassunta in tre fasi principali:
- connessione al database;
- ricerca o aggiornamento delle informazioni necessarie;
- disconnessione e restituzione dei valori richiesti.
Per ovvi motivi di spazio non sarà possibile analizzare il funzionamento di ogni singola funzione: ne verranno quindi prese in esame due fra le più significative. Vi invito quindi a scaricare il codice completo allegato all'articolo come ulteriore riferimento.
Analizziamo per prima RoleExists()
Listato 5. RoleExists()
public override bool RoleExists(string rolename) { bool result = false;
SQLiteConnection conn = new SQLiteConnection(connectionString);
SQLiteCommand cmd = new SQLiteCommand("SELECT * FROM [" + rolesTable + "]" +
" WHERE Rolename = ? AND ApplicationName = ?", conn);
cmd.Parameters.Add("@Rolename", DbType.String, 255).Value = rolename;
cmd.Parameters.Add("@ApplicationName", DbType.String, 255).Value = ApplicationName;
try
{
conn.Open();
if (cmd.ExecuteReader().HasRows) result = true;
}
catch (SQLiteException e) { throw e; }
finally { conn.Close(); }
return result;
}
La logica è abbastanza semplice: per prima cosa viene dichiarata una variabile di supporto chiamata result
, inizialmente impostata su false
. In seguito vengono creati la connessione e l'oggetto SQLiteCommand
e, all'interno di un blocco try
, viene eseguita l'interrogazione. Se l'esito è positivo alla variabile result
viene assegnato il valore true
. Nel caso si verifichi un errore, questo viene intercettato dal blocco catch
. Per semplicità, è stata omessa una gestione più complessa delle eccezioni. Per concludere, dopo essersi assicurati che la connessione sia chiusa all'interno del blocco finally, viene restituito il valore della variabile result
.
Il metodo CreateRole()
si occupa invece di creare un nuovo ruolo.
Listato 6. CreateRole()
public override void CreateRole(string rolename)
{
if (rolename.IndexOf(",") > 0)
throw new ArgumentException("Role names cannot contain commas.");
if (RoleExists(rolename))
throw new ProviderException("Role name already exists.");
SQLiteConnection conn = new SQLiteConnection(connectionString);
SQLiteCommand cmd = new SQLiteCommand("INSERT INTO " + rolesTable +
" (Rolename, ApplicationName) " + " Values(?, ?)", conn);
cmd.Parameters.Add("@Rolename", DbType.String, 255).Value = rolename;
cmd.Parameters.Add("@ApplicationName", DbType.String, 255).Value = ApplicationName;
try
{
conn.Open();
cmd.ExecuteNonQuery();
}
catch (SQLiteException e) { throw e; }
finally { conn.Close(); }
}
Dopo aver controllato che il nome del nuovo ruolo sia valido (non contiene virgole e non è già presente nel database), vengono creati gli oggetti SQLiteConnection e SQLiteCommand necessari. L'interrogazione viene anche in questo caso gestita all'interno di un blocco try-catch-finally. Questa funzione non restituisce alcun valore. Nel caso che il nome del ruolo non sia valido o si presenti qualche errore durante le operazioni sul database viene restituita un'eccezione.
È bene sottolineare che data la semplice gestione degli errori utilizzata nel codice d'esempio, questa si rende necessaria a livelli di codice più alti. È infatti preferibile non mostrare mai all'utente messaggi di errore interni, soprattutto per quanto riguarda servizi di autenticazione e gestione dei privilegi quali Membership e Role Provider.
Non ho volutamente analizzato la funzione Initialize(), molto importante in quanto si preoccupa di caricare le impostazioni dal file web.config, perché molto simile a quella già trattata per il Membership Provider, da cui differisce soltanto per i dati caricati.
Come ulteriore riferimento per la scrittura di provider personalizzati vi rimando all'ottima sezione di MSDN,
.