簡體   English   中英

我應該如何從ASP.NET核心視圖訪問我的ApplicationUser屬性?

[英]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 這是一個關於如何合理地解決這個問題的問題。 具體來說,我不想:

  • 從我的視圖中執行任何類型的數據庫查找(關注點分離)
  • 必須為每個控制器添加邏輯以檢索當前用戶的詳細信息(DRY原則)
  • 必須為每個ViewModel添加User屬性。

這似乎是一個“橫切關注點”,應該有一個集中的標准解決方案,但我覺得我錯過了一塊拼圖游戲。 從視圖中獲取這些自定義用戶屬性的最佳方法是什么?

注意:似乎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)

這符合我的所有原始要求,因為:

  1. 我正在控制器中執行數據庫查找(View Component是一種控制器),而不是在View中。 此外,數據可能已經在內存中,因為框架已經對請求進行了身份驗證。 我沒有考慮過另一個數據庫往返是否真的發生了,我可能不會打擾,但如果有人知道,請加入!
  2. 邏輯在一個明確定義的地方; DRY原則得到尊重。
  3. 我不必修改任何其他視圖模型。

結果! 我希望有人能發現這個有用......

我有同樣的問題和相同的問題,但是我選擇了一個不同的解決方案,而不是創建一個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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM