Reputation: 1560
I am trying to create my own UserManager
extending from the original, and when I do a search by email, the user is not found. But if I do a search from the context, if I find the user (see the Get
method). To verify that it is really well implemented, I overwrote the FindByEmailAsync
method and it is really being called, but I do not know why the user can not find it. Some help? Thank you!
public void ConfigureServices(IServiceCollection servicesCollection)
{
servicesCollection.AddDbContext<MyIndentityContext>(currentOptions =>
currentOptions.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
servicesCollection.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<MyIndentityContext>()
.AddRoleStore<ApplicationRoleStore>()
.AddUserStore<ApplicationUserStore>()
.AddUserManager<ApplicationUserManager>()
.AddRoleManager<ApplicationRoleManager>()
.AddSignInManager<ApplicationSignInManager>()
.AddDefaultTokenProviders();
...
...
...
}
public class MyIndentityContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
private readonly IConfiguration _configuration;
private readonly IHttpContextAccessor _httpContextAccessor;
public MyIndentityContext(DbContextOptions dbContextOptions, IHttpContextAccessor httpContextAccessor,
IConfiguration configuration)
: base(dbContextOptions)
{
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("Sample.API");
}
}
public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole> roleStore,
IEnumerable<IRoleValidator<ApplicationRole>> roleValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, ILogger<ApplicationRoleManager> logger) : base(roleStore,
roleValidators,
keyNormalizer, errors, logger)
{
}
}
public class ApplicationSignInManager : SignInManager<ApplicationUser>
{
public ApplicationSignInManager(UserManager<ApplicationUser> userManager, IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory, IOptions<IdentityOptions> optionsAccessor,
ILogger<ApplicationSignInManager> logger, IAuthenticationSchemeProvider schemes) : base(userManager,
contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
}
}
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> userStore, IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<ApplicationUser> passwordHasher,
IEnumerable<IUserValidator<ApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors, IServiceProvider services, ILogger<ApplicationUserManager> logger) :
base(userStore, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors,
services, logger)
{ }
// Custom implementation to check if you are really calling the method
public override Task<ApplicationUser> FindByEmailAsync(string email)
{
return Task.Run(() => new ApplicationUser
{
UserName = "A_NAME"
});
}
}
public class ApplicationRoleStore : RoleStore<ApplicationRole, MyIndentityContext>
{
public ApplicationRoleStore(MyIndentityContext dbContext, IdentityErrorDescriber identityErrorDescriber)
: base(dbContext, identityErrorDescriber)
{}
}
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, MyIndentityContext, string>
{
public ApplicationUserStore(MyIndentityContext dbContext, IdentityErrorDescriber identityErrorDescriber)
: base(dbContext, identityErrorDescriber)
{}
}
public class ApplicationUser : IdentityUser {}
public class ApplicationRole : IdentityRole
{
public ApplicationRole() { }
public ApplicationRole(string roleName) : base(roleName) { }
public ApplicationRole(string roleName, string roleDescription) : base(roleName)
{
Description = roleDescription;
}
}
[Authorize]
[ApiController]
[Route("api/[controller]")]
[EnableCors(CORS.AllowSpecificOrigins)]
public class UserController : BaseController
{
private readonly ApplicationUserManager _applicationUserManager;
public UserController(ApplicationUserManager applicationUserManager)
{
_applicationUserManager = applicationUserManager;
}
// GET: api/User/5
[HttpGet("{id}")]
public async Task<UserDTO> Get(int id)
{
var currentUser = await _applicationUserManager.FindByEmailAsync("[email protected]"); ==> RETURN NULL!
var otherUser = _indentityContext.Users.Where(x => x.Email == "[email protected]"); ==> RETURN CORRECT USER!
return currentUser;
}
}
Upvotes: 4
Views: 3200
Reputation: 93003
Note: This answer references code and values shown in your Github repro.
When you call UserManager.FindByEmailAsync
, the value you pass into the method is normalised - by default, this normalisation converts the value to uppercase. This normalised value is then used to search the NormalizedEmail
column in the AspNetUsers
table.
Inside of your MyIndentityContext.OnModelCreating
method, you have the following code:
modelBuilder.Entity<ApplicationUser>().HasData(
new ApplicationUser
{
Email = "[email protected]"
});
As you're taking control of the data here and setting only Email
, the NormalizedEmail
value in the database is not being set (it's null
). This means that when you use UserManager.FindByEmailAsync
and are looking for [email protected]
in the NormalizedEmail
column, there's no match. However, when you use the DbSet
directly and look at the Email
column, you can find a matching record for [email protected]
.
To resolve this, I recommend that instead of using HasData
to seed your user(s), you use the UserManager.CreateAsync
method inside of a seed method within your application. This will ensure that normalisation and other related processing occurrs as it should before the records are persisted in the database.
Upvotes: 6