我应该如何从MVC 6视图中访问我的ApplicationUser属性?

我正在开发一个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属性。 我找到了很好的post: http : //benfoster.io/blog/customising-claims-transformation-in-aspnet-core-identity

用户类

 public class ApplicationUser : IdentityUser { public string MyProperty { get; set; } } 

我们将MyProperty放入经过身份validation的用户声明中。 为此,我们重写了UserClaimsPrincipalFactory

 public class MyUserClaimsPrincipalFactory : UserClaimsPrincipalFactory { public MyUserClaimsPrincipalFactory ( UserManager userManager, RoleManager roleManager, IOptions optionsAccessor) : base(userManager, roleManager, optionsAccessor) { } public async override Task 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, 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 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的新function。 这些工作有点像部分视图,但它们有一个与它们相关的“ 迷你控制器 ”。 View Component是一个轻量级控制器,不参与模型绑定,但它可以在构造函数参数中传递一些东西,可能使用dependency injection,然后它可以构造一个View Model并将其传递给局部视图。 因此,例如,您可以将UserManager实例注入View组件,使用它来检索当前用户的ApplicationUser对象并将其传递给局部视图。

这是代码中的样子。 一,View Component,它位于/ViewComponents目录中:

 public class UserProfileViewComponent : ViewComponent { readonly UserManager userManager; public UserProfileViewComponent(UserManager userManager) { Contract.Requires(userManager != null); this.userManager = userManager; } public IViewComponentResult Invoke([CanBeNull] ClaimsPrincipal user) { return InvokeAsync(user).WaitForResult(); } public async Task 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  

最后,我在_Navigation.cshtml局部视图中调用它,如下所示:

 @await Component.InvokeAsync("UserProfile", User) 

这符合我的所有原始要求,因为:

  1. 我正在控制器中执行数据库查找(View Component是一种控制器),而不是在View中。 此外,数据可能已经存在于内存中,因为框架已经对请求进行了身份validation。 我还没有看过另一个数据库往返是否真的发生了,我可能不会打扰,但如果有人知道,请加入!
  2. 逻辑在一个明确定义的地方; DRY原则得到尊重。
  3. 我不必修改任何其他视图模型。

结果! 我希望有人能发现这个有用……

我有同样的问题和相同的问题,但是我选择了一个不同的解决方案,而不是创建一个ClaimsPrincipal的扩展方法,并让扩展方法检索自定义用户属性。

这是我的扩展方法:

 public static class PrincipalExtensions { public static string ProfilePictureUrl(this ClaimsPrincipal user, UserManager userManager) { if (user.Identity.IsAuthenticated) { var appUser = userManager.FindByIdAsync(user.GetUserId()).Result; return appUser.ProfilePictureUrl; } return ""; } } 

接下来在我的视图(也是LoginPartial视图)中,我注入UserManager,然后将UserManager传输到扩展方法:

 @inject Microsoft.AspNet.Identity.UserManager userManager;  

我相信这个解决方案也符合您对关注点分离的3个要求,DRY并且不会对任何ViewModel进行任何更改。 然而,虽然这个解决方案很简单,并且不仅可以在标准视图中使用ViewComponents,但我仍然不满意。 现在在我看来我可以写: @ User.ProfilePictureUrl(userManager) ,但我认为要求我只能写: @ User.ProfilePictureUrl()并不会太多。

如果只有我可以在我的扩展方法中使用UserManager(或IServiceProvider)而没有函数注入它,它将解决问题,但我知道没办法这样做。

正如我被问到的那样,我发布了最终的解决方案,尽管是在一个不同的(MVC5 / EF6)项目中。

首先,我定义了一个接口:

 public interface ICurrentUser { ///  /// Gets the display name of the user. ///  /// The display name. string DisplayName { get; } ///  /// Gets the login name of the user. This is typically what the user would enter in the login screen, but may be /// something different. ///  /// The name of the login. string LoginName { get; } ///  /// 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. ///  /// The unique identifier. string UniqueId { get; } ///  /// Gets a value indicating whether the user has been authenticated. ///  /// true if this instance is authenticated; otherwise, false. bool IsAuthenticated { get; } 

然后,我在一个具体的类中实现它:

 ///  /// Encapsulates the concept of a 'current user' based on ASP.Net Identity. ///  ///  public class AspNetIdentityCurrentUser : ICurrentUser { private readonly IIdentity identity; private readonly UserManager manager; private ApplicationUser user; ///  /// Initializes a new instance of the  class. ///  /// The ASP.Net Identity User Manager. /// The identity as reported by the HTTP Context. public AspNetIdentityCurrentUser(ApplicationUserManager manager, IIdentity identity) { this.manager = manager; this.identity = identity; } ///  /// Gets the display name of the user. This implementation returns the login name. ///  /// The display name. public string DisplayName => identity.Name; ///  /// Gets the login name of the user. /// something different. ///  /// The name of the login. public string LoginName => identity.Name; ///  /// Gets the unique identifier of the user, which can be used to look the user up in a database. /// the user's details. ///  /// The unique identifier. public string UniqueId { get { if (user == null) user = GetApplicationUser(); return user.Id; } } ///  /// Gets a value indicating whether the user has been authenticated. ///  /// true if the user is authenticated; otherwise, false. public bool IsAuthenticated => identity.IsAuthenticated; private ApplicationUser GetApplicationUser() { return manager.FindByName(LoginName); } } 

最后,我在我的DI内核中进行了以下配置(我正在使用Ninject):

  kernel.Bind().ToSelf() .InRequestScope(); kernel.Bind().ToSelf().InRequestScope(); kernel.Bind() .ToMethod(m => HttpContext.Current.GetOwinContext().Authentication) .InRequestScope(); kernel.Bind().ToMethod(p => HttpContext.Current.User.Identity).InRequestScope(); kernel.Bind().To(); 

然后,每当我想访问当前用户时,我只需通过添加ICurrentUser类型的构造函数参数将其注入我的控制器。

我喜欢这个解决方案,因为它很好地封装了关注点并避免了我的控制器直接依赖于EF。

您需要使用当前用户的名称进行搜索(使用例如Entity Framework):

 HttpContext.Current.User.Identity.Name