[英]How to access custom ApplicationUser properties from view in ASP.net core 6?
[英]How should I access my ApplicationUser properties from ASP.NET Core Views?
我正在開發一個ASP.Net vNext / MVC6項目。 我正在掌握ASP.Net Identity。
ApplicationUser
類顯然是我應該添加任何其他用戶屬性的地方,這適用於Entity Framework,我的其他屬性按預期存儲在數據庫中。
但是,當我想從我的視圖中訪問當前登錄用戶的詳細信息時,問題就出現了。 具體來說,我有一個_loginPartial.cshtml
,我想在其中檢索並顯示用戶的Gravatar圖標,我需要該電子郵件地址。
Razor View
基類有一個User屬性,它是一個ClaimsPrincipal
。 如何從此User
屬性返回到我的ApplicationUser
,以檢索我的自定義屬性?
請注意,我不是在詢問如何查找信息; 我知道如何從User.GetUserId()
值中查找ApplicationUser
。 這是一個關於如何合理地解決這個問題的問題。 具體來說,我不想:
這似乎是一個“橫切關注點”,應該有一個集中的標准解決方案,但我覺得我錯過了一塊拼圖游戲。 從視圖中獲取這些自定義用戶屬性的最佳方法是什么?
注意:似乎MVC團隊通過確保UserName屬性始終設置為用戶的電子郵件地址,在項目模板中側面解決了這個問題,巧妙地避免了他們執行此查找以獲取用戶的電子郵件地址! 這對我來說似乎有點欺騙,在我的解決方案中,用戶的登錄名可能是也可能不是他們的電子郵件地址,所以我不能依賴這個技巧(我懷疑還有其他屬性我需要稍后訪問)。
我認為您應該為此目的使用User的Claims屬性。 我找到了很好的帖子: http : //benfoster.io/blog/customising-claims-transformation-in-aspnet-core-identity
用戶類
public class ApplicationUser : IdentityUser
{
public string MyProperty { get; set; }
}
我們將MyProperty放入經過身份驗證的用戶聲明中。 為此,我們重寫了UserClaimsPrincipalFactory
public class MyUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public MyUserClaimsPrincipalFactory (
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
}
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
//Putting our Property to Claims
//I'm using ClaimType.Email, but you may use any other or your own
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim(ClaimTypes.Email, user.MyProperty)
});
return principal;
}
}
在Startup.cs中注冊我們的UserClaimsPrincipalFactory
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, MyUserClaimsPrincipalFactory>();
//...
}
現在我們可以像這樣訪問我們的主題
User.Claims.FirstOrDefault(v => v.Type == ClaimTypes.Email).Value;
我們可以創建一個擴展
namespace MyProject.MyExtensions
{
public static class MyUserPrincipalExtension
{
public static string MyProperty(this ClaimsPrincipal user)
{
if (user.Identity.IsAuthenticated)
{
return user.Claims.FirstOrDefault(v => v.Type == ClaimTypes.Email).Value;
}
return "";
}
}
}
我們應該將@Using添加到View(我將它添加到全局_ViewImport.cshtml)
@using MyProject.MyExtensions
最后,我們可以在任何View中使用此屬性作為方法調用
@User.MyProperty()
在這種情況下,您沒有額外的數據庫查詢來獲取用戶信息。
更新到原始答案:(這違反了操作系統的第一個要求,如果您有相同的要求,請參閱我的原始答案)您可以通過在Razor視圖中引用FullName來修改聲明並添加擴展文件(在我的原始解決方案中)如:
@UserManager.GetUserAsync(User).Result.FullName
原答案:
這只是這個stackoverflow問題的一個較短的例子,並且在本教程之后 。
假設您已經在“ApplicationUser.cs”中設置了屬性以及適用的ViewModel和Views進行注冊。
使用“FullName”作為額外屬性的示例:
將“AccountController.cs”注冊方法修改為:
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser {
UserName = model.Email,
Email = model.Email,
FullName = model.FullName //<-ADDED PROPERTY HERE!!!
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//ADD CLAIM HERE!!!!
await _userManager.AddClaimAsync(user, new Claim("FullName", user.FullName));
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
return View(model);
}
然后我添加了一個新文件“Extensions / ClaimsPrincipalExtension.cs”
using System.Linq;
using System.Security.Claims;
namespace MyProject.Extensions
{
public static class ClaimsPrincipalExtension
{
public static string GetFullName(this ClaimsPrincipal principal)
{
var fullName = principal.Claims.FirstOrDefault(c => c.Type == "FullName");
return fullName?.Value;
}
}
}
然后在您查看您需要訪問屬性的位置添加:
@using MyProject.Extensions
並在需要時通過以下方式調用:
@User.GetFullName()
這樣做的一個問題是我必須刪除我當前的測試用戶,然后重新注冊,以便查看“FullName”,即使數據庫中有FullName屬性。
好的,這是我最終做到的。 我在MVC6中使用了一個名為View Components的新功能。 這些工作有點像部分視圖,但它們有一個與它們相關的“ 迷你控制器 ”。 View Component是一個輕量級控制器,不參與模型綁定,但它可以在構造函數參數中傳遞一些東西,可能使用依賴注入,然后它可以構造一個View Model並將其傳遞給局部視圖。 因此,例如,您可以將UserManager
實例注入View組件,使用它來檢索當前用戶的ApplicationUser
對象並將其傳遞給局部視圖。
這是代碼中的樣子。 首先,View Component,它位於/ViewComponents
目錄中:
public class UserProfileViewComponent : ViewComponent
{
readonly UserManager<ApplicationUser> userManager;
public UserProfileViewComponent(UserManager<ApplicationUser> userManager)
{
Contract.Requires(userManager != null);
this.userManager = userManager;
}
public IViewComponentResult Invoke([CanBeNull] ClaimsPrincipal user)
{
return InvokeAsync(user).WaitForResult();
}
public async Task<IViewComponentResult> InvokeAsync([CanBeNull] ClaimsPrincipal user)
{
if (user == null || !user.IsSignedIn())
return View(anonymousUser);
var userId = user.GetUserId();
if (string.IsNullOrWhiteSpace(userId))
return View(anonymousUser);
try
{
var appUser = await userManager.FindByIdAsync(userId);
return View(appUser ?? anonymousUser);
}
catch (Exception) {
return View(anonymousUser);
}
}
static readonly ApplicationUser anonymousUser = new ApplicationUser
{
Email = string.Empty,
Id = "anonymous",
PhoneNumber = "n/a"
};
}
注意, userManager
構造函數參數由MVC框架注入; 默認情況下,這在新項目的Startup.cs
中配置,因此無法完成配置。
不出所料,通過調用Invoke
方法或它的異步版本來調用視圖組件。 如果可能,該方法將檢索ApplicationUser
,否則它將使用具有一些安全defaultspreconfigured的匿名用戶。 它將此用戶用於其視圖模型的partiel視圖。 該視圖位於/Views/Shared/Components/UserProfile/Default.cshtml
並以如下所示開頭:
@model ApplicationUser
<div class="dropdown profile-element">
<span>
@Html.GravatarImage(Model.Email, size:80)
</span>
<a data-toggle="dropdown" class="dropdown-toggle" href="#">
<span class="clear">
<span class="block m-t-xs">
<strong class="font-bold">@Model.UserName</strong>
</span> <span class="text-muted text-xs block">@Model.PhoneNumber <b class="caret"></b></span>
</span>
</a>
</div>
最后,我從我的_Navigation.cshtml
局部視圖中調用它,如下所示:
@await Component.InvokeAsync("UserProfile", User)
這符合我的所有原始要求,因為:
結果! 我希望有人能發現這個有用......
我有同樣的問題和相同的問題,但是我選擇了一個不同的解決方案,而不是創建一個ClaimPrincipal的擴展方法,讓擴展方法檢索自定義用戶屬性。
這是我的擴展方法:
public static class PrincipalExtensions
{
public static string ProfilePictureUrl(this ClaimsPrincipal user, UserManager<ApplicationUser> userManager)
{
if (user.Identity.IsAuthenticated)
{
var appUser = userManager.FindByIdAsync(user.GetUserId()).Result;
return appUser.ProfilePictureUrl;
}
return "";
}
}
接下來在我的視圖(也是LoginPartial視圖)中,我注入UserManager,然后將UserManager傳輸到擴展方法:
@inject Microsoft.AspNet.Identity.UserManager<ApplicationUser> userManager;
<img src="@User.ProfilePictureUrl(userManager)">
我相信這個解決方案也符合您對關注點分離的3個要求,DRY並且不會對任何ViewModel進行任何更改。 然而,雖然這個解決方案很簡單,並且可以在標准視圖中使用,而不僅僅是ViewComponents,我仍然不滿意。 現在,在我看來,我可以這樣寫:@ User.ProfilePictureUrl(的UserManager),但我認為這不會是過分的要求,我應該可以只寫:@ User.ProfilePictureUrl()。
如果只有我可以在我的擴展方法中使用UserManager(或IServiceProvider)而沒有函數注入它,它將解決問題,但我知道無法做到這一點。
正如我被問到的那樣,我發布了最終的解決方案,盡管是在一個不同的(MVC5 / EF6)項目中。
首先,我定義了一個接口:
public interface ICurrentUser
{
/// <summary>
/// Gets the display name of the user.
/// </summary>
/// <value>The display name.</value>
string DisplayName { get; }
/// <summary>
/// Gets the login name of the user. This is typically what the user would enter in the login screen, but may be
/// something different.
/// </summary>
/// <value>The name of the login.</value>
string LoginName { get; }
/// <summary>
/// Gets the unique identifier of the user. Typically this is used as the Row ID in whatever store is used to persist
/// the user's details.
/// </summary>
/// <value>The unique identifier.</value>
string UniqueId { get; }
/// <summary>
/// Gets a value indicating whether the user has been authenticated.
/// </summary>
/// <value><c>true</c> if this instance is authenticated; otherwise, <c>false</c>.</value>
bool IsAuthenticated { get; }
然后,我在一個具體的類中實現它:
/// <summary>
/// Encapsulates the concept of a 'current user' based on ASP.Net Identity.
/// </summary>
/// <seealso cref="MS.Gamification.DataAccess.ICurrentUser" />
public class AspNetIdentityCurrentUser : ICurrentUser
{
private readonly IIdentity identity;
private readonly UserManager<ApplicationUser, string> manager;
private ApplicationUser user;
/// <summary>
/// Initializes a new instance of the <see cref="AspNetIdentityCurrentUser" /> class.
/// </summary>
/// <param name="manager">The ASP.Net Identity User Manager.</param>
/// <param name="identity">The identity as reported by the HTTP Context.</param>
public AspNetIdentityCurrentUser(ApplicationUserManager manager, IIdentity identity)
{
this.manager = manager;
this.identity = identity;
}
/// <summary>
/// Gets the display name of the user. This implementation returns the login name.
/// </summary>
/// <value>The display name.</value>
public string DisplayName => identity.Name;
/// <summary>
/// Gets the login name of the user.
/// something different.
/// </summary>
/// <value>The name of the login.</value>
public string LoginName => identity.Name;
/// <summary>
/// Gets the unique identifier of the user, which can be used to look the user up in a database.
/// the user's details.
/// </summary>
/// <value>The unique identifier.</value>
public string UniqueId
{
get
{
if (user == null)
user = GetApplicationUser();
return user.Id;
}
}
/// <summary>
/// Gets a value indicating whether the user has been authenticated.
/// </summary>
/// <value><c>true</c> if the user is authenticated; otherwise, <c>false</c>.</value>
public bool IsAuthenticated => identity.IsAuthenticated;
private ApplicationUser GetApplicationUser()
{
return manager.FindByName(LoginName);
}
}
最后,我在我的DI內核中進行了以下配置(我正在使用Ninject):
kernel.Bind<ApplicationUserManager>().ToSelf()
.InRequestScope();
kernel.Bind<ApplicationSignInManager>().ToSelf().InRequestScope();
kernel.Bind<IAuthenticationManager>()
.ToMethod(m => HttpContext.Current.GetOwinContext().Authentication)
.InRequestScope();
kernel.Bind<IIdentity>().ToMethod(p => HttpContext.Current.User.Identity).InRequestScope();
kernel.Bind<ICurrentUser>().To<AspNetIdentityCurrentUser>();
然后每當我想訪問當前用戶時,我只需通過添加ICurrentUser
類型的構造函數參數將其注入我的控制器。
我喜歡這個解決方案,因為它很好地封裝了關注點並避免了我的控制器直接依賴於EF。
您需要使用當前用戶的名稱進行搜索(使用例如Entity Framework):
HttpContext.Current.User.Identity.Name
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.