Chuyển đến nội dung chính

ASP.NET Identity: Custom Identity User and thêm trang Profile - Part 4

Trong bài viết này, chúng ta sẽ thêm một số thuộc tính cho Identity User và tạo trang Profile dùng để update thông tin cho user

Xem thêm:

Custom Identity User

Mở file ApplicationUser.cs, thêm 2 thuộc tính DateOfBirth và ProfilePicture

Chúng ta định nghĩa Profile Picture kiểu byte[] dùng để lưu trữ image data. Nếu các bạn muốn lưu trữ bằng file vật lý trong thư mục wwwroot thì tham khảo thêm ở đây: ASP.NET Core: Upload file

public class ApplicationUser : IdentityUser
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public DateTime? DateOfBirth { get; set; }
	public byte[] ProfilePicture { get; set; }
}
Mở Package Manager Console, thực hiện tạo mới 1 migration
Add-Migration AddProfilePicture
Update-Database

Thêm trang Profile

Chúng ta sẽ có các step như sau
  1. Thêm 2 action: Manage (get) và Manage (post) để quản lý việc hiển thị và update Identity User
  2.  Thêm Model UserProfileViewModel chứa các properties dùng để hiển thị trên trang Profile
  3. Thêm option SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true để hệ thống không thêm thêm Required cho các property
Thêm file UserProfileViewModel
public class UserProfileViewModel
{
	[Display(Name = "First Name")]
	[Required(ErrorMessage = "Your First Name is required")]
	public string FirstName { get; set; }

	[Display(Name = "Last Name")]
	[Required(ErrorMessage = "Your Last Name is required")]
	public string LastName { get; set; }

	[Display(Name = "User name")]
	public string Username { get; set; }
	[Display(Name = "Date Of Birth")]
	[BindProperty, DataType(DataType.Date)]
	public DateTime? DateOfBirth { get; set; }

	[Phone]
	[Display(Name = "Phone number")]
	[Required]
	public string PhoneNumber { get; set; }
	[Display(Name = "Profile Picture")]
	public byte[] ProfilePicture { get; set; }
}
Mở file AccountController, thêm đoạn code như sau:
public async Task<IActionResult> Manage()
{
	var username = _userManager.GetUserName(User);
	if(string.IsNullOrEmpty(username))
	{
		return RedirectToAction("Login", "Account");
	}
	var user = await _userManager.FindByNameAsync(username);
	if (user == null)
	{
		return RedirectToAction("Login", "Account");
	}
	var userProfile = new UserProfileViewModel
	{
		PhoneNumber = user.PhoneNumber??string.Empty,
		Username = user.UserName ?? string.Empty,
		FirstName = user.FirstName,
		LastName = user.LastName,
		DateOfBirth = user.DateOfBirth,
		ProfilePicture = user.ProfilePicture
	};
	return View(userProfile);
}

[HttpPost]
public async Task<IActionResult> Manage(UserProfileViewModel model, IFormFile ProfilePicture)
{
	if(!ModelState.IsValid)
	{
		model.Username = User.Identity.Name;
		return View(model);
	}
	var username = User.Identity?.Name;
	if (string.IsNullOrEmpty(username))
	{
		return RedirectToAction("Login", "Account");
	}
	var user = await _userManager.FindByNameAsync(username);
	if (user == null)
	{
		return RedirectToAction("Login", "Account");
	}
	user.FirstName = model.FirstName;
	user.LastName = model.LastName;
	user.PhoneNumber = model.PhoneNumber;
	user.DateOfBirth = model.DateOfBirth;
	if(ProfilePicture != null)
	{
		using (var dataStream = new MemoryStream())
		{
			await ProfilePicture.CopyToAsync(dataStream);
			user.ProfilePicture = dataStream.ToArray();
		}
	}
	await _userManager.UpdateAsync(user);
	model.ProfilePicture = user.ProfilePicture;
	model.Username = username;
	return View(model);
}
Thêm View cho action Manage:
@model UserProfileViewModel

<form id="profile-form" method="post" enctype="multipart/form-data">
    @Html.ValidationSummary(true)
    <div class="row">
        <div class="col-md-6">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group mb-3">
                <label asp-for="FirstName"></label>
                <input asp-for="FirstName" class="form-control" />
                <span asp-validation-for="FirstName" class="text-danger"></span>
            </div>
            <div class="form-group mb-3">
                <label asp-for="LastName"></label>
                <input asp-for="LastName" class="form-control" />
                <span asp-validation-for="LastName" class="text-danger"></span>
            </div>
            <div class="form-group mb-3">
                <label asp-for="Username"></label>
                <input asp-for="Username" class="form-control" disabled />
                <span asp-validation-for="Username" class="text-danger"></span>
            </div>
            <div class="form-group mb-3">
                <label asp-for="DateOfBirth"></label>
                <input asp-for="DateOfBirth" class="form-control" max="@DateTime.Now.ToString("yyyy-MM-ddThh:mm")" min="@DateTime.Now.AddYears(-100).ToString("yyyy-MM-ddThh:mm")" />
                <span asp-validation-for="DateOfBirth" class="text-danger"></span>
            </div>
            <div class="form-group mb-3">
                <label asp-for="PhoneNumber"></label>
                <input asp-for="PhoneNumber" class="form-control" />
                <span asp-validation-for="PhoneNumber" class="text-danger"></span>
            </div>
            <button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
        </div>
        <div class="col-md-6">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group mb-3">
                <label asp-for="ProfilePicture" style="width: 100%;"></label>
                @if (Model.ProfilePicture != null && Model.ProfilePicture.Length > 0)
                {
                    <img id="profilePicture" style="width:275px;height:275px; object-fit:cover" src="data:image/*;base64,@(Convert.ToBase64String(Model.ProfilePicture))">
                }
                else
                {
                    <img id="profilePicture" style="width:275px;height:275px; object-fit:cover" src="~/images/default_avatar-png.png">
                }
            </div>
            <div class="form-group mb-3">
                <input type="file" class="form-control"
                       accept=".png,.jpg,.jpeg,.gif,.tif"
                       asp-for="ProfilePicture"
                       onchange="document.getElementById('profilePicture').src = window.URL.createObjectURL(this.files[0])" />
                <span asp-validation-for="ProfilePicture" class="text-danger"></span>
            </div>
        </div>

    </div>
</form>

 

Set SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true trong Program.cs để hệ thống không thêm attribute Required các các thuộc tính non-null trên View

// Add services to the container.
builder.Services.AddControllersWithViews(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

var app = builder.Build();
Nguyên nhân:
The validation system in .NET Core 3.0 and later treats non-nullable parameters or bound properties as if they had a [Required] attribute.

Build và run application để kiểm tra thành quả.

Download source code: https://github.com/anbinhtrong/AuthenticationAndAuthorization/releases/tag/profile_page_v4.0

Tham khảo

https://codewithmukesh.com/blog/user-management-in-aspnet-core-mvc/#Adding_a_Profile_Picture 

Chúc các bạn thành công!

Nhận xét