目前應該處理了文章的相關功能,但是缺少登入登出等帳號相關部分,發的文章其實都是無名氏 XDD
今天來補這一塊。
首先先定義 IService 介面,先定義這幾個,不然今天寫不完。
public interface IUserService
{
public Task<User?> GetAsync(int id);
public Task InitCreateAsync(UserViewModel viewModel);
public Task CreateAsync(UserViewModel viewModel);
public Task UpdateAsync(UserViewModel viewModel);
}
介面中四個方法比較特別的是 InitCreateAsync
因為我希望第一位註冊的人是最大權限管理者。除了權限設定外,其餘邏輯跟 CreateAsync
一樣。
先定義 UserViewModel 讓我們在註冊與編輯時使用,不直接使用原本的 User 物件原因是時間欄位不是註冊跟編輯需樣的,另外加上 User 物件要加驗證屬性的話,需要使用 partial class 與 MetadataType,不然使用 EF Core 反向工程時會被覆蓋到,不如直接做 ViewModel。
這裡模型驗證需檢查的項目必填 [Required] 、信箱格式[EmailAddress]、密碼相同[Compare],另外我實做了 IValidatableObject 介面的 Validate 方法,來幫我檢查此信箱跟DisplayId有沒有被重複使用,最後實作了 ToUser() 方法,幫我轉成 User 物件。
public class UserViewModel : IValidatableObject
{
public int Id { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
[Compare(nameof(Password))]
public string PasswordCheck { get; set; } = string.Empty;
[Required]
public string Name { get; set; } = string.Empty;
[Required]
public string DisplayId { get; set; } = string.Empty;
public int Role { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Email))
{
yield break;
}
var userRepository = validationContext.GetService<IUserRepository>();
var emailUser = userRepository.Query(x => x.Email == Email).FirstOrDefault();
if (emailUser is not null && emailUser.Id != Id)
{
yield return new ValidationResult("此 Email 已存在", new[] { nameof(Email) });
}
var displayIdUser = userRepository.Query(x => x.DisplayId == DisplayId).FirstOrDefault();
if (displayIdUser is not null && displayIdUser.Id != Id)
{
yield return new ValidationResult("此 DisplayId 已存在", new[] { nameof(DisplayId) });
}
}
public User ToUser()
{
var user = new User();
user.Id = Id;
user.Name = Name;
user.Email = Email;
user.Password = Password;
user.DisplayId = DisplayId;
user.Role = Role;
user.CreateDate = new DateTime();
user.UpdateDate = user.CreateDate;
return user;
}
}
現在開始實作 UserService ,其中建構是注入除了,IUserRepository 之外,我還注入了 PasswordHasher ,幫密碼做 Hash 再存入。
public UserService(IUserRepository userRepository, PasswordHasher<User> passwordHasher)
{
_userRepository = userRepository;
_passwordHasher = passwordHasher;
}
public async Task<User?> GetAsync(int id)
{
var user = await _userRepository.GetAsync(x => x.Id == id);
return user;
}
有 HashUserPassword
和 VerifyHashedPassword
,分別做 Hash 雜湊,與驗證使用者密碼,明天做登入時會需要。
private void HashUserPassword(User user)
{
var hashedPassword = _passwordHasher.HashPassword(user, user.Password);
user.Password = hashedPassword;
}
private PasswordVerificationResult VerifyHashedPassword(User user, string password)
{
return _passwordHasher.VerifyHashedPassword(user, user.Password, password);
}
最後是 CreateAsync 和 UpdateAsync
public async Task CreateAsync(UserViewModel viewModel)
{
var user = viewModel.ToUser();
HashUserPassword(user);
user.Role = viewModel.Role;
user.CreateDate = DateTime.Now;
user.UpdateDate = user.CreateDate;
await _userRepository.CreateAsync(user);
_userRepository.UnitOfWork.Save();
}
public async Task UpdateAsync(UserViewModel viewModel)
{
var user = await _userRepository.GetAsync(x => x.Id == viewModel.Id);
if (user is null)
{
return;
}
user.Name = viewModel.Name;
user.Email = viewModel.Email;
user.Password = viewModel.Password;
user.DisplayId = viewModel.DisplayId;
HashUserPassword(user);
user.UpdateDate = DateTime.Now;
_userRepository.UnitOfWork.Save();
}
實際程式碼以 GitHub 上為主。