Reputation: 14610
My login method is below and you'll see I have to call my user again after I fetch it with _userManager.FindByEmailAsync()
because there are a couple of fields Points
and Tokens
that are calculated as a Sum()
Question - Is there any way I can extend _userManager.FindByEmailAsync()
so that it will run the Sum
as I do in the .Select()
call, so I don't have a redundant call to the DB?
I tried to call:
var userFromRepo = await _userManager.Users.Select(p => new Core.Entities.User { Id = p.Id, Email = p.Email, UserName = p.UserName}).SingleOrDefaultAsync(p => p.Email == loginDto.Email);
and pass it into CheckPasswordSignInAsync()
but the result failed, so it's looking for something other than Email, UserName, or Id. But what?
If I just call this to return the whole User
:
var userFromRepo = await _userManager.Users.SingleOrDefaultAsync(p => p.Email == loginDto.Email);
It works but I can't run the Sum()
calls for Point
and Token
I also tried extending UserManager like this, with no luck.
public static User FindByEmail(this UserManager <User> input, string email) {
return input.Users.Select(p => new User {
Id = p.Id,
Email = p.Email,
UserName = p.UserName
}).SingleOrDefault(x => x.Email == email);
}
Here is my login method.
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login(LoginDto loginDto) {
var userFromRepo = await _userManager.FindByEmailAsync(loginDto.Email);
if (userFromRepo == null) return Unauthorized(new ApiResponse(401));
if (userFromRepo.IsActive == false) return NotFound(new ApiResponse(404, "This account is no longer active."));
var result = await _signInManager.CheckPasswordSignInAsync(userFromRepo, loginDto.Password, false);
if (!result.Succeeded) return Unauthorized(new ApiResponse(401));
var user = await _dbContext.Users
.Select(p => new {
Id = p.Id,
Email = p.Email,
UserName = p.UserName,
Hosted = p.Hosted,
Instructed = p.Instructed,
Attended = p.Attended,
IsBoarded = p.IsBoarded,
IsActive = p.IsActive,
Likers = p.LikersCount,
Rating = p.Rating,
YearStarted = p.YearStarted,
YearsInstructing = p.YearsInstructing,
YearsPracticing = System.DateTime.Now.Year - p.YearStarted,
Certification = p.Certification.ToString(),
CreatedDate = p.CreatedDate,
PhotoUrl = p.IsBoarded ? p.UserPhoto : "assets/images/user.png",
Age = p.DateOfBirth.CalculateAge(),
DateOfBirth = p.DateOfBirth,
ExperienceLevel = p.ExperienceLevel.GetEnumName(),
Points = p.UserPoints.Sum(p => p.Points),
Tokens = p.Tokens.Sum(p => p.Tokens),
Token = _tokenService.CreateToken(userFromRepo, (from userRole in p.UserRoles join role in _dbContext.Roles on userRole.RoleId equals role.Id select role.Name).ToList()),
IsInstructor = p.IsInstructor
})
.FirstOrDefaultAsync(p => p.Id == userFromRepo.Id);
if (user == null) return NotFound(new ApiResponse(404));
return Ok(user);
}
Upvotes: 3
Views: 225
Reputation: 5
You do not have to call the user again the user already exist on userfromrepo.
Just preform your logic on your current user object, userfromrepo.column name = what ever value. It will not update the database. Just the current user object and return it.
Upvotes: -2
Reputation: 14610
To solve my issue I just changed this code below. Now I only fetch once from the DB, instead of 3 times.
Maybe someone who knows UserManager
can weigh in on the difference between what I had and what I have now in terms of security?
// Old Code
var userFromRepo = await _userManager.FindByEmailAsync(loginDto.Email);
// New code
var user = await _userManager.Users
.Include(p => p.TokensPoints)
.Include(p => p.UserRoles)
.ThenInclude(r => r.Role)
.SingleOrDefaultAsync(x => x.NormalizedEmail == loginDto.Email);
next I just map the fields I want to the DTO being returned to the client.
Upvotes: 0
Reputation: 28397
If you want to extend the _userManager.FindByEmailAsync
method, you need to create a custom class which inherts the IUserEmailStore
, since the FindByEmailAsync
method will call the IUserEmailStore's FindByEmailAsync method.
The source codes for the _userManager
is like this:
public virtual async Task<TUser?> FindByEmailAsync(string email)
{
ThrowIfDisposed();
IUserEmailStore<TUser> store = GetEmailStore();
Microsoft.AspNetCore.Shared.ArgumentNullThrowHelper.ThrowIfNull(email, "email");
email = NormalizeEmail(email);
TUser val = await store.FindByEmailAsync(email, CancellationToken).ConfigureAwait(continueOnCapturedContext: false);
if (val == null && Options.Stores.ProtectPersonalData)
{
ILookupProtectorKeyRing service = _services.GetService<ILookupProtectorKeyRing>();
ILookupProtector protector = _services.GetService<ILookupProtector>();
if (service != null && protector != null)
{
foreach (string allKeyId in service.GetAllKeyIds())
{
string normalizedEmail = protector.Protect(allKeyId, email);
val = await store.FindByEmailAsync(normalizedEmail, CancellationToken).ConfigureAwait(continueOnCapturedContext: false);
if (val != null)
{
return val;
}
}
}
}
return val;
}
Inside the new implemented IUserEmailStore class ,you could modify the FindByEmailAsync like below:
_dbContext.Users
.Where(u => u.Email == email)
.Select(p => new UserDto
{
Id = p.Id,
Email = p.Email,
UserName = p.UserName,
Hosted = p.Hosted,
Instructed = p.Instructed,
Attended = p.Attended,
IsBoarded = p.IsBoarded,
IsActive = p.IsActive,
Likers = p.LikersCount,
Rating = p.Rating,
YearStarted = p.YearStarted,
YearsInstructing = p.YearsInstructing,
YearsPracticing = DateTime.Now.Year - p.YearStarted,
Certification = p.Certification.ToString(),
CreatedDate = p.CreatedDate,
PhotoUrl = p.IsBoarded ? p.UserPhoto : "assets/images/user.png",
Age = p.DateOfBirth.CalculateAge(),
DateOfBirth = p.DateOfBirth,
ExperienceLevel = p.ExperienceLevel.GetEnumName(),
Points = p.UserPoints.Sum(up => up.Points),
Tokens = p.Tokens.Sum(t => t.Tokens),
Token = _tokenService.CreateToken(p,
(from userRole in p.UserRoles
join role in _dbContext.Roles on userRole.RoleId equals role.Id
select role.Name).ToList()),
IsInstructor = p.IsInstructor
})
.FirstOrDefaultAsync();
Then register it inside the program.cs:
builder.Services.AddTransient<IUserEmailStore<ApplicationUser>, CustomUserEmailStore>();
More details about how to use it, you could refer to this article.
Upvotes: -1