Подтвердить что ты не робот

Как мне получить доступ к своим свойствам ApplicationUser из ASP.NET Core Views?

Я работаю над проектом ASP.Net vNext/MVC6. Я получаю доступ к идентификатору ASP.Net.

Класс ApplicationUser, по-видимому, там, где я должен добавить любые дополнительные пользовательские свойства, и это работает с Entity Framework, и мои дополнительные свойства будут храниться в базе данных, как ожидалось.

Однако проблема возникает, когда я хочу получить доступ к деталям текущего пользователя в моих представлениях. В частности, у меня есть _loginPartial.cshtml, в котором я хочу получить и отобразить значок пользователя Gravatar, для которого мне нужен адрес электронной почты.

Базовый класс Razor View имеет свойство User, которое является ClaimsPrincipal. Как мне перейти от этого свойства User к моему ApplicationUser, чтобы получить мои пользовательские свойства?

Обратите внимание, что я не спрашиваю, как найти информацию; Я знаю, как искать ApplicationUser из значения User.GetUserId(). Это больше вопрос о том, как разумно подойти к этой проблеме. В частности, я не хочу:

  • Выполните любой поиск базы данных из моих представлений (разделение проблем)
  • Необходимо добавить логику для каждого контроллера, чтобы получить текущие данные пользователя (принцип DRY)
  • Необходимо добавить свойство User для каждого ViewModel.

Это похоже на "сквозную проблему", которая должна иметь централизованное стандартное решение, но я чувствую, что мне не хватает части головоломки. Каков наилучший способ получить эти пользовательские свойства из представлений?

Примечание. Кажется, что команда MVC пошарила эту проблему в шаблонах проекта, гарантируя, что свойство UserName всегда настроено на адрес электронной почты пользователя, аккуратно избегая необходимости выполнять этот поиск, чтобы получить пользователя адрес электронной почты! Мне кажется, что это немного обманщик, и в моем решении имя пользователя входа может быть или не быть их адресом электронной почты, поэтому я не могу полагаться на этот трюк (и я подозреваю, что будут другие свойства, которые мне нужны для доступа позже).

4b9b3361

Ответ 1

Я думаю, вы должны использовать свойство Claims User для этой цели. Я нашел хороший пост: 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;
    }
}

Регистрация нашего UserClaimsPrincipalFactory в Startup.cs

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 для просмотра (я добавляю его в глобальный _ViewImport.cshtml)

@using MyProject.MyExtensions

И, наконец, мы можем использовать это свойство в любом представлении как метод, вызывающий

@User.MyProperty()

В этом случае у вас нет дополнительных запросов к базе данных для получения информации о пользователе.

Ответ 2

Обновить исходный ответ: (Это нарушает первое требование op, см. мой первоначальный ответ, если у вас есть такое же требование). Вы можете сделать это без изменения претензий и добавления файла расширения (в моем оригинальное решение) путем ссылки на FullName в представлении Razor как:

@UserManager.GetUserAsync(User).Result.FullName

Исходный ответ:

Это в значительной степени просто более короткий пример этого вопроса о стеке_поверхности и после этого учебник.

Предполагая, что у вас уже есть свойство, установленное в "ApplicationUser.cs", а также применимые ViewModels и 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;
            }   
        }
    }

а затем в ваших представлениях, где вам нужно получить доступ к свойству add:

@using MyProject.Extensions

и назовите его, когда необходимо:

@User.GetFullName()

Единственная проблема заключается в том, что мне пришлось удалить моего текущего пользователя теста, а затем перерегистрировать его, чтобы увидеть "FullName", даже если в базе данных было свойство FullName.

Ответ 3

ОК, вот как я в конце концов это сделал. Я использовал новую функцию в MVC6 под названием Просмотреть компоненты. Они немного похожи на частичные виды, но у них есть "мини-контроллер", связанный с ними. Компонент View - это легкий контроллер, который не участвует в привязке модели, но может иметь что-то переданное ему в параметрах конструктора, возможно, используя инъекцию зависимостей, а затем он может построить View Model и передать это частичному представлению. Например, вы можете вставить экземпляр UserManager в компонент View, использовать его для извлечения объекта ApplicationUser для текущего пользователя и передать его в частичный вид.

Вот как это выглядит в коде. Во-первых, компонент View, который находится в каталоге /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 в новом проекте, поэтому настройка не требуется.

Компонент view вызывается, что неудивительно, вызывая метод Invoke или асинхронную версию. Метод извлекает ApplicationUser, если это возможно, в противном случае он использует анонимного пользователя с некоторыми безопасными параметрами defaultspreconfigured. Он использует этого пользователя для просмотра в своем представлении модели представления. Взгляд живет в /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)

Это соответствует всем моим первоначальным требованиям, потому что:

  • Я выполняю поиск базы данных в контроллере (компонент View - это тип контроллера), а не в представлении. Кроме того, данные могут уже быть в памяти, поскольку структура уже проверила запрос. Я не смотрел, действительно ли происходит другая поездка в базу данных, и я, вероятно, не буду беспокоиться, но если кто-нибудь знает, пожалуйста, звоните!
  • Логика находится в одном четко определенном месте; соблюдается принцип DRY.
  • Мне не нужно изменять любую другую модель просмотра.

Результат! Я надеюсь, что кто-то найдет это полезным...

Ответ 4

У меня одна и та же проблема и те же проблемы, однако я выбрал другое решение вместо создания метода расширения для ClaimsPrincipal и позвольте методу расширения получить пользовательское свойство пользователя.

Вот мой метод расширения:

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) доступным в моем методе расширения без функции, введя его, это решит проблему, но я не знаю, как это сделать.

Ответ 5

Как меня спрашивали об этом, я отправляю свое возможное решение, хотя и в другом проекте (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 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 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.

Ответ 6

Вам необходимо выполнить поиск (используя, например, Entity Framework) имя текущего пользователя:

HttpContext.Current.User.Identity.Name