วิธีการขยายคุณสมบัติที่มีอยู่ของ User.Identity


130

ฉันใช้ MVC5 Identity 2.0 เพื่อให้ผู้ใช้ล็อกอินเข้าสู่เว็บไซต์ของฉันซึ่งรายละเอียดการตรวจสอบความถูกต้องจะถูกเก็บไว้ในฐานข้อมูล SQL Asp.net Identity ได้รับการปรับใช้ในรูปแบบมาตรฐานตามที่พบได้ในแบบฝึกหัดออนไลน์มากมาย

คลาส ApplicationUser ใน IdentityModels ได้รับการขยายเพื่อรวมคุณสมบัติที่กำหนดเองบางอย่างเช่น OrganizationId จำนวนเต็ม แนวคิดคือผู้ใช้จำนวนมากสามารถสร้างและกำหนดให้กับองค์กรทั่วไปเพื่อวัตถุประสงค์ด้านความสัมพันธ์ของฐานข้อมูล

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

ฉันจะดึงคุณสมบัติ OrganizationId ของผู้ใช้ที่ล็อกอินในปัจจุบันจากภายในคอนโทรลเลอร์ได้อย่างไร สามารถใช้งานได้โดยใช้วิธีการเมื่อผู้ใช้เข้าสู่ระบบหรือฉันจะดึง OrganizationId จากฐานข้อมูลตาม UserId ทุกครั้งที่เมธอดคอนโทรลเลอร์ดำเนินการหรือไม่

อ่านรอบ ๆ บนเว็บฉันเห็นว่าฉันจำเป็นต้องใช้สิ่งต่อไปนี้เพื่อเข้าสู่ระบบ UserId เป็นต้น

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

อย่างไรก็ตาม OrganizationId ไม่ใช่คุณสมบัติที่มีอยู่ใน User.Identity ฉันจำเป็นต้องขยาย User.Identity เพื่อรวมคุณสมบัติ OrganizationId หรือไม่ ถ้าเป็นเช่นนั้นฉันจะไปเกี่ยวกับเรื่องนี้ได้อย่างไร

เหตุผลที่ฉันต้องการ OrganizationId บ่อยมากก็คือแบบสอบถามตารางจำนวนมากต้องพึ่งพา OrganizationId เพื่อดึงข้อมูลที่เกี่ยวข้องกับองค์กรที่เชื่อมโยงกับผู้ใช้ที่เข้าสู่ระบบ


3
คำตอบของฉันที่นี่ช่วยคุณได้หรือไม่?
รองเท้า

1
คำตอบเดียวกันจากฉันที่นี่: stackoverflow.com/a/28138594/809357 - หากคุณต้องการข้อมูลนี้เป็นประจำตลอดชีวิตของคำขอคุณสามารถวางไว้บนคุกกี้เพื่อเป็นการอ้างสิทธิ์
trailmax

1
ขอบคุณ @Shoe ทั้งสองคำตอบของคุณได้ผล นอกจากคำตอบของคุณแล้วฉันต้องเพิ่มการอ้างสิทธิ์เพื่อเก็บไว้ในคุกกี้ ในคลาส IdentityModels ฉันต้องเพิ่มuserIdentity.AddClaim (การอ้างสิทธิ์ใหม่ ("MyApp: OrganizationId", OrganizationId ToString ())); กับประชาชน async งาน <ClaimsIdentity> GenerateUserIdentityAsync (UserManager <ApplicationUser> ผู้จัดการ) วิธีการ
RobHurd

คำตอบ:


219

เมื่อใดก็ตามที่คุณต้องการขยายคุณสมบัติของ User.Identity ด้วยคุณสมบัติเพิ่มเติมใด ๆ เช่นคำถามด้านบนให้เพิ่มคุณสมบัติเหล่านี้ในคลาส ApplicationUser ก่อนดังนี้:

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

จากนั้นสิ่งที่คุณต้องการคือสร้างวิธีการขยายเช่นนี้ (ฉันสร้างของฉันในโฟลเดอร์ส่วนขยายใหม่):

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

เมื่อคุณสร้าง Identity ในคลาส ApplicationUser เพียงแค่เพิ่ม Claim -> OrganizationId ดังนี้:

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

เมื่อคุณเพิ่มการอ้างสิทธิ์และมีวิธีการส่วนขยายของคุณแล้วเพื่อให้พร้อมใช้งานเป็นคุณสมบัติในผู้ใช้ของคุณรหัสประจำตัวให้เพิ่มคำสั่งใช้ในหน้า / ไฟล์ที่คุณต้องการเข้าถึง :

ในกรณีของฉัน: using App.Extensions;ภายในคอนโทรลเลอร์และ@using. App.Extensionsด้วย. cshtml ดูไฟล์

แก้ไข:

สิ่งที่คุณสามารถทำได้เพื่อหลีกเลี่ยงการเพิ่มคำสั่งใช้ในทุก View คือไปที่โฟลเดอร์ Views และค้นหาไฟล์ Web.config ในนั้น ตอนนี้มองหา<namespaces>แท็กและเพิ่มเนมสเปซส่วนขยายของคุณดังนี้:

<add namespace="App.Extensions" />

บันทึกไฟล์ของคุณและคุณทำเสร็จแล้ว ตอนนี้ทุก View จะรู้ถึงส่วนขยายของคุณ

คุณสามารถเข้าถึงวิธีการขยาย:

var orgId = User.Identity.GetOrganizationId();

สวัสดี pawel ฉันได้ลองเหมือนกัน แต่ได้รับข้อผิดพลาดที่ผู้ใช้แอปพลิเคชันไม่มีคำจำกัดความของ OrganisationId
เป็นกับดัก

@RachitGupta เกิดขึ้นเมื่อไหร่? เมื่อคุณพยายามเพิ่มการอ้างสิทธิ์หรือเมื่อคุณพยายามเข้าถึงค่าในภายหลังในโค้ด? หากคุณกำลังเพิ่มการอ้างสิทธิ์โปรดตรวจสอบให้แน่ใจว่า ApplicationUser ของคุณมีคุณสมบัติที่กำหนดไว้ ... หากมีการกำหนดคุณสมบัติในภายหลังอย่าลืมเพิ่มคำสั่งใช้ในตำแหน่งที่คุณสร้างวิธีการขยายเช่น: โดยใช้ App.Extensions ;
Pawel

มีข้อผิดพลาดในบรรทัด userIdentity.AddClaim ฉันได้สร้างคลาส IdentityExtensions ในไฟล์ IdentityModels.cs เท่านั้น นั่นอาจเป็นที่มาของปัญหาได้หรือไม่?
เป็นกับดัก

ไม่ถ้าเป็นตอนที่คุณเพิ่มการอ้างสิทธิ์มันจะเกิดขึ้นก่อนที่ identityExtensions จะเข้ามามีบทบาท (นั่นคือเมื่อคุณอ่านค่าย้อนกลับ) ... ตรวจสอบให้แน่ใจว่า ApplicationUser ของคุณมีคุณสมบัติที่คุณพยายามจะเพิ่มเป็นการอ้างสิทธิ์: ในตัวอย่างนี้คือ สาธารณะยาว OrganizationId {get; ตั้ง; }
Pawel

6
ขอบคุณมันใช้งานได้ ฉันคิดว่านี่เป็นแนวทางปฏิบัติที่ดีที่สุดสำหรับตัวแปรที่กำหนดเองใน Asp Net Idendity 2 ฉันไม่รู้ว่าทำไมชุมชน Asp.Net ไม่ได้ให้ตัวอย่างเช่นนี้ในบทความเริ่มต้นบนเว็บไซต์ของพวกเขา
oneNiceFriend

17

ฉันกำลังมองหาวิธีแก้ปัญหาเดียวกันและ Pawel ให้คำตอบ 99% สิ่งเดียวที่ขาดหายไปที่ฉันต้องการเพื่อให้ส่วนขยายแสดงคือการเพิ่ม Razor Code ต่อไปนี้ในหน้า cshtml (view):

@using programname.Models.Extensions

ฉันกำลังมองหา FirstName เพื่อแสดงที่ด้านบนขวาของ NavBar ของฉันหลังจากที่ผู้ใช้เข้าสู่ระบบ

ฉันคิดว่าฉันจะโพสต์ในกรณีนี้เพื่อช่วยคนอื่นดังนั้นนี่คือรหัสของฉัน:

ฉันสร้างโฟลเดอร์ใหม่ชื่อ Extensions (ภายใต้โฟลเดอร์ Models ของฉัน) และสร้างคลาสใหม่ตามที่ Pawel ระบุไว้ข้างต้น: 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;
    }
}

จากนั้นใน_LoginPartial.cshtml(ภายใต้Views/Sharedโฟลเดอร์) ของฉันฉันเพิ่ม@using.ProgramName.Models.Extensions

จากนั้นฉันได้เพิ่มการเปลี่ยนแปลงในบรรทัดของรหัสที่จะใช้ชื่อผู้ใช้หลังจากเข้าสู่ระบบ:

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

บางทีสิ่งนี้อาจช่วยคนอื่นได้


11

ดูโพสต์บล็อกที่ยอดเยี่ยมนี้โดย John Atten: ASP.NET Identity 2.0: การปรับแต่งผู้ใช้และบทบาท

มีข้อมูลทีละขั้นตอนที่ดีเกี่ยวกับกระบวนการทั้งหมด ไปอ่านกันเลย :)

นี่คือบางส่วนของพื้นฐาน

ขยายคลาส ApplicationUser เริ่มต้นโดยการเพิ่มคุณสมบัติใหม่ (เช่น - ที่อยู่เมืองรัฐ ฯลฯ ):

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

จากนั้นคุณเพิ่มคุณสมบัติใหม่ของคุณลงใน RegisterViewModel ของคุณ

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

จากนั้นอัปเดตมุมมองการลงทะเบียนเพื่อรวมคุณสมบัติใหม่

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

จากนั้นอัปเดตเมธอด Register () บน AccountController ด้วยคุณสมบัติใหม่

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

16
นี่เป็นตัวอย่างที่ดี แต่ไม่ตอบคำถามนั่นคือคุณจะได้รับคุณสมบัติใหม่เหล่านั้นจาก User.Identity ได้อย่างไร
Dejan Bogatinovski

5
โหวตลงเนื่องจากคำตอบไม่ได้แสดงว่าคุณสมบัติที่กำหนดเองสามารถเรียกดูจาก User.Identity ได้อย่างไร
maulik13

3

สำหรับใครก็ตามที่พบคำถามนี้กำลังมองหาวิธีเข้าถึงคุณสมบัติที่กำหนดเองใน ASP.NET Core 2.1 - มันง่ายกว่ามาก: คุณจะมี UserManager เช่นใน _LoginPartial.cshtml จากนั้นคุณสามารถทำได้ (สมมติว่า "ScreenName" คือ คุณสมบัติที่คุณได้เพิ่มลงใน AppUser ของคุณเองซึ่งสืบทอดมาจาก 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
ควรสังเกตว่าGetUserAsync(User)จะสอบถามฐานข้อมูลเพื่อดึง OrganizationId ในทางตรงกันข้ามโซลูชันที่ยอมรับจะรวม OrganizationId ไว้ในการอ้างสิทธิ์ (เช่นคุกกี้) ข้อดีของการดึงข้อมูลนี้จากฐานข้อมูลคือสามารถย้ายบุคคลระหว่างองค์กรได้โดยไม่ต้องล็อกเอาต์ / ล็อกอิน แน่นอนข้อเสียคือต้องใช้แบบสอบถามฐานข้อมูลเพิ่มเติม
Matt

1

Dhaust เป็นวิธีที่ดีในการเพิ่มคุณสมบัติให้กับคลาส ApplicationUser เมื่อดูรหัส OP ปรากฏว่าพวกเขาอาจทำสิ่งนี้หรืออยู่ในระหว่างการติดตาม คำถามถามว่า

ฉันจะดึงคุณสมบัติ OrganizationId ของผู้ใช้ที่ล็อกอินในปัจจุบันจากภายในคอนโทรลเลอร์ได้อย่างไร อย่างไรก็ตาม OrganizationId ไม่ใช่คุณสมบัติที่มีอยู่ใน User.Identity ฉันจำเป็นต้องขยาย User.Identity เพื่อรวมคุณสมบัติ OrganizationId หรือไม่

Pawel ให้วิธีการเพิ่มวิธีการขยายที่ต้องใช้คำสั่งหรือเพิ่มเนมสเปซไปยังไฟล์ web.config

อย่างไรก็ตามคำถามจะถามว่าคุณ "จำเป็นต้อง" ขยาย User.Identity เพื่อรวมคุณสมบัติใหม่หรือไม่ มีทางเลือกอื่นในการเข้าถึงคุณสมบัติโดยไม่ต้องขยาย User.Identity หากคุณทำตามวิธี Dhaust คุณสามารถใช้รหัสต่อไปนี้ในตัวควบคุมของคุณเพื่อเข้าถึงคุณสมบัติใหม่

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

ฉันยังได้เพิ่มหรือขยายคอลัมน์เพิ่มเติมในตาราง AspNetUsers ของฉัน เมื่อฉันต้องการเพียงแค่ดูข้อมูลนี้ฉันพบตัวอย่างมากมายเช่นโค้ดด้านบนที่มี "ส่วนขยาย" เป็นต้นสิ่งนี้ทำให้ฉันประหลาดใจมากที่คุณต้องเขียนโค้ดเหล่านั้นทั้งหมดเพื่อให้ได้ค่าสองสามค่าจากผู้ใช้ปัจจุบัน

ปรากฎว่าคุณสามารถสอบถามตาราง AspNetUsers ได้เหมือนตารางอื่น ๆ :

 ApplicationDbContext db = new ApplicationDbContext();
 var user = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.