Jak rozszerzyć dostępne właściwości User.Identity

130

Używam MVC5 Identity 2.0, aby użytkownicy logowali się do mojej witryny internetowej, gdzie szczegóły uwierzytelniania są przechowywane w bazie danych SQL. Tożsamość Asp.net została zaimplementowana w standardowy sposób, co można znaleźć w wielu samouczkach online.

Klasa ApplicationUser w IdentityModels została rozszerzona o niektóre właściwości niestandardowe, takie jak liczba całkowita OrganizationId. Chodzi o to, że wielu użytkowników można utworzyć i przypisać do wspólnej organizacji na potrzeby relacji z bazą danych.

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; }
    }

Jak mogę pobrać właściwość OrganizationId aktualnie zalogowanego użytkownika z poziomu kontrolera? Czy jest to dostępne za pomocą metody, gdy użytkownik jest zalogowany, czy też zawsze pobieram identyfikator organizacji z bazy danych na podstawie identyfikatora użytkownika za każdym razem, gdy jest wykonywana metoda kontrolera?

Czytając w Internecie, zauważyłem, że muszę użyć następujących elementów, aby uzyskać zalogowany identyfikator użytkownika itp.

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

Jednak OrganizationId nie jest właściwością dostępną w User.Identity. Czy muszę rozszerzyć User.Identity, aby uwzględnić właściwość OrganizationId? Jeśli tak, jak mam się do tego zabrać.

Powodem, dla którego potrzebuję identyfikatora OrganizationId tak często, jest to, że wiele zapytań do tabel jest zależnych od identyfikatora OrganizationId w celu pobrania danych odpowiednich dla organizacji, która jest skojarzona z zalogowanym użytkownikiem.

RobHurd
źródło
3
Czy moja odpowiedź tutaj w ogóle ci pomaga?
But
1
Prawie ta sama odpowiedź ode mnie tutaj: stackoverflow.com/a/28138594/809357 - jeśli potrzebujesz tych informacji regularnie w życiu żądania, możesz umieścić je w pliku cookie jako żądanie.
trailmax
1
Dzięki @Shoe obie odpowiedzi zadziałały. Oprócz twoich odpowiedzi musiałem dodać żądanie przechowywania w pliku cookie. W klasie IdentityModels musiałem dodać userIdentity.AddClaim (new Claim ("MyApp: OrganizationId", OrganizationId.ToString ())); do publicznej metody Async Task <ClaimsIdentity> GenerateUserIdentityAsync (UserManager <ApplicationUser> menedżera) .
RobHurd

Odpowiedzi:

221

Ilekroć chcesz rozszerzyć właściwości User.Identity o wszelkie dodatkowe właściwości, takie jak powyższe pytanie, najpierw dodaj te właściwości do klasy ApplicationUser w następujący sposób:

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; }
}

Następnie musisz utworzyć taką metodę rozszerzenia (tworzę swoją w nowym folderze Rozszerzenia):

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;
        }
    }
}

Kiedy tworzysz tożsamość w klasie ApplicationUser, po prostu dodaj Claim -> OrganizationId w następujący sposób:

    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;
    }

Po dodaniu roszczenia i wprowadzeniu metody rozszerzenia, aby udostępnić ją jako właściwość na swoim koncie użytkownika.Tożsamość, dodaj instrukcję using na stronie / pliku, do którego chcesz uzyskać dostęp :

w moim przypadku: using App.Extensions;w kontrolerze i za @using. App.Extensionspomocą pliku widoku .cshtml.

EDYTOWAĆ:

Aby uniknąć dodawania instrukcji using w każdym widoku, możesz również przejść do folderu Views i zlokalizować tam plik Web.config. Teraz poszukaj <namespaces>tagu i dodaj tam swoją przestrzeń nazw rozszerzenia:

<add namespace="App.Extensions" />

Zapisz plik i gotowe. Teraz każdy widok będzie wiedział o twoich rozszerzeniach.

Możesz uzyskać dostęp do metody rozszerzenia:

var orgId = User.Identity.GetOrganizationId();
Paweł
źródło
Cześć Paweł, próbowałem tego samego, ale pojawia się błąd, że użytkownik aplikacji nie zawiera definicji OrganisationId
To pułapka.
@RachitGupta Kiedy to się dzieje? Kiedy próbujesz dodać roszczenie lub próbujesz uzyskać dostęp do jego wartości później w kodzie? Jeśli jest to podczas dodawania roszczenia, upewnij się, że ApplicationUser ma zdefiniowaną właściwość ... Jeśli jest później w kodzie, nie zapomnij dodać instrukcji using w miejscu, w którym utworzyłeś metodę rozszerzenia, taką jak: using App.Extensions ;
Paweł
Wystąpił błąd w wierszu userIdentity.AddClaim. Utworzyłem klasę IdentityExtensions tylko w pliku IdentityModels.cs. Czy to może być źródłem problemu?
To pułapka
Nie, jeśli dodajesz roszczenie, dzieje się to na długo przed wejściem w grę identityExtensions (wtedy, gdy odczytujesz wartość z powrotem) ... Upewnij się, że ApplicationUser ma właściwość, którą próbujesz dodać jako oświadczenie: w tym przykładzie była to public long OrganizationId {get; zestaw; }
Paweł
6
Dzięki, zadziałało. Myślę, że jest to najlepsza praktyka dla zmiennych niestandardowych w Asp Net Idendity 2. Nie wiem, dlaczego społeczność Asp.Net nie podaje takiego przykładu w domyślnych artykułach na swoich stronach internetowych.
oneNiceFriend
17

Szukałem tego samego rozwiązania i Paweł udzielił mi 99% odpowiedzi. Jedyne, czego mi brakowało, aby rozszerzenie wyświetlało się, to dodanie następującego kodu Razor do strony cshtml (widok):

@using programname.Models.Extensions

Szukałem FirstName, aby wyświetlić w prawym górnym rogu mojego NavBar po zalogowaniu się użytkownika.

Pomyślałem, że opublikuję to, jeśli pomoże to komuś innemu, więc oto mój kod:

Stworzyłem nowy folder o nazwie Extensions (Under my Models Folder) i utworzyłem nową klasę, jak Paweł wskazał powyżej: 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;
    }
}

Następnie w moich _LoginPartial.cshtml(Pod Views/Sharedfolderach) dodałem@using.ProgramName.Models.Extensions

Następnie dodałem zmianę do następującego wiersza kodu, który miał używać imienia użytkownika po zalogowaniu:

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

Być może pomoże to komuś innemu.

AxleWack
źródło
11

Zapoznaj się z tym świetnym wpisem na blogu autorstwa Johna Attena: ASP.NET Identity 2.0: dostosowywanie użytkowników i ról

Zawiera świetne informacje krok po kroku o całym procesie. Przeczytaj to :)

Oto kilka podstaw.

Rozszerz domyślną klasę ApplicationUser, dodając nowe właściwości (np. Adres, Miasto, Stan itp.):

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);
        }
    }

Następnie dodaj nowe właściwości do swojego RegisterViewModel.

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

Następnie zaktualizuj widok rejestru, aby uwzględnić nowe właściwości.

    <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>

Następnie zaktualizuj metodę Register () w AccountController przy użyciu nowych właściwości.

    // Add the Address properties:
    user.Address = model.Address;
    user.City = model.City;
    user.State = model.State;
    user.PostalCode = model.PostalCode;
Wyczerpanie
źródło
16
To jest dobry przykład, ale nie odpowiada na pytanie, jak uzyskać te nowe właściwości od User.Identity.
Dejan Bogatinovski
5
Negocjowano, ponieważ odpowiedź nie pokazuje, w jaki sposób można pobrać właściwości niestandardowe z User.Identity.
maulik13,
3

Dla każdego, kto znajdzie to pytanie, szukając sposobu uzyskania dostępu do właściwości niestandardowych w ASP.NET Core 2.1 - jest to znacznie łatwiejsze: będziesz mieć UserManager, np. W _LoginPartial.cshtml, a następnie możesz po prostu to zrobić (zakładając, że „ScreenName” to właściwość dodana do własnego AppUser, która dziedziczy po 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>
}
Jashan
źródło
2
Należy zauważyć, że GetUserAsync(User)wysyła zapytanie do bazy danych w celu pobrania OrganizationId. Natomiast przyjęte rozwiązanie będzie zawierało OrganizationId w roszczeniach (np. Plik cookie). Zaletą pobierania tych informacji z bazy danych jest to, że ludzie mogą być przenoszeni między organizacjami bez konieczności wylogowywania się / logowania. Oczywiście wadą jest to, że wymaga dodatkowego zapytania do bazy danych.
Matt,
1

Wyczerpanie to dobry sposób na dodanie właściwości do klasy ApplicationUser. Patrząc na kod OP, wydaje się, że mogli to zrobić lub byli na dobrej drodze, aby to zrobić. Pytanie zadaje

Jak mogę pobrać właściwość OrganizationId aktualnie zalogowanego użytkownika z poziomu kontrolera? Jednak OrganizationId nie jest właściwością dostępną w User.Identity. Czy muszę rozszerzyć User.Identity, aby uwzględnić właściwość OrganizationId?

Paweł daje możliwość dodania metody rozszerzenia, która wymaga użycia instrukcji lub dodania przestrzeni nazw do pliku web.config.

Jednak pojawia się pytanie, czy „trzeba” rozszerzyć User.Identity, aby uwzględnić nową właściwość. Istnieje alternatywny sposób uzyskiwania dostępu do właściwości bez rozszerzania właściwości User.Identity. Jeśli zastosowałeś metodę Wyczerpywanie, możesz użyć następującego kodu w kontrolerze, aby uzyskać dostęp do nowej właściwości.

ApplicationDbContext db = new ApplicationDbContext();
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
var currentUser = manager.FindById(User.Identity.GetUserId());
var myNewProperty = currentUser.OrganizationId;
codeMethod
źródło
1

Dodałem również lub rozszerzyłem dodatkowe kolumny do mojej tabeli AspNetUsers. Kiedy chciałem po prostu przejrzeć te dane, znalazłem wiele przykładów, takich jak powyższy kod z "Rozszerzeniami" itp. To naprawdę zdziwiło mnie, że trzeba było napisać wszystkie te linie kodu, aby uzyskać kilka wartości od obecnych użytkowników.

Okazuje się, że możesz wysyłać zapytania do tabeli AspNetUsers jak w przypadku każdej innej tabeli:

 ApplicationDbContext db = new ApplicationDbContext();
 var user = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault();
Anthony Griggs
źródło