前回のあらすじ
IUserStoreが必要やで。
IUserStoreを実装する
IUserStoreインタフェースを実装することで、UserManager
また、併せてIUserXxxStoreを実装することで、UserManagerがサポートする機能を追加することができます。
今回はパスワード機能を追加したいので、IUserPasswordStoreを併せて実装します。
IUserPasswordStoreのほかにも、IUserTwoFactorStoreや、IUserLockOutStore、IUserSecurityStampStoreなど色々とあります。
public class InMemoryUserStore : IUserStore<ApplicationUser>, IUserPasswordStore<ApplicationUser> { private static ConcurrentDictionary<Guid, ApplicationUser> Users { get; } = new ConcurrentDictionary<Guid, ApplicationUser>(); private static IdentityErrorDescriber IdentityErrorDescriber = new IdentityErrorDescriber(); public Task<IdentityResult> CreateAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); var result = Users.TryAdd(user.Id, user); if(!result) { return Task.FromResult(IdentityResult.Failed(IdentityErrorDescriber.ConcurrencyFailure())); } return Task.FromResult(IdentityResult.Success); } public Task<IdentityResult> DeleteAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); ApplicationUser removedUser; var result = Users.TryRemove(user.Id, out removedUser); if (!result) { return Task.FromResult(IdentityResult.Failed(IdentityErrorDescriber.ConcurrencyFailure())); } return Task.FromResult(IdentityResult.Success); } public Task<ApplicationUser> FindByIdAsync(string userId, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (string.IsNullOrEmpty(userId)) throw new ArgumentNullException(nameof(userId)); Guid id = Guid.Parse(userId); ApplicationUser user; return Task.FromResult(Users.TryGetValue(id, out user) ? user : null); } public Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (string.IsNullOrEmpty(normalizedUserName)) throw new ArgumentNullException(nameof(normalizedUserName)); return Task.FromResult(Users.Values.FirstOrDefault(u => u.NormalizedLoginName.Equals(normalizedUserName))); } public Task<string> GetNormalizedUserNameAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult(user.NormalizedLoginName); } public Task<string> GetUserIdAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult(user.UserId); } public Task<string> GetUserNameAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult(user.LoginName); } public Task SetUserNameAsync(ApplicationUser user, string userName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); user.LoginName = userName; return Task.CompletedTask; } public Task SetNormalizedUserNameAsync(ApplicationUser user, string normalizedName, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); user.NormalizedLoginName = normalizedName; return Task.CompletedTask; } public async Task<IdentityResult> UpdateAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); var existingUser = await FindByIdAsync(user.UserId, cancellationToken); var result = Users.TryUpdate(user.Id, user, existingUser); if (!result) { return IdentityResult.Failed(IdentityErrorDescriber.ConcurrencyFailure()); } return IdentityResult.Success; } public Task<bool> HasPasswordAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult(user.PasswordHash != null); } public Task<string> GetPasswordHashAsync(ApplicationUser user, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult(user.PasswordHash); } public Task SetPasswordHashAsync(ApplicationUser user, string passwordHash, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); user.PasswordHash = passwordHash; return Task.CompletedTask; } public void Dispose() { _disposed = true; } protected void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } private bool _disposed; }
今回は外部RDBを用いずにインメモリオブジェクトにてユーザー管理をするので、private staticオブジェクトで管理します。
GetUserIdAsyncがstringを返さないといけないので、前回のUserIdプロパティを用いました。無くても良かったなと思いました。
ちなみに、同様にIRoleStore
IRoleStore
public class InMemoryRoleStore : IRoleStore<ApplicationRole> { public void Dispose() { } public Task<IdentityResult> CreateAsync(ApplicationRole role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<IdentityResult> DeleteAsync(ApplicationRole role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<ApplicationRole> FindByIdAsync(string roleId, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<ApplicationRole> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetNormalizedRoleNameAsync(ApplicationRole role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetRoleIdAsync(ApplicationRole role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<string> GetRoleNameAsync(ApplicationRole role, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task SetNormalizedRoleNameAsync(ApplicationRole role, string normalizedName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task SetRoleNameAsync(ApplicationRole role, string roleName, CancellationToken cancellationToken) { throw new NotImplementedException(); } public Task<IdentityResult> UpdateAsync(ApplicationRole role, CancellationToken cancellationToken) { throw new NotImplementedException(); } }
これらをStarup.csのConfigureServicesに登録して準備完了です。
//Starup.cs ConfigureServices
services.AddTransient<IUserStore<ApplicationUser>, InMemoryUserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, InMemoryRoleStore>();
services
.AddIdentity<ApplicationUser, ApplicationRole>()
.AddDefaultTokenProviders();
使ってみよう
では使ってみましょう。
HomeControllerに下記のように追加しました。
ViewModelやViewを作成するのが面倒だったのですべてGetメソッドで実装しました。
public async Task<IActionResult> Register([FromServices]UserManager<ApplicationUser> userManager) { var user = new ApplicationUser { LoginName = "foo", Email = "foo@bar.com", ScreenName="Foo Bar" }; var password = "Pa$$w0rd"; var result = await userManager.CreateAsync(user, password); return View("Index"); } public async Task<IActionResult> LogIn([FromServices]SignInManager<ApplicationUser> signInManager) { var loginName = "foo"; var password = "Pa$$w0rd"; var result = await signInManager.PasswordSignInAsync(loginName, password, false, false); return View("Index"); } public async Task<IActionResult> LogOut([FromServices]SignInManager<ApplicationUser> signInManager) { await signInManager.SignOutAsync(); return View("Index"); }
Registerメソッドにブレイクポイントを貼ってから、/Home/Registerにアクセスすると、初回は下記のようにユーザーの作成に成功します。
ですが、2回目にアクセスすると、下記のようにDuplicatedUserNameと失敗します。
/Home/Registerにアクセスした後に、/Home/LogInにアクセスすると、ログインに成功します。
ここでパスワードを変えてからアクセスすると、下記のようにログインに失敗します。
また、/Home/Registerでユーザー登録する前にログインしても当然失敗します。
今日はここまで
次回はログイン後のユーザー情報取得などについて書きます。