C# cơ bản .NET Core

Bài này tiếp tục thực hành, phát triển trên dự án của ví dụ Album trước: Identity (4) - Ứng dụng Album

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 UserName, Email, PhoneNumber 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


    Đăng ký nhận bài viết mới