Home Page Home Page Articoli Membership, Role e Profile Provider personalizzati

Membership, Role e Profile Provider personalizzati

Chi deve gestire in modo consistente autenticazione, gestione dei ruoli e profilazione degli utenti di un sito Internet non può non conoscere i Providers (MembershipProvider, RoleProvider e ProfileProvider) offerti da ASP.NET. Vediamo che cosa sono, a cosa servono e come si costruiscono.
Autore: Marco Rossi Livello:

Introduzione


Uno dei problemi più comuni che si devono affrontare nello sviluppo di progetti web riguarda la gestione della sicurezza nelle applicazioni web e quindi la personalizzazione dei provider di ASP.NET relativi a Membership, Roles e Profile.

ASP.NET 2.0 ci fornisce infatti alcuni provider già pronti (SqlMembershipProvider e ActiveDirectoryMembershipProvider) ma il più delle volte si ha l’esigenza di utilizzare una base dati già esistente o da definire, e quindi ci si trova di fronte al problema di dover implementare dei provider specifici.

A chi non ha abbastanza familiarità con il provider model di ASP.NET 2.0 e di come venga sfruttato dai vari controlli web suggerisco la lettura di questi due articoli:
Aree di accesso protette con ASP.NET 2.0: Membership e Roles API 
La classe Profile di ASP.NET 2.0 

Scenario


Come caso di esempio si supponga di dover creare un sito web con delle aree a cui possono accedere solo gli utenti registrati. Questi utenti saranno definiti con i seguenti ruoli di accesso:
? User
? Power User
? Administrator


Come detto in precedenza non si vuole utilizzare il provider predefinito di ASP.NET, e quindi il relativo database, ma si ha l’esigenza che l'elenco degli utenti e la loro tipologia venga caricata da un database Sql Server che ha la seguente struttura:



La tabella Users contiene l'elenco degli utenti e lo UserTypeID rappresenta la tipologia di utente. Ogni utente che si autentica sul sito potrà accedere a determinate pagine in base al ruolo associato (UserType).

La struttura del sito web (definito come Web Application Project) è la seguente:



Ci sono due cartelle Admin e User che conterranno le pagine a cui possono accedere solo gli amministratori e gli utenti registrati. Le pagine Default e Login invece sono visibili a tutti gli utenti, compresi gli anonimi.

Solution


La solution che andremo a creare conterrà tre progetti:
- Sito web: MyWebSite
- Class library per l’accesso ai dati: MyWebSite.Data
- Class library con la business logic: MyWebSite.Business


All'interno del progetto MyWebSite.Business verrà definita la classe User che rappresenta un utente e l'enum con le tipologie di utenti:



Anche la class library MyWebSite.Data sarà abbastanza semplice, conterrà le classi che si occuperanno dell'accesso a SQL Server:



MembershipProvider


Adesso che è stata definita la struttura si può procedere con l’implementazione del MembershipProvider, per avere una corretta validazione degli utenti che effettuano il login sul sito. Basterà creare una classe che eredita da System.Web.Security.MembershipProvider ed effettuare l'override delle funzioni che ci interessano. In questo caso specifico verrà implementata solo la funzione ValidateUser che si occupa di verificare i dati di accesso di un utente:

Codice .NET n°1
namespace MyWebSite.Providers
{
public class MyMembershipProvider : System.Web.Security.MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
//Richiama la stored di validazione dell'utente
return Business.User.ValidateLogin(username, password);
}

#region "NonImplementate"

...

#endregion
}
}


Come si può vedere dal codice la funzione non fa altro che richiamare il metodo ValidateLogin della classe Business.User, la quale a sua volta, attraverso la classe Data.User richiamerà una stored procedure per la validazione dei dati dell’utente:

Codice SQL n°2
CREATE PROCEDURE [Common].[proc_Users_ValidateLogin]
@Username varchar(20),
@Password varchar(15)
AS
BEGIN

SET NOCOUNT ON

SELECT
userID
FROM
Common.Users
WHERE
Username = @Username
AND [Password] = @Password

END


Di tutte le altre funzioni bisogna comunque fare l'override perchè la classe MembershipProvider è una classe astratta:

Codice .NET n°3
public override MembershipUser CreateUser(string username, string password,   
string email, string passwordQuestion, string passwordAnswer, bool isApproved, bject providerUserKey, out MembershipCreateStatus status)
{
//Metodo non implementato
throw new NotImplementedException();
}


Se ci sarà l’esigenza di utilizzare gli altri controlli web che ASP.NET fornisce (CreateUserWizard, ChangePassword ecc.) basterà implementare anche i metodi del provider che questi controlli andranno a richiamare, per evitare che vengano generate delle eccezioni:



Una volta creato il provider si potrà procedere con la configurazione del tag all’interno del web.config della nostra applicazione affinché lo utilizzi correttamente:

Codice XML n°4
<system.web>

...

<!-- Autenticazione Forms -->
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" defaultUrl="~/Default.aspx"/>
</authentication>

<!-- Acceso a tutti gli utenti -->
<authorization>
<allow users="*"/>
</authorization>

<!--
La classe MyMembershiProvider viene aggiunta all'elenco dei provider
e impostata come defaultProvider
-->

<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider" type="MyWebSite.Providers.MyMembershipProvider"/>
</providers>
</membership>

...

</system.web>

A questo punto basterà inserire un controllo Login in una pagina web e provare ad effettuare un accesso.

RoleProvider


Il passo successivo consiste nel gestire i ruoli associati all'utente. Nel nostro caso abbiamo detto che il ruolo sarà definito dal campo UserTypeID e dalla relativa enum quindi i ruoli disponibili saranno:
• User
• PowerUser
• Administrator


All'interno del sito web sono state definite due cartelle (Admin e User)



con le seguenti impostazioni di accesso (inserite nel web.config della cartella):

• Admin: solo utenti che hanno effettuato il login e con ruolo administrator

Codice XML n°5
<configuration>
<system.web>
<authorization>
<allow roles="Administrator"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>

• User: tutti gli utenti non anonimi
Codice XML n°6
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>

Il passo successivo consiste nel creare una classe MyRoleProvider che eredita da System.Web.Security.RoleProvider ed implementa il metodo GetRolesForUser (elenco dei ruoli di un utente). Nel nostro caso viene caricata la classe User e, se l'utente esiste, viene ritornata la stringa che rappresenta il valore dell'enum.

Codice .NET n°7
namespace MyWebSite.Providers
{
public class MyRoleProvider : System.Web.Security.RoleProvider
{

public override string[] GetRolesForUser(string username)
{
//Carica i dati utente
Business.User user = Business.User.GetUser(username);

if (user != null)
{
//ritorna il tipo utente come nome del ruolo
return new string[] { user.Type.ToString() };
}

return new string[] { };
}

...
}
}


All’interno del web.config andrà abilitata e configurata la sezione roleManager:

Codice XML n°8
  <roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear/>
<add name="MyRoleProvider" type="MyWebSite.Providers.MyRoleProvider"/>
</providers>
</roleManager>

ProfileProvider


L'ultimo Provider che risulta molto comodo implementare è il ProfileProvider. Questo provider ci consente di accedere a delle proprietà di contorno dell'utente che vengono definite nel web.config:

Codice XML n°9
  <profile defaultProvider="MyProfileProvider" enabled="true">
<providers>
<add name="MyProfileProvider" type="MyWebSite.Providers.MyProfileProvider"/>
</providers>
<properties>
<add name="Name" type="String"/>
<add name="Surname" type="String"/>
<add name="Type" type="MyWebSite.Business.UserType"/>
<add name="Email" type="String"/>
</properties>
</profile>

Come si può vedere sono state esposte le seguenti proprietà:
- Name
- Surname
- Type
- Email


La classe che definisce il provider personalizzato è la seguente:

Codice .NET n°10
namespace MyWebSite.Providers
{
public class MyProfileProvider : System.Web.Profile.ProfileProvider
{
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
//Crea la collection che conterrà i valori
SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection();
if (collection.Count == 0)
return settings;

//Popola la collection
foreach (SettingsProperty prop in collection)
{
settings.Add(new SettingsPropertyValue(prop));
}

//Preleva Username dal context
string username = Convert.ToString(context["UserName"]);
if (!string.IsNullOrEmpty(username))
{
//Carica istanza dell'oggetto utente
Business.User objUser = Business.User.GetUser(username);


if (objUser != null)
{
//Riferimento al tipo di classe dell'oggetto user
Type objType = objUser.GetType();

//Cicla l'elenco delle proprietà da valorizzare
foreach (SettingsPropertyValue prop in settings)
{
//Ricerca la property
System.Reflection.PropertyInfo propInfo = objType.GetProperty(prop.Name);

//Verifica esistenza della property
if (propInfo == null)
throw new ProviderException(String.Format("Impossibile trovare la proprietà '{0}' specificata nel profilo", prop.Name));

//Preleva il valore della property dall'istanza della classe
prop.PropertyValue = propInfo.GetValue(objUser, null);
}
}
else
throw new ProviderException(String.Format("Impossibile caricare i dati dell'utente '{0}'", username));
}

return settings;

}

public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
//La fase di salvataggio delle proprietà non è implementata
//throw new NotImplementedException();
}

...

}
}


E' stato fatto l’override di due metodi:
• GetPropertyValues: carica i valori delle proprietà associate al profilo
• SetPropertyValues: salva i valori delle proprietà associate al profilo

La fase di lettura dei dati è stata implementata sfruttando la Reflection che ci consente di prelevare dinamicamente i valori delle property della classe Business.User.
Il salvataggio invece non è stato implementato perché per quest’applicazione di esempio non è prevista la possibilità di salvare dei dati del profilo. Nulla vieta di farlo sfruttando sempre le potenzialità della Reflection.

Per chi ha familiarità con i WebSite noterà che nei WebProject la classe Profile non viene generata automaticamente in base alle proprietà del profilo definite nel web.config.

Per risolvere questo problema ci sono due possibilità:
• Definire una classe profile che eredita da System.Web.Profile.ProfileBase e si occupa del caricamento dati
• Utilizzare il tool gratuito Web Profile Generator 

Io personalmente utilizzo sempre il tool perché risulta molto comodo. Se avete Visual Studio 2005 lo potete installare attraverso il setup e successivamente troverete una voce Generate Web Profile nel menu contestuale del web.config:



Questa opzione genererà in automatico una classe WebProfile che espone tutte le proprietà definite nel web.config. Per poter accedere al profilo corrente basterà utilizzare la proprietà statica Current della classe WebProfile:

Codice .NET n°11
if (!WebProfile.Current.IsAnonymous)
{
string strSurname = WebProfile.Current.Surname;

...
}


Per chi possiede Visual Studio 2008 suggerisco la lettura questo articolo per poter utilizzare l'Add-In anche nel nuovo ambiente di sviluppo:
Load VS2005 Add-in In VS2008 

Un'ultima ottimizzazione che possiamo fare è quella di caricare i dati dell'utente in Session, per evitare di accedere al database tutte le volte che viene richiamato il provider dei ruoli e quello del profilo. Questa opzione è configurabile a livello di web.config attraverso il parametro UserDataInSession:

Codice XML n°12
<appSettings>
<add key="UserDataInSession" value="1"/>
</appSettings>

All'interno del progetto è stata definita una nuova classe UserLoader che si occupa della fase di caricamento dati dell’utente (metodo GetUserObject) con relativa gestione dei dati in Session:

Codice .NET n°13
namespace MyWebSite
{
public class UserLoader
{
private const string SessionKey = "User";

/// <summary>
/// Ritorna la classe User dell'utente loggato
/// </summary>
public static Business.User GetUserObject(string username)
{

HttpContext context = HttpContext.Current;

//Abilitato caricamento dati dalla session
if (Config.UserDataInSession && context.Session != null)
{
if (context.Session[SessionKey] == null)
{
//Aggiunge i dati in session
context.Session.Add(SessionKey, Business.User.GetUser(username));
}

return context.Session[SessionKey] as Business.User;
}
else
{
//Carica i dati da db
return Business.User.GetUser(username);
}
}

/// <summary>
/// Cancella i dati dell'utente dalla memoria
/// </summary>
public static void ClearCache()
{
HttpContext context = HttpContext.Current;

if (context.Session != null && context.Session[SessionKey] != null)
{
context.Session[SessionKey] = null;
}
}

}
}


All'interno dei provider basterà sostituire la fase di caricamento dei dati dell'utente:

Codice .NET n°14
//Vecchia versione
Business.User objUser = Business.User.GetUser(username);


Codice .NET n°15
//Nuova versione
Business.User objUser = UserLoader.GetUserObject(username);


Conclusione


A questo punto, una volta definiti tutti e tre i provider possiamo procedere con lo sviluppo della nostra applicazione sfruttando tutte le potenzialità che ASP.NET ci fornisce, certi che avremo una struttura affidabile e allo stesso tempo facilmente configurabile. Se in un futuro ci sarà l'esigenza di modificare il tipo di provider sarà sufficiente modificare la configurazione e tutto il resto continuerà a funzionare regolarmente. Allego il progetto di esempio realizzato con Visual Studio .NET 2008.
Voto medio articolo: 4.9 Numero Voti: 11

File allegati


Marco Rossi

Marco Rossi

Ho iniziato a lavorare nel campo dell'informatica nel 2001. Inizialmente ho sviluppato progetti in web con tecnlogia ASP e COM+ (VB6). Successivamente mi sono spostato sul .Net Framework 1.0 e tutte le release successive (1.1 e 2.0). La mia esperienza è maturata anche in ambiti diversi dai siti web: - winfor... Profilo completo

Articoli collegati

Riordinare le righe di una GridView con AJAX
La GridView, un controllo molto potente e flessibile che però non può offrire tutto. Vediamo in questo articolo come estenderlo implementando meccanismi di reordering delle righe usando AJAX
Autore: Riccardo D'Aria | Difficoltà:
Da AJAX ad Atlas nuove tecnologie per un Web di nuova generazione
Nell'ultimo periodo si è parlato molto di Atlas l'ultima tecnologia Microsoft per rendere le pagine web dinamiche, interattive e molto più attraenti, in modo da essere sempre più somiglianti a vere e proprie applicazioni Windows. Cominciamo a conoscere questa tecnologia per sapere cosa potremo fare nelle applicazioni ASP.NET di prossima generazione.
Autore: Matteo Raumer | Difficoltà: | Commenti: 6
La classe Profile di ASP.NET 2.0
In un'applicazione ASP.NET è cosa comune avere la necessità di gestire alcune proprietà che caratterizzino ogni utente, sempre disponibili tra le varie pagine. L'approccio con la versione 1.1 del Framework si basava sull'uso dell'oggetto Session. Con la versione 2.0 il Framework ci mette a disposizione la classe Profile.
Autore: Matteo Raumer | Difficoltà: | Commenti: 3 | Voto:
Le WebParts in ASP.NET 2.0
Scopriamo le WebParts, i nuovi Web Controls di ASP.NET 2.0 che consentono di personalizzare sia i contenuti che l'interfaccia grafica delle nostre applicazioni Web, tecnologia giù utilizzata ampiamente nel prodotto Sharepoint Portal Server 2003.
Autore: Marco Caruso | Difficoltà: | Commenti: 6 | Voto:
Scopriamo l'oggetto Gridview di ASP.NET 2.0
Nonostante la Datagrid di ASP.NET 1.x offrisse già molta flessibilità, con il Framework 2.0 è stato introdotto un nuovo oggetto, la Gridview, erede della DataGrid che, migliorata e potenziata, faciliterà di molto la vita agli sviluppatori Web.
Autore: Giovanni Ferron | Difficoltà: | Commenti: 1 | Voto:
Configurazioni Web con ASP.NET 2.0
Vediamo come cambia e come viene semplificata la configurazione in ASP.NET 2.0 attraverso i nuovi controlli di login, il tool d'amministrazione e l'amministrazione attraverso Internet Information Server (IIS)
Autore: Marco Caruso | Difficoltà: | Commenti: 4
Più facile con il .NET Framework 2.0 :-)
Da quando ho cominciato a "smanettare" per arrivare poi a "lavorare" con il framework 1.x, mi sono trovato subito a mio agio con gli elementi che il .NET metteva a disposizione, ma per alcune cose già sapevo che avrei dovuto lavorare. Con la versione 2.0 del Framework alcune di queste sono state già inserite semplificandomi ulteriormente la vita...
Autore: Matteo Raumer | Difficoltà: | Commenti: 1
ASP.NET Themes e Skins con Visual Studio .NET 2005
Una delle novità introdotte in Whidbey è la possibilità di applicare dei Temi (Themes) alle nostre applicazioni o pagine Web e addirittura applicare delle Skins singolarmente ai controlli delle Web Forms in modo estremamente flessibile e rapido.
Autore: David De Giacomi | Difficoltà: | Commenti: 3 | Voto:
Anteprima su Visual Studio .NET 2005
Visual Studio .NET Whidbey, ossia la prossima versione di Visual Studio .NET basato sul Framework 2.0 è ancora in cantiere ma grazie all'Alpha rilasciata agli sviluppatori in occasione della PDC 2003 possiamo vedere quali novità ci aspettano. Eccone alcune!
Autore: David De Giacomi | Difficoltà: | Commenti: 2
Copyright © dotNetHell.it 2002-2017
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5