ASP.Net vNext/MVC6プロジェクトに取り組んでいます。私はASP.Net Identityを理解しています。
ApplicationUser
クラスは、追加のユーザープロパティを追加することになっているようです。これはEntity Frameworkで機能し、追加のプロパティは期待どおりにデータベースに格納されます。
ただし、現在ログインしているユーザーの詳細にビューからアクセスしたい場合に問題が発生します。具体的には、ユーザーのGravatarアイコンを取得して表示する__loginPartial.cshtml
_があります。このアイコンにはメールアドレスが必要です。
Razor View
基本クラスには、ClaimsPrincipal
であるUserプロパティがあります。このUser
プロパティからApplicationUser
に戻り、カスタムプロパティを取得するにはどうすればよいですか?
情報を見つける方法を尋ねているのではないことに注意してください。 User.GetUserId()
値からApplicationUser
をルックアップする方法を知っています。これは、この問題に賢明に対処する方法についての質問です。具体的には、私はしたくない:
これは一元化された標準ソリューションを持つべき「横断的関心事」のように思えますが、ジグソーパズルの一部を見逃しているように感じます。ビュー内からカスタムユーザープロパティを取得する最良の方法は何ですか?
注:MVCチームは、UserNameプロパティが常にユーザーの電子メールアドレスに設定されるようにすることで、プロジェクトテンプレート内でこの問題を回避し、ユーザーがこのルックアップを実行してユーザーの電子メールアドレスを取得する必要性をきちんと回避しているようです!それは私には少しカンニングのようで、私のソリューションでは、ユーザーのログイン名は電子メールアドレスである場合とそうでない場合があるため、そのトリックに頼ることはできません(そして、後でアクセスする必要がある他のプロパティがあると思います)。
元の回答への更新:(これはopの最初の要件に違反しています。同じ要件がある場合は元の回答を参照してください)クレームを変更せずに実行できますRazorビューでFullNameを次のように参照して、拡張ファイルを追加します(元のソリューションで)。
@UserManager.GetUserAsync(User).Result.FullName
元の答え:
これは this stackoverflow question とこれに続く tutorial のほんの短い例です。
「ApplicationUser.cs」で設定されたプロパティと、登録用の該当するViewModelおよびビューがすでにあると仮定します。
追加のプロパティとして「FullName」を使用した例:
「AccountController.cs」Registerメソッドを次のように変更します。
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()
これに関する1つの問題は、データベースにFullNameプロパティが含まれていても、現在のテストユーザーを削除し、「FullName」を表示するために再登録する必要があることです。
そのためには、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を追加する必要があります(グローバル_ViewImport.cshtmlに追加します)
@using MyProject.MyExtensions
そして最後に、任意のビューでこのプロパティをメソッド呼び出しとして使用できます
@User.MyProperty()
この場合、ユーザー情報を取得するためのデータベースへの追加クエリはありません。
OK、最終的には次のようになりました。 View Componentsと呼ばれるMVC6の新機能を使用しました。これらは部分ビューのように機能しますが、「ミニコントローラー」が関連付けられています。 Viewコンポーネントは、モデルバインディングに参加しない軽量のコントローラーですが、おそらく依存関係の挿入を使用して、コンストラクターパラメーターで何かを渡すことができ、View Modelを構築してそれを部分ビューに渡すことができます。そのため、たとえば、ViewコンポーネントにUserManager
インスタンスを挿入し、それを使用して現在のユーザーのApplicationUser
オブジェクトを取得し、それを部分ビューに渡すことができます。
コードでは次のようになります。まず、/ViewComponents
ディレクトリにあるViewコンポーネント:
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
を取得します。それ以外の場合は、事前設定された安全なデフォルト値を持つ匿名ユーザーを使用します。このユーザーを使用して、ビューモデルのパーティションビューにアクセスします。ビューは/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)
次の理由により、これは私の当初の要件をすべて満たしています。
結果!誰かがこれが役立つと思います...
私は同じ問題と同じ懸念を持っていますが、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)を使用可能にできれば、問題は解決しますが、それを行う方法はわかりません。
それについて尋ねられたので、別の(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