我正在开发一个ASP.Net vNext / MVC6项目。我正在掌握ASP.Net Identity。
ApplicationUser
类显然是我应该添加任何其他用户属性的地方,这适用于Entity Framework,我的其他属性按预期存储在数据库中。
但是,当我想从我的视图中访问当前登录用户的详细信息时,问题就出现了。具体来说,我有_loginPartial.cshtml
我要检索并显示用户的Gravatar图标,我需要该电子邮件地址。
Razor View
基类具有User属性,即ClaimsPrincipal
。如何从此User
属性返回到我的ApplicationUser
,以检索我的自定义属性?
请注意,我不会询问如何查找信息;我知道如何从ApplicationUser
值中查找User.GetUserId()
。这是一个关于如何合理地解决这个问题的问题。具体来说,我不想:
这似乎是一个跨领域的问题'应该有一个集中的标准解决方案,但我觉得我错过了一块拼图游戏。从视图中获取这些自定义用户属性的最佳方法是什么?
注意:似乎MVC团队通过确保UserName属性始终设置为用户的电子邮件地址,在项目模板中侧面解决了这个问题,巧妙地避免了他们执行此查找的必要性获取用户的电子邮件地址!这对我来说似乎有点欺骗,在我的解决方案中,用户的登录名可能是也可能不是他们的电子邮件地址,所以我不能依赖这个技巧(我怀疑会有其他的)我需要稍后访问的属性。
答案 0 :(得分:14)
我认为您应该为此目的使用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()
在这种情况下,您没有额外的数据库查询来获取用户信息。
答案 1 :(得分:14)
更新为原始回答:(这违反了操作的第一个要求,如果您有相同的要求,请参阅我的原始答案)您可以在不修改声明和添加扩展文件的情况下执行此操作(在我的原始解决方案)通过在Razor视图中引用FullName为:
@UserManager.GetUserAsync(User).Result.FullName
原始答案:
这只是this stackoverflow question的一个较短示例,并且遵循此tutorial。
假设您已经在“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;
}
}
}
然后在您的视图中,您需要访问属性add:
@using MyProject.Extensions
并在需要时通过以下方式调用:
@User.GetFullName()
这个问题的一个问题是我必须删除当前的测试用户,然后重新注册,以便查看“FullName”,即使数据库中有FullName属性。
答案 2 :(得分:2)
好的,这是我最终如何做到的。我在MVC6中使用了一个名为 View Components 的新功能。这些工作有点像部分视图,但他们有一个&#34; 迷你控制器&#34;与他们相关联。 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)
这符合我原来的所有要求,因为:
结果!我希望有人会觉得这很有用......
答案 3 :(得分:2)
我有同样的问题和相同的问题,但是我选择了另一种解决方案,而不是创建一个ClaimPrincipal的扩展方法,并让扩展方法检索自定义用户属性。
这是我的扩展方法:
dll.a
接下来在我的视图(也是LoginPartial视图)中,我注入UserManager,然后将UserManager传输到扩展方法:
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 "";
}
}
我相信这个解决方案也符合您对关注点分离的3个要求,DRY并且不会对任何ViewModel进行任何更改。然而,虽然这个解决方案很简单,并且可以在标准视图中使用,而不仅仅是ViewComponents,我仍然不满意。现在在我看来我可以写: @ User.ProfilePictureUrl(userManager),但我认为我应该只能写下来并不会太多: 的 @ User.ProfilePictureUrl()强>
如果只有我可以在我的扩展方法中使用UserManager(或IServiceProvider)而没有函数注入它,它将解决问题,但我知道没办法这样做。
答案 4 :(得分:1)
正如我被问到的那样,我发布了我的最终解决方案,尽管是在另一个(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。
答案 5 :(得分:0)
您需要使用当前用户的名称进行搜索(例如使用实体框架):
HttpContext.Current.User.Identity.Name