Il presente articolo è stato tradotto automaticamente.

Cutting Edge

Archivia i dati degli utenti in ASP.NET Identity

Dino Esposito

Dino EspositoASP.NET Identità in Visual Studio 2013 è un modo per semplificare il noioso ma essenziali compiti di gestione dei dati utente e stabilire un più efficace sistema di appartenenza. In precedenza, fornito una panoramica delle API di identità ASP.NET (msdn.microsoft.com/magazine/dn605872) e ha esaminato il suo coinvolgimento con i social network e il protocollo OAuth (msdn.microsoft.com/magazine/dn745860). In questo articolo, potrai ampliare i punti di estendibilità dell'identità ASP.NET , iniziando con la rappresentazione dei dati utente e l'archivio dati sottostante.

Gettare le basi

Prima cosa, creiamo un nuovo progetto vuoto di MVC ASP.NET in Visual Studio 2013. Tutte le impostazioni predefinite che della procedura guidata fornisce sono OK, ma assicurati di selezionare il modello di autenticazione utente singolo. Il codice messo memorizza i dati utente in un file locale SQL Server con un nome generato automaticamente con la seguente convenzione: ASPNET-[NomeProgetto]-[RandomNumber]. Il codice utilizza anche Entity Framework per accedere al database utente in lettura e scrittura. La rappresentazione di dati utente è nella classe ApplicationUser:

public class ApplicationUser : IdentityUser
{
}

Come potete vedere, la classe ApplicationUser eredita dalla classe fornita dal sistema IdentityUser. Per personalizzare la rappresentazione utente, facciamo partire da qui e aggiungere un nuovo membro alla classe:

public class ApplicationUser : IdentityUser
{
  public String MagicCode { get; set; }
}

Il nome della classe — ApplicationUser in questo esempio — non è obbligatorio e può essere cambiato se vuoi. In questo esempio, ho deliberatamente scelto il campo "codice magico" strano per indicare la doppia possibilità. È possibile aggiungere i campi che richiedono un'interfaccia utente come ad esempio la data di nascita o il numero di sicurezza sociale o quant'altro che si desidera specificare durante la registrazione. È inoltre possibile aggiungere i campi che dovete avere, ma si può calcolare in silenzio (per esempio, un codice "magico" app-specifico) quando in realtà è creato il record di utente. La classe ApplicationUser include di default i membri elencati Figura 1.

Figura 1 membri definiti nella classe Base IdentityUser

Membro Descrizione
Id Auto-generata identificatore univoco (GUID) per la tabella. Questo campo è la chiave primaria.
UserName Nome visualizzato dell'utente.
PasswordHash Hash risultante dalla password fornita.
SecurityStamp Un GUID creato automaticamente in punti specifici nella durata oggetto UserManager. In genere, ha creato e aggiornato quando i cambiamenti di password o login sociale viene aggiunto o rimosso. Il timbro di sicurezza generalmente scatta un'istantanea di informazioni utente e accede automaticamente gli utenti se non è cambiato nulla.
Discriminatore Questa colonna è specifica per il modello di persistenza del Entity Framework e determina la classe a cui appartiene la riga. Ti hanno un valore unico discriminatore per ogni classe della gerarchia radicata nella IdentityUser.

 

I campi elencati nella Figura 1, come bene come qualsiasi altri campi è aggiungere a livello di codice nella definizione della classe ApplicationUser, alla fine archiviate in una tabella di database. Il nome di tabella predefinito è AspNetUsers. Inoltre, la classe IdentityUser espone alcune proprietà più come account di accesso, crediti e ruoli. Queste proprietà non sono memorizzate nella tabella AspNetUsers, ma trovare il loro posto in altre tabelle laterali nello stesso database — AspNetUserRoles, AspNetUserLogins e AspNetUserClaims (vedere Figura 2).

struttura predefinita del Database utente identità ASP.NET
Figura 2 struttura predefinita del Database utente identità ASP.NET

Modificare la classe ApplicationUser non garantisce la che avrete ulteriori campi immediatamente riflessa nell'interfaccia utente e salvati nel database. Per fortuna, l'aggiornamento del database non prende troppo lavoro.

Modifiche al codice messo

Sarà necessario modificare le viste di applicazione e modelli per riflettere i nuovi campi. Il modulo di richiesta dove nuovi utenti registrati al sito, sarà necessario aggiungere alcuni tag per mostrare l'interfaccia utente per il codice magico e qualsiasi altri campi. Figura 3 illustrato il codice modificato per il file CSHTML rasoio dietro la forma del registro.

Figura 3 rasoio File agli utenti presenti con campi aggiuntivi

@using (Html.BeginForm("Register", "Account",
  FormMethod.Post, new 
    { @class = "form-horizontal", role = "form" }))
{
  @Html.AntiForgeryToken()
  <h4>Create a new account.</h4>
  <hr />
  @Html.ValidationSummary()
  <div class="form-group">
    @Html.LabelFor(m => m.MagicCode, 
      new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
      @Html.TextBoxFor(m => m.MagicCode, 
        new { @class = "form-control" })
    </div>
  </div>   
  ...
}

Il modulo di registro CSHTML è basato su una classe di modello di visualizzazione, convenzionalmente chiamato RegisterViewModel. Figura 4 Mostra le modifiche per inserire il meccanismo di convalida classico della maggior parte delle applicazioni ASP.NET MVC basata su annotazioni di dati necessaria alla classe RegisterViewModel.

Figura 4 modifiche alla classe di modello di registro vista

public class RegisterViewModel
{
  [Required]
  [Display(Name = "User name")]
  public string UserName { get; set; }
  [Required]
  [StringLength(100, ErrorMessage = 
    "The {0} must be at least {1} character long.",
    MinimumLength = 6)]
  [DataType(DataType.Password)]
  [Display(Name = "Password")]
  public string Password { get; set; }
  [DataType(DataType.Password)]
  [Display(Name = "Confirm password")]
  [Compare("Password", ErrorMessage = 
    "Password and confirmation do not match.")]
  public string ConfirmPassword { get; set; }
  [Required]
  [Display(Name = "Internal magic code")]
  public string MagicCode { get; set; }
}

Questi cambiamenti non sono sufficienti, tuttavia. C'è un altro passo necessario ed è il più critico. Devi passare i dati supplementari fino allo strato che scriverà all'archivio persistente. È necessario effettuare ulteriori modifiche al metodo controller che elabora l'azione POST dal modulo di registro:

public async Task<ActionResult> Register(RegisterViewModel model)
{
  if (ModelState.IsValid) {
    var user = new ApplicationUser() { UserName = model.UserName,
      MagicCode = model.MagicCode };
    var result = await UserManager.CreateAsync(user, model.Password);
    if (result.Succeeded) {
      await SignInAsync(user, isPersistent: false);
      return RedirectToAction("Index", "Home");
    }
}

Il cambio di tonalità è salvare i dati inseriti per il codice magico (o qualsiasi altra cosa che si desidera aggiungere alla definizione dell'utente). Questo viene salvato nell'istanza ApplicationUser viene passato al metodo CreateAsync dell'oggetto UserManager.

Cerca nell'archivio persistente

Nell'esempio di codice generato dal modello MVC ASP.NET , la classe AccountController ha un membro definito qui:

public UserManager<ApplicationUser> UserManager { get; private set; }

Viene creata un'istanza della classe UserManager passando l'oggetto dell'archivio utente. ASP.NET Identità viene fornito con un archivio di utente predefinito:

var defaultUserStore = new UserStore<ApplicationUser>(new ApplicationDbContext())

Tanto come ApplicationUser, la classe ApplicationDbContext eredita da una classe definita dal sistema (denominata IdentityDbContext) e avvolge il Entity Framework per fare il lavoro effettivo di persistenza. La grande notizia è potete scollegare del tutto il meccanismo di archiviazione predefinito e rotolare il proprio. Si può basare il motore di archiviazione personalizzati sul SQL Server ed Entity Framework e basta usare uno schema diverso. Si potrebbe anche approfittare di un motore di archiviazione completamente diversi come MySQL o una soluzione NoSQL. Vediamo come organizzare un archivio utente basato su una versione embedded di RavenDB (ravendb.net). Il prototipo della classe, sarà necessario è illustrato di seguito:

public class RavenDbUserStore<TUser> :
  IUserStore<TUser>, IUserPasswordStore<TUser>
  where TUser : TypicalUser
{
  ...
}

Se si intende supportare gli accessi, ruoli e crediti, sarà necessario implementare più interfacce. Per una soluzione di lavoro minima, IUserStore e IUserPasswordStore sono sufficienti. La classe tipica­utente è una classe personalizzata creata per rimanere disaccoppiato dall'infrastruttura ASP.NET identità quanto possibile:

public class TypicalUser : IUser
{
  // IUser interface
  public String Id { get; set; }
  public String UserName { get; set; }
  // Other members
  public String Password { get; set; }
  public String MagicCode { get; set; }
}

Perlomeno, la classe utente deve implementare l'interfaccia IUser. L'interfaccia conta due membri — Id e UserName. Probabilmente si vorrà aggiungere un membro di Password, pure. Questa è la classe di utente viene salvata nell'archivio RavenDB.

Aggiungere il supporto di RavenDB al progetto tramite il pacchetto RavenDB.Embedded NuGet. In Global. asax, sarà anche necessario inizializzare il database con il seguente codice:

private static IDocumentStore _instance;
public static IDocumentStore Initialize()
{
  _instance = new EmbeddableDocumentStore 
    { ConnectionStringName = "RavenDB" };
  _instance.Initialize();
  return _instance;
}

La stringa di connessione punta al percorso dove è necessario creare il database. In un'applicazione Web ASP.NET , il naturale adatta è una sottocartella sotto App_Data:

<add name="RavenDB" connectionString="DataDir = ~\App_Data\Ravendb" />

Classe archivio utente contiene il codice per i metodi nelle interfacce IUserStore e IUserPasswordStore. Questi Consenti l'applicazione di gestire gli utenti e le relative password. Figura 5 viene illustrata l'implementazione dell'archivio.

Figura 5 minimamente lavoro archivio utente basato su RavenDB

public class RavenDbUserStore<TUser> :
  IUserStore<TUser>, IUserPasswordStore<TUser>
  where TUser : TypicalUser
{
  private IDocumentSession DocumentSession { get; set; }
  public RavenDbUserStore()
  {
    DocumentSession = RavenDbConfig.Instance.OpenAsyncSession();
  }
  public Task CreateAsync(TUser user)
  {
    if (user == null)
      throw new ArgumentNullException();
    DocumentSession.Store(user);
    return Task.FromResult<Object>(null);
  }
  public Task<TUser> FindByIdAsync(String id)
  {
    if (String.IsNullOrEmpty(id))
      throw new ArgumentException();
    var user = DocumentSession.Load<TUser>(id);
    return Task.FromResult<TUser>(user);
  }
  public Task<TUser> FindByNameAsync(String userName)
  {
    if (string.IsNullOrEmpty(userName))
      throw new ArgumentException("Missing user name");
    var user = DocumentSession.Query<TUser>()
      .FirstOrDefault(u => u.UserName == userName);
    return Task.FromResult<TUser>(user);
  }
  public Task UpdateAsync(TUser user)
  {
    if (user != null)
      DocumentSession.Store(user);
    return Task.FromResult<Object>(null);
  }
  public Task DeleteAsync(TUser user)
  {
    if (user != null)
      DocumentSession.Delete(user);
    return Task.FromResult<Object>(null);
  }
  public void Dispose()
  {
    if (DocumentSession == null)
      return;
    DocumentSession.SaveChanges();
    DocumentSession.Dispose();
  }
  public Task SetPasswordHashAsync(TUser user, String passwordHash)
  {
    user.Password = passwordHash;
    return Task.FromResult<Object>(null);
  }
  public Task<String> GetPasswordHashAsync(TUser user)
  {
    var passwordHash = user.Password;
    return Task.FromResult<string>(passwordHash);
  }
  public Task<Boolean> HasPasswordAsync(TUser user)
  {
    var hasPassword = String.IsNullOrEmpty(user.Password);
    return Task.FromResult<Boolean>(hasPassword);
  }
 }
}

Qualsiasi interazione con RavenDB passa attraverso l'apertura e la chiusura di una sessione di deposito del documento. Nel costruttore della RavenDbUserStore, si apre la sessione e respingerlo nel metodo Dispose dell'oggetto del negozio. Prima di congedare la sessione, però, chiamare il metodo SaveChanges per mantenere tutte le modifiche in sospeso secondo il modello di unità di lavoro:

public void Dispose()
{
  if (DocumentSession == null)
    return;
  DocumentSession.SaveChanges();
  DocumentSession.Dispose();
}

L'API per lavorare con il database di RavenDB è abbastanza semplice. Ecco il codice che necessario per creare un nuovo utente:

public Task CreateAsync(TUser user)
{
  if (user == null)
    throw new ArgumentNullException();
  DocumentSession.Store(user);
  return Task.FromResult<Object>(null);
}

Per recuperare un determinato utente, utilizzare il metodo Query sul documento­oggetto di sessione:

var user = DocumentSession.Load<TUser>(id);

RavenDB assume qualsiasi classe che persisti ha una proprietà Id. Se non, creerà implicitamente tale proprietà così si può sempre utilizzare il metodo Load per recuperare qualsiasi oggetto ID, se è il tuo ID o un ID generato dal sistema. Per recuperare un utente di nome, eseguire una query classica utilizzando una sintassi LINQ :

var user = DocumentSession
              .Query<TUser>()
              .FirstOrDefault(u => u.UserName == userName);

Utilizzare ToList per selezionare una varietà di oggetti e memorizzarli in un elenco gestibile. Gestione delle password è altrettanto semplice. RavenDB memorizza le password in formato hash, ma di hash è gestito di fuori del modulo RavenDB. Il metodo di SetPasswordHashAsync, infatti, già riceve l'hash della password che l'utente fornisce.

Figura 5 è il codice sorgente completo di istituire un archivio utente di RavenDB compatibile con identità ASP.NET . È sufficiente accedere utenti dentro e fuori, quando hai la versione embedded di RavenDB installato. Per caratteristiche più sofisticate come gli accessi esterni e gestione account, è necessario implementare tutte le interfacce utente correlati di identità ASP.NET o signifi­cativo rielaborare il codice del conto­Controller si ottiene da ponteggi.

La linea di fondo

ASP.NET Identità consente di scollegare completamente il Entity Framework-basato su infrastruttura di storage e uso RavenDB, invece, un documento e privo di schemi database. Può installare RavenDB come servizio di Windows, un'applicazione IIS o incorporati come ho fatto qui. Basta scrivere una classe che implementa alcune interfacce di identità ASP.NET e iniettare questa nuovo negozio di classe per l'infrastruttura UserManager. Modificare lo schema di tipo utente è facile, anche nella configurazione predefinita basata su EF. Se si utilizza RavenDB, però, è sbarazzarsi di eventuali problemi di migrazione dovrebbe cambiare il formato dell'utente.


Dino Esposito ' co-autore di "Microsoft .NET: Architecting Applications for the Enterprise"(Microsoft Press, 2014) e" programmazione ASP.NET MVC 5"(Microsoft Press, 2014). Technical evangelist di Microsoft .NET Framework e piattaforme Android a JetBrains e relatore a eventi del settore in tutto il mondo, Esposito condivide la sua visione del software presso software2cents.wordpress.com e su Twitter a /despos..

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Mauro Servienti