Come estendere le proprietà disponibili di User.Identity


130

Sto utilizzando MVC5 Identity 2.0 per consentire agli utenti di accedere al mio sito Web, dove i dettagli di autenticazione sono memorizzati in un database SQL. Asp.net Identity è stata implementata in modo standard come è possibile trovare in molti tutorial online.

La classe ApplicationUser in IdentityModels è stata estesa per includere alcune proprietà personalizzate, come OrganizationId intero. L'idea è che molti utenti possono essere creati e assegnati a un'organizzazione comune per scopi di relazione con il database.

public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }

        //Extended Properties
        public DateTime? BirthDate { get; set; }
        public long? OrganizationId { get; set; }

        //Key Mappings
        [ForeignKey("OrganizationId")]
        public virtual Organization Organization { get; set; }
    }

Come posso recuperare la proprietà OrganizationId dell'utente attualmente connesso da un controller? È disponibile tramite un metodo una volta che un utente ha effettuato l'accesso o devo sempre recuperare OrganizationId dal database, in base a UserId, ogni volta che viene eseguito un metodo controller?

Leggendo sul Web ho visto che devo usare quanto segue per ottenere l'Identificativo registrato ecc.

using Microsoft.AspNet.Identity;
...
User.Identity.GetUserId();

Tuttavia, OrganizationId non è una proprietà disponibile in User.Identity. Devo estendere User.Identity per includere la proprietà OrganizationId? In tal caso, come posso procedere.

Il motivo per cui ho bisogno di OrganizationId così spesso è che molte query di tabelle dipendono da OrganizationId per recuperare i dati rilevanti per l'organizzazione associati all'utente che ha effettuato l'accesso.


3
La mia risposta qui ti aiuta affatto?
Scarpa

1
Praticamente la stessa risposta da me qui: stackoverflow.com/a/28138594/809357 - se hai bisogno di queste informazioni regolarmente nella vita della richiesta, puoi inserirle nel cookie come reclamo.
trailmax,

1
Grazie @Scara entrambe le risposte hanno funzionato. Oltre alle tue risposte, ho dovuto aggiungere un reclamo per essere memorizzato nel cookie. Nella classe IdentityModels ho dovuto aggiungere userIdentity.AddClaim (new Claim ("MyApp: OrganizationId", OrganizationId.ToString ())); al metodo dell'attività asincrona <ClaimsIdentity> GenerateUserIdentityAsync (gestore UserAppager <ApplicationUser>) .
RobHurd,

Risposte:


219

Ogni volta che si desidera estendere le proprietà di User.Identity con eventuali proprietà aggiuntive come la domanda precedente, aggiungere prima queste proprietà alla classe ApplicationUser in questo modo:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }

    // Your Extended Properties
    public long? OrganizationId { get; set; }
}

Quindi quello che ti serve è creare un metodo di estensione in questo modo (creo il mio in una nuova cartella di estensioni):

namespace App.Extensions
{
    public static class IdentityExtensions
    {
        public static string GetOrganizationId(this IIdentity identity)
        {
            var claim = ((ClaimsIdentity)identity).FindFirst("OrganizationId");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        }
    }
}

Quando crei l'identità nella classe ApplicationUser, aggiungi semplicemente Claim -> OrganizationId in questo modo:

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here => this.OrganizationId is a value stored in database against the user
        userIdentity.AddClaim(new Claim("OrganizationId", this.OrganizationId.ToString()));

        return userIdentity;
    }

Dopo aver aggiunto il reclamo e aver attivato il metodo di estensione, per renderlo disponibile come proprietà sul tuo Utente. Identità, aggiungi un'istruzione using sulla pagina / file a cui desideri accedervi :

nel mio caso: using App.Extensions;all'interno di un controller e all'interno di un @using. App.Extensionsfile .cshtml View.

MODIFICARE:

Quello che puoi fare anche per evitare di aggiungere un'istruzione using in ogni Vista è andare nella cartella Viste e individuare il file Web.config in essa. Ora cerca il <namespaces>tag e aggiungi lo spazio dei nomi delle estensioni lì in questo modo:

<add namespace="App.Extensions" />

Salva il tuo file e il gioco è fatto. Ora ogni vista saprà delle tue estensioni.

È possibile accedere al metodo di estensione:

var orgId = User.Identity.GetOrganizationId();

Ciao Pawel, ho provato lo stesso, ma ricevendo l'errore che l'utente dell'applicazione non contiene una definizione di OrganisationId
È una trappola

@RachitGupta Quando succede? Quando si tenta di aggiungere il reclamo o quando si tenta di accedere al suo valore in un secondo momento nel codice? Se è quando stai aggiungendo un reclamo, assicurati che ApplicationUser abbia la proprietà definita ... Se è più avanti nel codice, non dimenticare di aggiungere l'istruzione using a dove hai creato il metodo di estensione come: using App.Extensions ;
Pawel,

Errore nella riga userIdentity.AddClaim. Ho creato la classe IdentityExtensions solo nel file IdentityModels.cs. Può essere una fonte di problemi?
È una trappola il

No, se è quando aggiungi il reclamo succede molto prima che entri in gioco identityExtensions (è quando leggi il valore indietro) ... Assicurati che il tuo ApplicationUser abbia la proprietà che stai cercando di aggiungere come reclamo: in questo esempio è stato public lungo OrganizationId {get; impostato; }
Pawel,

6
Grazie, ha funzionato. Penso che questa sia la migliore pratica per le variabili personalizzate in Asp Net Idendity 2. Non so perché la comunità Asp.Net non fornisca un tale esempio negli articoli predefiniti sui loro siti Web.
oneNiceFriend

17

Stavo cercando la stessa soluzione e Pawel mi ha dato il 99% della risposta. L'unica cosa che mancava per la visualizzazione dell'estensione era l'aggiunta del seguente codice Razor nella pagina cshtml (view):

@using programname.Models.Extensions

Stavo cercando il FirstName, da visualizzare in alto a destra sul mio NavBar dopo che l'utente ha effettuato l'accesso.

Ho pensato di pubblicare questo messaggio in caso di aiuto a qualcun altro, quindi ecco il mio codice:

Ho creato una nuova cartella denominata Extensions (Under my Models Folder) e ho creato la nuova classe come Pawel specificato sopra: IdentityExtensions.cs

using System.Security.Claims;
using System.Security.Principal;

namespace ProgramName.Models.Extensions
{
    public static class IdentityExtensions
    {
        public static string GetUserFirstname(this IIdentity identity)
        {
            var claim = ((ClaimsIdentity)identity).FindFirst("FirstName");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        }
    }
}

IdentityModels.cs :

public class ApplicationUser : IdentityUser
{

    //Extended Properties
    public string FirstName { get; internal set; }
    public string Surname { get; internal set; }
    public bool isAuthorized { get; set; }
    public bool isActive { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        userIdentity.AddClaim(new Claim("FirstName", this.FirstName));

        return userIdentity;
    }
}

Poi nella mia _LoginPartial.cshtml(sotto le Views/Sharedcartelle) ho aggiunto@using.ProgramName.Models.Extensions

Ho quindi aggiunto la modifica alla riga di codice che stava per utilizzare il Nome utenti dopo aver effettuato l'accesso:

@Html.ActionLink("Hello " + User.Identity.GetUserFirstname() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })

Forse questo aiuta qualcun altro lungo la linea.


11

Dai un'occhiata a questo fantastico post sul blog di John Atten: ASP.NET Identity 2.0: personalizzazione di utenti e ruoli

Ha ottime informazioni dettagliate sull'intero processo. Vai a leggerlo:)

Ecco alcune delle basi.

Estendi la classe ApplicationUser predefinita aggiungendo nuove proprietà (ad es. Indirizzo, Città, Stato, ecc.):

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
    GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(this,  DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }

    // Concatenate the address info for display in tables and such:
    public string DisplayAddress
    {
        get
        {
            string dspAddress = string.IsNullOrWhiteSpace(this.Address) ? "" : this.Address;
            string dspCity = string.IsNullOrWhiteSpace(this.City) ? "" : this.City;
            string dspState = string.IsNullOrWhiteSpace(this.State) ? "" : this.State;
            string dspPostalCode = string.IsNullOrWhiteSpace(this.PostalCode) ? "" : this.PostalCode;

            return string.Format("{0} {1} {2} {3}", dspAddress, dspCity, dspState, dspPostalCode);
        }
    }

Quindi aggiungi le nuove proprietà a RegisterViewModel.

    // Add the new address properties:
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }

Quindi aggiornare la vista Registro per includere le nuove proprietà.

    <div class="form-group">
        @Html.LabelFor(m => m.Address, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
        </div>
    </div>

Quindi aggiornare il metodo Register () su AccountController con le nuove proprietà.

    // Add the Address properties:
    user.Address = model.Address;
    user.City = model.City;
    user.State = model.State;
    user.PostalCode = model.PostalCode;

16
Questo è un buon esempio ma non risponde alla domanda che è, come si ottengono queste nuove proprietà da User.Identity.
Dejan Bogatinovski,

5
Sottovalutato perché la risposta non mostra come le proprietà personalizzate possono essere recuperate da User.Identity.
maulik13

3

Per chiunque trovi questa domanda cercando come accedere alle proprietà personalizzate in ASP.NET Core 2.1 - è molto più semplice: avrai un UserManager, ad esempio in _LoginPartial.cshtml, e poi puoi semplicemente farlo (supponendo che "ScreenName" sia un proprietà che hai aggiunto al tuo AppUser che eredita da IdentityUser):

@using Microsoft.AspNetCore.Identity

@using <namespaceWhereYouHaveYourAppUser>

@inject SignInManager<AppUser> SignInManager
@inject UserManager<AppUser> UserManager

@if (SignInManager.IsSignedIn(User)) {
    <form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })" 
          method="post" id="logoutForm" 
          class="form-inline my-2 my-lg-0">

        <ul class="nav navbar-nav ml-auto">
            <li class="nav-item">
                <a class="nav-link" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">
                    Hello @((await UserManager.GetUserAsync(User)).ScreenName)!
                    <!-- Original code, shows Email-Address: @UserManager.GetUserName(User)! -->
                </a>
            </li>
            <li class="nav-item">
                <button type="submit" class="btn btn-link nav-item navbar-link nav-link">Logout</button>
            </li>
        </ul>

    </form>
} else {
    <ul class="navbar-nav ml-auto">
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Register">Register</a></li>
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Login">Login</a></li>
    </ul>
}

2
Va notato che GetUserAsync(User)interrogherà il database per recuperare OrganizationId. Al contrario, la soluzione accettata includerà OrganizationId nei reclami (ad es. Cookie). Il vantaggio di estrarre queste informazioni dal database è che le persone possono essere spostate tra le organizzazioni senza richiedere loro di disconnettersi / accedere. Naturalmente, lo svantaggio è che richiede una query aggiuntiva sul database.
Matt,

1

Dhaust offre un buon modo per aggiungere la proprietà alla classe ApplicationUser. Guardando il codice OP sembra che abbiano fatto questo o che fossero sulla buona strada per farlo. La domanda si pone

Come posso recuperare la proprietà OrganizationId dell'utente attualmente connesso da un controller? Tuttavia, OrganizationId non è una proprietà disponibile in User.Identity. Devo estendere User.Identity per includere la proprietà OrganizationId?

Pawel offre un modo per aggiungere un metodo di estensione che richiede l'utilizzo di istruzioni o l'aggiunta dello spazio dei nomi al file web.config.

Tuttavia, la domanda chiede se è necessario "estendere" User.Identity per includere la nuova proprietà. Esiste un modo alternativo per accedere alla proprietà senza estendere User.Identity. Se hai seguito il metodo Dhaust, puoi utilizzare il seguente codice nel controller per accedere alla nuova proprietà.

ApplicationDbContext db = new ApplicationDbContext();
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
var currentUser = manager.FindById(User.Identity.GetUserId());
var myNewProperty = currentUser.OrganizationId;

1

Avevo anche aggiunto o esteso colonne aggiuntive nella mia tabella AspNetUsers. Quando volevo semplicemente visualizzare questi dati ho trovato molti esempi come il codice sopra con "Estensioni", ecc ... Questo mi ha davvero stupito che dovevi scrivere tutte quelle righe di codice solo per ottenere un paio di valori dagli utenti attuali.

Si scopre che è possibile eseguire una query sulla tabella AspNetUsers come qualsiasi altra tabella:

 ApplicationDbContext db = new ApplicationDbContext();
 var user = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.