Add NormalizedUserName / IUserNameNormalizer

+ Use normalized username for FindByUserName
This commit is contained in:
Hao Kung 2014-07-29 13:45:16 -07:00
parent 7942d2bc82
commit 626362d8a2
16 changed files with 235 additions and 36 deletions

View File

@ -114,6 +114,29 @@ namespace Microsoft.AspNet.Identity.EntityFramework
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task<string> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = new CancellationToken())
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult(user.NormalizedUserName);
}
public Task SetNormalizedUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = new CancellationToken())
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException("user");
}
user.NormalizedUserName = userName;
return Task.FromResult(0);
}
public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -183,16 +206,16 @@ namespace Microsoft.AspNet.Identity.EntityFramework
} }
/// <summary> /// <summary>
/// Find a user by name /// Find a user by normalized name
/// </summary> /// </summary>
/// <param name="userName"></param> /// <param name="userName"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public virtual Task<TUser> FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) public virtual Task<TUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken))
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed(); ThrowIfDisposed();
return GetUserAggregate(u => u.UserName.ToUpper() == userName.ToUpper(), cancellationToken); return GetUserAggregate(u => u.NormalizedUserName == normalizedUserName, cancellationToken);
} }
public IQueryable<TUser> Users public IQueryable<TUser> Users

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Identity
{
/// <summary>
/// Used to normalize a user name
/// </summary>
public interface IUserNameNormalizer
{
/// <summary>
/// Returns the normalized user name
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
string Normalize(string userName);
}
}

View File

@ -39,6 +39,24 @@ namespace Microsoft.AspNet.Identity
Task SetUserNameAsync(TUser user, string userName, Task SetUserNameAsync(TUser user, string userName,
CancellationToken cancellationToken = default(CancellationToken)); CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Returns the normalized user name
/// </summary>
/// <param name="user"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Set the normalized user name
/// </summary>
/// <param name="user"></param>
/// <param name="userName"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SetNormalizedUserNameAsync(TUser user, string userName,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary> /// <summary>
/// Insert a new user /// Insert a new user
/// </summary> /// </summary>
@ -72,11 +90,11 @@ namespace Microsoft.AspNet.Identity
Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)); Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken));
/// <summary> /// <summary>
/// Returns the user associated with this name /// Returns the user associated with this normalized user name
/// </summary> /// </summary>
/// <param name="name"></param> /// <param name="normalizedUserName"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
Task<TUser> FindByNameAsync(string name, CancellationToken cancellationToken = default(CancellationToken)); Task<TUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken));
} }
} }

View File

@ -26,6 +26,7 @@ namespace Microsoft.AspNet.Identity
yield return describe.Transient<IUserValidator<TUser>, UserValidator<TUser>>(); yield return describe.Transient<IUserValidator<TUser>, UserValidator<TUser>>();
yield return describe.Transient<IPasswordValidator<TUser>, PasswordValidator<TUser>>(); yield return describe.Transient<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
yield return describe.Transient<IPasswordHasher<TUser>, PasswordHasher<TUser>>(); yield return describe.Transient<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
yield return describe.Transient<IUserNameNormalizer, UpperInvariantUserNameNormalizer>();
// TODO: rationalize email/sms/usertoken services // TODO: rationalize email/sms/usertoken services
} }

View File

@ -36,6 +36,7 @@ namespace Microsoft.AspNet.Identity
public virtual TKey Id { get; set; } public virtual TKey Id { get; set; }
public virtual string UserName { get; set; } public virtual string UserName { get; set; }
public virtual string NormalizedUserName { get; set; }
/// <summary> /// <summary>
/// Email /// Email

View File

@ -25,6 +25,8 @@
<Compile Include="ClaimsIdentityFactory.cs" /> <Compile Include="ClaimsIdentityFactory.cs" />
<Compile Include="ClaimsIdentityOptions.cs" /> <Compile Include="ClaimsIdentityOptions.cs" />
<Compile Include="Crypto.cs" /> <Compile Include="Crypto.cs" />
<Compile Include="UpperInvariantUserNameNormalizer.cs" />
<Compile Include="IUserNameNormalizer.cs" />
<Compile Include="PhoneNumberTokenProvider.cs" /> <Compile Include="PhoneNumberTokenProvider.cs" />
<Compile Include="IAuthenticationManager.cs" /> <Compile Include="IAuthenticationManager.cs" />
<Compile Include="IClaimsIdentityFactory.cs" /> <Compile Include="IClaimsIdentityFactory.cs" />

View File

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Identity
{
/// <summary>
/// Normalizes user names via ToUpperInvariant()
/// </summary>
public class UpperInvariantUserNameNormalizer : IUserNameNormalizer
{
/// <summary>
/// Normalizes user names via ToUpperInvariant()
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public string Normalize(string userName)
{
if (userName == null)
{
return null;
}
return userName.Normalize().ToUpperInvariant();
}
}
}

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Identity
/// <param name="claimsIdentityFactory"></param> /// <param name="claimsIdentityFactory"></param>
public UserManager(IUserStore<TUser> store, IOptionsAccessor<IdentityOptions> optionsAccessor, public UserManager(IUserStore<TUser> store, IOptionsAccessor<IdentityOptions> optionsAccessor,
IPasswordHasher<TUser> passwordHasher, IUserValidator<TUser> userValidator, IPasswordHasher<TUser> passwordHasher, IUserValidator<TUser> userValidator,
IPasswordValidator<TUser> passwordValidator) IPasswordValidator<TUser> passwordValidator, IUserNameNormalizer userNameNormalizer)
{ {
if (store == null) if (store == null)
{ {
@ -57,6 +57,7 @@ namespace Microsoft.AspNet.Identity
PasswordHasher = passwordHasher; PasswordHasher = passwordHasher;
UserValidator = userValidator; UserValidator = userValidator;
PasswordValidator = passwordValidator; PasswordValidator = passwordValidator;
UserNameNormalizer = userNameNormalizer;
// TODO: Email/Sms/Token services // TODO: Email/Sms/Token services
} }
@ -96,6 +97,11 @@ namespace Microsoft.AspNet.Identity
/// </summary> /// </summary>
public IPasswordValidator<TUser> PasswordValidator { get; set; } public IPasswordValidator<TUser> PasswordValidator { get; set; }
/// <summary>
/// Used to normalize user names for uniqueness
/// </summary>
public IUserNameNormalizer UserNameNormalizer { get; set; }
/// <summary> /// <summary>
/// Used to send email /// Used to send email
/// </summary> /// </summary>
@ -309,6 +315,7 @@ namespace Microsoft.AspNet.Identity
{ {
await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken); await GetUserLockoutStore().SetLockoutEnabledAsync(user, true, cancellationToken);
} }
await UpdateNormalizedUserName(user, cancellationToken);
await Store.CreateAsync(user, cancellationToken); await Store.CreateAsync(user, cancellationToken);
return IdentityResult.Success; return IdentityResult.Success;
} }
@ -332,6 +339,7 @@ namespace Microsoft.AspNet.Identity
{ {
return result; return result;
} }
await UpdateNormalizedUserName(user, cancellationToken);
await Store.UpdateAsync(user, cancellationToken); await Store.UpdateAsync(user, cancellationToken);
return IdentityResult.Success; return IdentityResult.Success;
} }
@ -381,6 +389,7 @@ namespace Microsoft.AspNet.Identity
{ {
throw new ArgumentNullException("userName"); throw new ArgumentNullException("userName");
} }
userName = NormalizeUserName(userName);
return Store.FindByNameAsync(userName, cancellationToken); return Store.FindByNameAsync(userName, cancellationToken);
} }
@ -423,6 +432,29 @@ namespace Microsoft.AspNet.Identity
return await CreateAsync(user, cancellationToken); return await CreateAsync(user, cancellationToken);
} }
/// <summary>
/// Normalize a user name for uniqueness comparisons
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public virtual string NormalizeUserName(string userName)
{
return (UserNameNormalizer == null) ? userName : UserNameNormalizer.Normalize(userName);
}
/// <summary>
/// Update the user's normalized user name
/// </summary>
/// <param name="user"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual async Task UpdateNormalizedUserName(TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
string userName = await GetUserNameAsync(user, cancellationToken);
await Store.SetNormalizedUserNameAsync(user, NormalizeUserName(userName), cancellationToken);
}
/// <summary> /// <summary>
/// Get the user's name /// Get the user's name
/// </summary> /// </summary>
@ -456,6 +488,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("user"); throw new ArgumentNullException("user");
} }
await Store.SetUserNameAsync(user, userName, cancellationToken); await Store.SetUserNameAsync(user, userName, cancellationToken);
await UpdateNormalizedUserName(user, cancellationToken);
return await UpdateAsync(user, cancellationToken); return await UpdateAsync(user, cancellationToken);
} }
@ -483,6 +516,7 @@ namespace Microsoft.AspNet.Identity
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
ThrowIfDisposed(); ThrowIfDisposed();
userName = NormalizeUserName(userName);
var user = await FindByNameAsync(userName, cancellationToken); var user = await FindByNameAsync(userName, cancellationToken);
if (user == null) if (user == null)
{ {

View File

@ -15,6 +15,7 @@
"System.Diagnostics.Debug": "4.0.10.0", "System.Diagnostics.Debug": "4.0.10.0",
"System.Diagnostics.Tools": "4.0.0.0", "System.Diagnostics.Tools": "4.0.0.0",
"System.Globalization": "4.0.10.0", "System.Globalization": "4.0.10.0",
"System.Globalization.Extensions": "4.0.0.0",
"System.Linq": "4.0.0.0", "System.Linq": "4.0.0.0",
"System.Linq.Expressions": "4.0.0.0", "System.Linq.Expressions": "4.0.0.0",
"System.Linq.Queryable": "4.0.0.0", "System.Linq.Queryable": "4.0.0.0",

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test
public virtual TKey Id { get; set; } public virtual TKey Id { get; set; }
public virtual string UserName { get; set; } public virtual string UserName { get; set; }
public virtual string NormalizedUserName { get; set; }
/// <summary> /// <summary>
/// Email /// Email

View File

@ -117,6 +117,29 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task<string> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = new CancellationToken())
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException("user");
}
return Task.FromResult(user.NormalizedUserName);
}
public Task SetNormalizedUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = new CancellationToken())
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException("user");
}
user.NormalizedUserName = userName;
return Task.FromResult(0);
}
public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) public async virtual Task CreateAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();

View File

@ -313,6 +313,17 @@ namespace Microsoft.AspNet.Identity.InMemory
return Task.FromResult(user.TwoFactorEnabled); return Task.FromResult(user.TwoFactorEnabled);
} }
public Task<string> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.NormalizedUserName);
}
public Task SetNormalizedUserNameAsync(TUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
user.NormalizedUserName = userName;
return Task.FromResult(0);
}
private class LoginComparer : IEqualityComparer<UserLoginInfo> private class LoginComparer : IEqualityComparer<UserLoginInfo>
{ {
public bool Equals(UserLoginInfo x, UserLoginInfo y) public bool Equals(UserLoginInfo x, UserLoginInfo y)

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -51,5 +52,15 @@ namespace Microsoft.AspNet.Identity.Test
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task<string> GetNormalizedUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<string>(null);
}
public Task SetNormalizedUserNameAsync(TestUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(0);
}
} }
} }

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Identity.Test
public TestManager(IUserStore<TestUser> store, IOptionsAccessor<IdentityOptions> optionsAccessor, public TestManager(IUserStore<TestUser> store, IOptionsAccessor<IdentityOptions> optionsAccessor,
IPasswordHasher<TestUser> passwordHasher, IUserValidator<TestUser> userValidator, IPasswordHasher<TestUser> passwordHasher, IUserValidator<TestUser> userValidator,
IPasswordValidator<TestUser> passwordValidator) IPasswordValidator<TestUser> passwordValidator)
: base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator) { } : base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null) { }
} }
[Fact] [Fact]
@ -130,13 +130,31 @@ namespace Microsoft.AspNet.Identity.Test
} }
[Fact] [Fact]
public async Task FindByNameCallsStore() public async Task FindByNameCallsStoreWithNormalizedName()
{
// Setup
var store = new Mock<IUserStore<TestUser>>();
var user = new TestUser {UserName="Foo"};
store.Setup(s => s.FindByNameAsync(user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable();
var userManager = MockHelpers.TestUserManager<TestUser>(store.Object);
// Act
var result = await userManager.FindByNameAsync(user.UserName);
// Assert
Assert.Equal(user, result);
store.VerifyAll();
}
[Fact]
public async Task CanFindByNameCallsStoreWithoutNormalizedName()
{ {
// Setup // Setup
var store = new Mock<IUserStore<TestUser>>(); var store = new Mock<IUserStore<TestUser>>();
var user = new TestUser {UserName="Foo"}; var user = new TestUser {UserName="Foo"};
store.Setup(s => s.FindByNameAsync(user.UserName, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); store.Setup(s => s.FindByNameAsync(user.UserName, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable();
var userManager = MockHelpers.TestUserManager<TestUser>(store.Object); var userManager = MockHelpers.TestUserManager<TestUser>(store.Object);
userManager.UserNameNormalizer = null;
// Act // Act
var result = await userManager.FindByNameAsync(user.UserName); var result = await userManager.FindByNameAsync(user.UserName);
@ -333,14 +351,10 @@ namespace Microsoft.AspNet.Identity.Test
Assert.False(manager.SupportsUserSecurityStamp); Assert.False(manager.SupportsUserSecurityStamp);
await Assert.ThrowsAsync<NotSupportedException>(() => manager.UpdateSecurityStampAsync(null)); await Assert.ThrowsAsync<NotSupportedException>(() => manager.UpdateSecurityStampAsync(null));
await Assert.ThrowsAsync<NotSupportedException>(() => manager.GetSecurityStampAsync(null)); await Assert.ThrowsAsync<NotSupportedException>(() => manager.GetSecurityStampAsync(null));
#if NET45 await Assert.ThrowsAsync<NotSupportedException>(
await
Assert.ThrowsAsync<NotSupportedException>(
() => manager.VerifyChangePhoneNumberTokenAsync(null, "1", "111-111-1111")); () => manager.VerifyChangePhoneNumberTokenAsync(null, "1", "111-111-1111"));
await await Assert.ThrowsAsync<NotSupportedException>(
Assert.ThrowsAsync<NotSupportedException>(
() => manager.GenerateChangePhoneNumberTokenAsync(null, "111-111-1111")); () => manager.GenerateChangePhoneNumberTokenAsync(null, "111-111-1111"));
#endif
} }
[Fact] [Fact]
@ -428,13 +442,13 @@ namespace Microsoft.AspNet.Identity.Test
var passwordValidator = new PasswordValidator<TestUser>(); var passwordValidator = new PasswordValidator<TestUser>();
Assert.Throws<ArgumentNullException>("store", Assert.Throws<ArgumentNullException>("store",
() => new UserManager<TestUser>(null, null, null, null, null)); () => new UserManager<TestUser>(null, null, null, null, null, null));
Assert.Throws<ArgumentNullException>("optionsAccessor", Assert.Throws<ArgumentNullException>("optionsAccessor",
() => new UserManager<TestUser>(store, null, null, null, null)); () => new UserManager<TestUser>(store, null, null, null, null, null));
Assert.Throws<ArgumentNullException>("passwordHasher", Assert.Throws<ArgumentNullException>("passwordHasher",
() => new UserManager<TestUser>(store, optionsAccessor, null, null, null)); () => new UserManager<TestUser>(store, optionsAccessor, null, null, null, null));
var manager = new UserManager<TestUser>(store, optionsAccessor, passwordHasher, userValidator, passwordValidator); var manager = new UserManager<TestUser>(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null);
Assert.Throws<ArgumentNullException>("value", () => manager.PasswordHasher = null); Assert.Throws<ArgumentNullException>("value", () => manager.PasswordHasher = null);
Assert.Throws<ArgumentNullException>("value", () => manager.Options = null); Assert.Throws<ArgumentNullException>("value", () => manager.Options = null);
@ -836,6 +850,16 @@ namespace Microsoft.AspNet.Identity.Test
{ {
return Task.FromResult<string>(null); return Task.FromResult<string>(null);
} }
public Task<string> GetNormalizedUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult<string>(null);
}
public Task SetNormalizedUserNameAsync(TestUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(0);
}
} }
private class NoOpTokenProvider : IUserTokenProvider<TestUser> private class NoOpTokenProvider : IUserTokenProvider<TestUser>
@ -1076,6 +1100,16 @@ namespace Microsoft.AspNet.Identity.Test
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<string> GetNormalizedUserNameAsync(TestUser user, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
public Task SetNormalizedUserNameAsync(TestUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
} }
} }
} }

View File

@ -6,20 +6,6 @@ using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Identity.Test namespace Microsoft.AspNet.Identity.Test
{ {
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store, IOptionsAccessor<IdentityOptions> options,
IPasswordHasher<ApplicationUser> passwordHasher, IUserValidator<ApplicationUser> userValidator,
IPasswordValidator<ApplicationUser> passwordValidator)
: base(store, options, passwordHasher, userValidator, passwordValidator) { }
}
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
public ApplicationRoleManager(IRoleStore<IdentityRole> store, IRoleValidator<IdentityRole> roleValidator)
: base(store, roleValidator) { }
}
public class ApplicationUser : IdentityUser public class ApplicationUser : IdentityUser
{ {
} }

View File

@ -33,7 +33,13 @@ namespace Microsoft.AspNet.Identity.Test
{ {
var store = new Mock<IUserStore<TUser>>(); var store = new Mock<IUserStore<TUser>>();
var options = new OptionsAccessor<IdentityOptions>(null); var options = new OptionsAccessor<IdentityOptions>(null);
return new Mock<UserManager<TUser>>(store.Object, options, new PasswordHasher<TUser>(), new UserValidator<TUser>(), new PasswordValidator<TUser>()); return new Mock<UserManager<TUser>>(
store.Object,
options,
new PasswordHasher<TUser>(),
new UserValidator<TUser>(),
new PasswordValidator<TUser>(),
new UpperInvariantUserNameNormalizer());
} }
public static Mock<RoleManager<TRole>> MockRoleManager<TRole>() where TRole : class public static Mock<RoleManager<TRole>> MockRoleManager<TRole>() where TRole : class
@ -51,8 +57,10 @@ namespace Microsoft.AspNet.Identity.Test
{ {
var options = new OptionsAccessor<IdentityOptions>(null); var options = new OptionsAccessor<IdentityOptions>(null);
var validator = new Mock<UserValidator<TUser>>(); var validator = new Mock<UserValidator<TUser>>();
var userManager = new UserManager<TUser>(store, options, new PasswordHasher<TUser>(), validator.Object, new PasswordValidator<TUser>()); var userManager = new UserManager<TUser>(store, options, new PasswordHasher<TUser>(),
validator.Setup(v => v.ValidateAsync(userManager, It.IsAny<TUser>(), CancellationToken.None)).Returns(Task.FromResult(IdentityResult.Success)).Verifiable(); validator.Object, new PasswordValidator<TUser>(), new UpperInvariantUserNameNormalizer());
validator.Setup(v => v.ValidateAsync(userManager, It.IsAny<TUser>(), CancellationToken.None))
.Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
return userManager; return userManager;
} }
} }