I have this method below that gets the current user, but it fetches the user 2 times, which seems a little redundant, so I'd like to see if there is a way I can reduce it to just once.
The first "User" fetch comes from "FindByEmailFromClaimsPrinciple()" and then the second comes from "_dbContext.Users"
Problem - I need User properties(email, username, roles) to pass to "CreateToken(User user)" for the claims. So it seems like I have no option but to fetch database data twice!
Quasi Solution - I can rework the extension method to only fetch the Id, UserName, and Email and that will save me a little DB bandwidth, but trying to see if I can reduce 2 fetches to 1.
Question - Is there a way I can refactor this code so that I only need to fetch User 1 time? More specifically, get my 2 User properties (most importantly roles), without having to get my whole User first, as it's a fairly large entity with ~30 columns.
[HttpGet]
public async Task <IActionResult> GetCurrentUser() {
var userFromRepo = await _userManager.FindByEmailFromClaimsPrinciple(HttpContext.User);
if (userFromRepo == null) return Unauthorized(new ApiResponse(401));
var token = await _tokenService.CreateToken(userFromRepo);
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,
Likers = p.Likers.Count(),
Rating = p.Rating,
CreatedDate = p.CreatedDate,
PhotoUrl = p.IsBoarded ? p.UserPhotos.FirstOrDefault(p => p.IsMain).Url : "assets/images/user.png",
Age = p.DateOfBirth.CalculateAge(),
DateOfBirth = p.DateOfBirth,
ExperienceLevel = p.ExperienceLevel.GetEnumName(),
Points = p.UserPoints.Sum(p => p.Points),
Tokens = p.UserTokens.Sum(p => p.Tokens),
Memberships = p.YogabandMemberships.Count(x => x.Status == YogabandMemberStatus.Active),
Token = token,
IsInstructor = p.IsInstructor,
})
.FirstOrDefaultAsync(p => p.Id == userFromRepo.Id);
if (user == null) return NotFound(new ApiResponse(404));
return Ok(user);
}
Here is CreateToken
public async Task <string> CreateToken(User user) {
var claims = new List <Claim> {
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.GivenName, user.UserName),
new Claim(JwtRegisteredClaimNames.NameId, user.Id.ToString()),
};
// GetRolesAync requires a User entity
var roles = await _userManager.GetRolesAsync(user);
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor {
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(365),
SigningCredentials = creds,
Issuer = _config["Token:Issuer"]
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
Here is the extension method
public static async Task<User> FindByEmailFromClaimsPrinciple(this UserManager<User> input, ClaimsPrincipal user)
{
var email = user?.Claims?.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value;
return await input.Users.SingleOrDefaultAsync(x => x.Email == email);
}