Bài 84: (ASP.NET Razor) Các trang quản lý tài khoản cá nhân trong Identity

Ngày đăng: 12/30/2022 9:29:23 AM

Các chức năng quản lý tài khoản cá nhân trong Identity

Mã nguồn mẫu phát sinh của Identity mà đã sử dụng trong các buổi trước nó còn có các trang Razor Page nằm ở thư mục Areas/Identity/Pages/Account/Manage nó gồm các trang Razor để User quản lý tài khoản của chính mihf như:

  • Index - hiện thị thông tin cơ bản của tài khoản
  • Email - thay đổi email tài khoản
  • Password - đổi password
  • ExternalLogins - liên kết với các dịch vụ ngoài Facebook, Google ... nếu trang hỗ trợ
  • PersonalData - tải thông tinn cá nhân, xóa tài khoản

 

Khi tài khoản đăng nhập, truy cập đến các chức năng này bắt đầu ở địa chỉ https://localhost:5001/identity/account/manage

Để các chức năng hoạt động bạn có thể tùy biến như sau:

Thêm vào file _ViewStart.cshtml ở thư mục Areas/Identity/Pages/Account/Manage với nội dung:

 @{
     Layout = "_Layout";
 }
 

Nó sẽ thiết lập tất cả cac trang này sử dụng _Layout tại thư mục hiện tại của chúng, tức dùng Areas/Identity/Pages/Account/Manage/_Layout.cshtml

File Areas/Identity/Pages/Account/Manage/_Layout.cshtml cập nhật thành

 @{
     if (ViewData.TryGetValue("ParentLayout", out var parentLayout))
     {
         Layout = (string)parentLayout;
     }
     else
     {
         // Layout = "/Areas/Identity/Pages/_Layout.cshtml";
         // Sử dụng tiếp Layout chung /Pages/Shared/_Layout.cshtml
         Layout = "/Pages/Shared/_Layout.cshtml";
     }
     
 }
 
 <h2>QUẢN LÝ TÀI KHOẢN</h2>
 
 <div>
     <h4>Cập nhật thông tin tài khoản của bạn</h4>
     <hr />
     <div class="row">
         <div class="col-md-3">
             <partial name="_ManageNav" />
         </div>
         <div class="col-md-9">
             @RenderBody()
         </div>
     </div>
 </div>
 
 @section Scripts {
     @RenderSection("Scripts", required: false)
 }

Giao diện truy cập sẽ là:

Thêm trường dữ liệu User, cập nhật trang Profile

Trong bàng User phát sinh theo Model - AppUser, giờ mong muốn ngoài các trường mặc định kế thừa từ IdentityUser bạn muốn thêm một vài trường dữ liệu nữa ví dụ bạn muốn thêm các trường: FullName, Address, Birthday

 using System;
 using System.ComponentModel.DataAnnotations;
 using Microsoft.AspNetCore.Identity;
 
 namespace Album.Models
 {
     public class AppUser : IdentityUser
     {
 
         [MaxLength(100)]
         public string FullName {set; get;}
         [MaxLength(255)]
         public string Address {set; get;}
         [DataType(DataType.Date)]
         public DateTime? Birthday {set; get;}
 
     }
 }

Nếu muốn thiết lập các trường phức tạp hơn bạn có thể dùng kỹ thuật Fluent API trong EF (EF Core) Tạo quan hệ trong Entity Framework với Fluent API C# CSharp

Thực hiện tạo Migration đặt tên updateuser và cập nhật vào Database

 dotnet ef migrations add updateuser
 dotnet ef database update
 

Sau khi cập nhật CSDL bảng User có thêm các trường mới thêm vào

Sửa đổi trang Hồ sơ cho phép cập nhật các trường bổ sung trên

Areas/Identity/Pages/Account/Manage/Index.cshtml.cs

 namespace Album.Areas.Identity.Pages.Account.Manage {
     public partial class IndexModel : PageModel {
         private readonly UserManager<AppUser> _userManager;
         private readonly SignInManager<AppUser> _signInManager;
 
         public IndexModel (
             UserManager<AppUser> userManager,
             SignInManager<AppUser> signInManager) {
             _userManager = userManager;
             _signInManager = signInManager;
         }
 
         [Display(Name = "Tên tài khoản")]
         public string Username { get; set; }
 
         [TempData]
         public string StatusMessage { get; set; }
 
         [BindProperty]
         public InputModel Input { get; set; }
 
         public class InputModel {
             [Phone]
             [Display (Name = "Số điện thoại")]
             public string PhoneNumber { get; set; }
 
             [MaxLength (100)]
             [Display(Name = "Họ tên đầy đủ")]
             public string FullName { set; get; }
 
             [MaxLength (255)]
             [Display(Name = "Địa chỉ")]
             public string Address { set; get; }
 
             [DataType (DataType.Date)]
             [Display(Name = "Ngày sinh d/m/y")]
             public DateTime? Birthday { set; get; }
 
         }
 
         // Nạp thông tin từ User vào Model
         private async Task LoadAsync (AppUser user) {
             var userName = await _userManager.GetUserNameAsync (user);
             var phoneNumber = await _userManager.GetPhoneNumberAsync (user);
             Username = userName;
             Input = new InputModel {
                 PhoneNumber = phoneNumber,
                 Birthday = user.Birthday,
                 Address = user.Address,
                 FullName = user.FullName
             };
         }
 
         public async Task<IActionResult> OnGetAsync () {
             var user = await _userManager.GetUserAsync (User);
 
             if (user == null) {
                 return NotFound ($"Không tải được tài khoản ID = '{_userManager.GetUserId(User)}'.");
             }
 
             await LoadAsync (user);
             return Page();
         }
 
         public async Task<IActionResult> OnPostAsync () {
             var user = await _userManager.GetUserAsync (User);
 
             if (user == null) {
                 return NotFound ($"Không có tài khoản ID: '{_userManager.GetUserId(User)}'.");
             }
 
             if (!ModelState.IsValid) {
                 await LoadAsync(user);
                 return Page ();
             }
 
             var phoneNumber = await _userManager.GetPhoneNumberAsync (user);
             if (Input.PhoneNumber != phoneNumber) {
                 var setPhoneResult = await _userManager.SetPhoneNumberAsync (user, Input.PhoneNumber);
                 if (!setPhoneResult.Succeeded) {
                     StatusMessage = "Lỗi cập nhật số điện thoại.";
                     return RedirectToPage ();
                 }
             }
 
             // Cập nhật các trường bổ sung
             user.Address  = Input.Address;
             user.Birthday = Input.Birthday;
             user.FullName = Input.FullName;
             await _userManager.UpdateAsync(user);
 
             // Đăng nhập lại để làm mới Cookie (không nhớ thông tin cũ)
             await _signInManager.RefreshSignInAsync (user);
             StatusMessage = "Hồ sơ của bạn đã cập nhật";
             return RedirectToPage ();
         }
     }
 }
 

Areas/Identity/Pages/Account/Manage/Index.cshtml

 @page
 @model IndexModel
 @{
     ViewData["Title"] = "HỒ SƠ";
     ViewData["ActivePage"] = ManageNavPages.Index;
 }
 
 <h4>@ViewData["Title"]</h4>
 <partial name="_StatusMessage" model="Model.StatusMessage" />
 <div class="row">
     <div class="col-md-6">
         <form id="profile-form" method="post">
             <div asp-validation-summary="ModelOnly" class="text-danger"></div>
             <div class="form-group">
                 <label asp-for="Username"></label>
                 <input asp-for="Username" class="form-control" disabled />
             </div>
             <div class="form-group">
                 <label asp-for="Input.PhoneNumber"></label>
                 <input asp-for="Input.PhoneNumber" class="form-control" />
                 <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
             </div>
             <div class="form-group">
                 <label asp-for="Input.FullName"></label>
                 <input asp-for="Input.FullName" class="form-control" />
                 <span asp-validation-for="Input.FullName" class="text-danger"></span>
             </div>
             <div class="form-group">
                 <label asp-for="Input.Address"></label>
                 <input asp-for="Input.Address" class="form-control" />
                 <span asp-validation-for="Input.Address" class="text-danger"></span>
             </div>
             <div class="form-group">
                 <label asp-for="Input.Birthday"></label>
                 @Html.TextBoxFor(m => m.Input.Birthday, "{0:d}", new {@class = "form-control"})
                 <span asp-validation-for="Input.Birthday" class="text-danger"></span>
             </div>
 
             <button id="update-profile-button" type="submit" class="btn btn-danger">Lưu hồ sơ</button>
         </form>
     </div>
 </div>
 
 @section Scripts {
     <partial name="_ValidationScriptsPartial" />
 }

Khi truy cập cập nhật hồ sơ, các trường bổ sung được lưu lại

Đối với dữ liệu ngày sinh bạn có thể thiết lập hiện thị ở định dạng chuỗi dd/MM/yyyy ngày - tháng - năm. Để thực hiện hãy sửa Areas/Identity/Pages/Account/Manage/Index.cshtml dùng Html.TextBoxFor để tạo HTML của Birthday

 <div class="form-group">
     <label asp-for="Input.Birthday"></label>
     @Html.TextBoxFor(m => m.Input.Birthday, "{0:dd/MM/yyyy}", new {@class = "form-control"})
     <span asp-validation-for="Input.Birthday" class="text-danger"></span>
 </div>

Tuy nhiên khi thiết lập hiện thị ở định dạng như vậy, khi thực hiện binding dữ liệu có thể dẫn tới lỗi. Trong trường hợp này bạn có thể áp dụng Xây dựng ModelBinder riêng

Ví dụ tạo ra lớp DayMonthYearBinder

Binder/DayMonthYearBinder.cs

 public class DayMonthYearBinder : IModelBinder {
     public Task BindModelAsync (ModelBindingContext bindingContext) {
         if (bindingContext == null) {
             throw new ArgumentNullException (nameof (bindingContext));
         }
         // Lấy tên Model (Object, thuộc tính ...)
         string modelName = bindingContext.ModelName;
 
         // Lấy giá trị truyền đến tương ứng với modelName
         ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue (modelName);
 
         // Không có giá trị
         if (valueProviderResult == ValueProviderResult.None) {
             return Task.CompletedTask;
         }
 
         // Thiết lập và lấy giá trị (đầu tiên)
         bindingContext.ModelState.SetModelValue (modelName, valueProviderResult);
         string valueStrinng = valueProviderResult.FirstValue;
 
         // Xử lý nếu giá trị truyền đến là null
         if (string.IsNullOrEmpty (valueStrinng)) {
             return Task.CompletedTask;
         }
 
         // Chuyển chuỗi giá trị thành DateTime
         DateTime? date = null;
         try {
             date = DateTime.ParseExact (valueStrinng, "dd/MM/yyyy", null);
         } catch {
 
             bindingContext.ModelState.TryAddModelError (modelName,
                     "Nhập ngày tháng bị sai - yêu cầu định dạng dd/MM/yyyy (ví dụ 20/11/2020)");
             return Task.CompletedTask;
         }
         if (date < DateTime.Parse ("1/1/1945")) {
             bindingContext.ModelState.TryAddModelError (modelName, "Abcs");
             return Task.CompletedTask;
 
         }
         // Gán giá trị thành công
         bindingContext.Result = ModelBindingResult.Success (date);
         return Task.CompletedTask;
 
     }
 }
 

Sau đó áp dụng vào thiết lập Binding của trường Birthday

 [DataType (DataType.Date)]
 [Display(Name = "Ngày sinh d/m/y")]
 [ModelBinder(BinderType=typeof(DayMonthYearBinder))]
 [DisplayFormat(ApplyFormatInEditMode=true, DataFormatString = "{0:dd/MM/yyyy}")]
 public DateTime? Birthday { set; get; }
 

Lưu ý

  • Để tải về User theo thông tin đang đăng nhập dùng UserManager gọi GetUserAsync
     var user = await _userManager.GetUserAsync (User);
  • Các của User gồm UserNameEmailPhoneNumber có phương thức trong UserManager để truy vấn và thiết lập thông tin đó tương ứng theo tên như: GetEmailAsync(), SetEmailAsync ...
  • Để cập nhật thông tin User có thể dùng phương thức UpdateAsync của
  • Để đăng nhập lại theo thông tin mới cập nhật sử dụng
     await _signInManager.RefreshSignInAsync (user);

Trang quản lý Email của User

Mã code trang này tại Areas/Identity/Pages/Account/Manage/Email.cshtml.cs, có chức năng để người dùng thay đổi Email của mình thành một địa chỉ email khác.

Khi thay đổi - một Email được gửi đến Email mới để xác nhận, trong email này có mã Token phát sinh bởi:

 var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);

Nếu người dùng mở Email và truy cập theo link hướng dẫn thì nó sẽ chuyển đến trang thực hiện thay đổi email thực sự,code trang này tại Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs

Thực hiện thay đổi Email bằng

 var result = await _userManager.ChangeEmailAsync(user, email, code);

Nếu code chính xác, email mới chưa ai dùng thì đổi email thành công

Trang đổi password của User

Trang này tại Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs có chức năng cho phép User đổi password mới.

Để đổi password thực hiện đoạn mã:

 var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
 

Chú ý:

Trước khi thiết lập mật khẩu cần kiểm tra tài khoản có mật khẩu hay không bằng

 var hasPassword = await _userManager.HasPasswordAsync(user);

Nếu tài khoản chưa từng đặt mật khẩu (ví dụ khi đăng ký từ dịch vụ ngoài Google, Facebook ...) thì chuyển hướng về trang Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs tại trang này sẽ cho đặt mặt khẩu mới bằng cách sử dụng

 var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);

Trang quản lý đăng nhập từ Google, Facebook ...

Trang này code tại Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs, có chức năng để User tích hợp đăng ký từ dịch vụ ngoài theo tính năng ứng dụng hỗ trợ như Google, Facebook ... hoặc để loại bỏ liên kết tài khoản từ dịch vụ ngoài

Để lấy các dịch vụ đã liên kết sử dụng

 CurrentLogins = await _userManager.GetLoginsAsync(user);

Để lấy các dịch vụ ứng dụng hỗ trợ mà tài khoản chưa liên kết sử dụng

 OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
     .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
     .ToList();
 

Để xóa bỏ một liên kết thực hiện

 var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
 

Để liên kết với dịch vụ chưa đăng ký cần thực hiện đăng xuất thông tin đang liên kết với tài khoản ngoài nếu có

 await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Sau đó chuyển hướng xác thực từ tài khoản này, sau khi xác thực sẽ chuyển hướngg về OnGetLinkLoginCallbackAsync trong ExternalLogins.cshtml.cs để thực hiện liên kết, tương từ như trang Login

Mã nguồn tham khảo ASP_NET_CORE/Album

Nguồn tin: Xuanthulab