Menambahkan, mengunduh, dan menghapus data pengguna kustom ke Identity dalam proyek ASP.NET Core
Oleh Rick Anderson
Artikel ini menunjukkan cara:
- Tambahkan data pengguna kustom ke aplikasi web ASP.NET Core.
- Tandai model data pengguna kustom dengan PersonalDataAttribute atribut sehingga secara otomatis tersedia untuk diunduh dan dihapus. Membuat data dapat diunduh dan dihapus membantu memenuhi persyaratan GDPR .
Sampel proyek dibuat dari Razor aplikasi web Pages, tetapi instruksinya mirip untuk aplikasi web ASP.NET Core MVC.
Melihat atau mengunduh kode sampel (cara mengunduh)
Prasyarat
Membuat Razor aplikasi web
- Dari menu File Visual Studio, pilih Proyek Baru>. Beri nama proyek WebApp1 jika Anda ingin cocok dengan namespace kode sampel unduhan.
- Pilih ASP.NET Core Web Application>OK
- Pilih Aplikasi>Web OK
- Buat dan jalankan proyek.
Jalankan Identity perancah
- Dari Penjelajah Solusi, klik kanan proyek >Tambahkan>Item Perancah Baru.
- Dari panel kiri dialog Tambahkan Perancah, pilihIdentity> Tambahkan.
- Dalam dialog TambahkanIdentity, opsi berikut ini:
- Pilih file tata letak yang ada
~/Pages/Shared/_Layout.cshtml
- Pilih file berikut untuk diambil alih:
- Akun/Daftar
- Akun/Kelola/Indeks
- Pilih tombol + untuk membuat kelas konteks Data baru. Terima jenis (WebApp1.Models.WebApp1Context jika proyek bernama WebApp1).
- Pilih tombol + untuk membuat kelas Pengguna baru. Terima jenis (WebApp1User jika proyek bernama WebApp1) >Tambahkan.
- Pilih file tata letak yang ada
- Pilih Tambahkan.
Ikuti instruksi dalam Migrasi, UseAuthentication, dan tata letak untuk melakukan langkah-langkah berikut:
- Buat migrasi dan perbarui database.
- Menambahkan
UseAuthentication
keProgram.cs
- Tambahkan
<partial name="_LoginPartial" />
ke file tata letak. - Uji aplikasi:
- Mendaftarkan pengguna
- Pilih nama pengguna baru (di samping tautan Keluar ). Anda mungkin perlu memperluas jendela atau memilih ikon bilah navigasi untuk menampilkan nama pengguna dan tautan lainnya.
- Pilih tab Data Pribadi.
- Pilih tombol Unduh dan periksa
PersonalData.json
file. - Uji tombol Hapus , yang menghapus pengguna yang masuk.
Menambahkan data pengguna kustom ke Identity DB
IdentityUser
Perbarui kelas turunan dengan properti kustom. Jika Anda menamai proyek WebApp1, file diberi nama Areas/Identity/Data/WebApp1User.cs
. Perbarui file dengan kode berikut:
using Microsoft.AspNetCore.Identity;
namespace WebApp1.Areas.Identity.Data;
public class WebApp1User : IdentityUser
{
[PersonalData]
public string? Name { get; set; }
[PersonalData]
public DateTime DOB { get; set; }
}
Properti dengan atribut PersonalData adalah:
- Dihapus saat
Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml
Razor Halaman memanggilUserManager.Delete
. - Disertakan dalam data yang diunduh oleh
Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml
Razor Halaman.
Account/Manage/Index.cshtml
Memperbarui halaman
Perbarui masuk InputModel
dengan kode yang disorot Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
berikut:
public class IndexModel : PageModel
{
private readonly UserManager<WebApp1User> _userManager;
private readonly SignInManager<WebApp1User> _signInManager;
public IndexModel(
UserManager<WebApp1User> userManager,
SignInManager<WebApp1User> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string Username { get; set; }
// Remaining API warnings ommited.
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
private async Task LoadAsync(WebApp1User user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
Name = user.Name,
DOB = user.DOB,
PhoneNumber = phoneNumber
};
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with 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($"Unable to load user with 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 = "Unexpected error when trying to set phone number.";
return RedirectToPage();
}
}
if (Input.Name != user.Name)
{
user.Name = Input.Name;
}
if (Input.DOB != user.DOB)
{
user.DOB = Input.DOB;
}
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
}
Areas/Identity/Pages/Account/Manage/Index.cshtml
Perbarui dengan markup yang disorot berikut ini:
@page
@model IndexModel
@{
ViewData["Title"] = "Profile";
ViewData["ActivePage"] = ManageNavPages.Index;
}
<h3>@ViewData["Title"]</h3>
<partial name="_StatusMessage" for="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-floating">
<input asp-for="Username" class="form-control" disabled />
<label asp-for="Username" class="form-label"></label>
</div>
<div class="form-floating">
<input asp-for="Input.Name" class="form-control" />
<label asp-for="Input.Name" class="form-label"></label>
</div>
<div class="form-floating">
<input asp-for="Input.DOB" class="form-control" />
<label asp-for="Input.DOB" class="form-label"></label>
</div>
<div class="form-floating">
<input asp-for="Input.PhoneNumber" class="form-control" />
<label asp-for="Input.PhoneNumber" class="form-label"></label>
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Account/Register.cshtml
Memperbarui halaman
Perbarui masuk InputModel
dengan kode yang disorot Areas/Identity/Pages/Account/Register.cshtml.cs
berikut:
public class RegisterModel : PageModel
{
private readonly SignInManager<WebApp1User> _signInManager;
private readonly UserManager<WebApp1User> _userManager;
private readonly IUserStore<WebApp1User> _userStore;
private readonly IUserEmailStore<WebApp1User> _emailStore;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<WebApp1User> userManager,
IUserStore<WebApp1User> userStore,
SignInManager<WebApp1User> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_userStore = userStore;
_emailStore = GetEmailStore();
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
// Remaining API warnings ommited.
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = CreateUser();
user.Name = Input.Name;
user.DOB = Input.DOB;
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
private WebApp1User CreateUser()
{
try
{
return Activator.CreateInstance<WebApp1User>();
}
catch
{
throw new InvalidOperationException($"Can't create an instance of '{nameof(WebApp1User)}'. " +
$"Ensure that '{nameof(WebApp1User)}' is not an abstract class and has a parameterless constructor, or alternatively " +
$"override the register page in /Areas/Identity/Pages/Account/Register.cshtml");
}
}
private IUserEmailStore<WebApp1User> GetEmailStore()
{
if (!_userManager.SupportsUserEmail)
{
throw new NotSupportedException("The default UI requires a user store with email support.");
}
return (IUserEmailStore<WebApp1User>)_userStore;
}
}
}
Areas/Identity/Pages/Account/Register.cshtml
Perbarui dengan markup yang disorot berikut ini:
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h2>Create a new account.</h2>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-floating">
<input asp-for="Input.Name" class="form-control" />
<label asp-for="Input.Name"></label>
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.DOB" class="form-control" />
<label asp-for="Input.DOB"></label>
<span asp-validation-for="Input.DOB" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" />
<label asp-for="Input.Email"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" />
<label asp-for="Input.Password"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-floating">
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" />
<label asp-for="Input.ConfirmPassword"></label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h3>Use another service to register.</h3>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins!)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Bangun proyek.
Memperbarui tata letak
Lihat Perubahan tata letak untuk instruksi menambahkan tautan masuk dan keluar ke setiap halaman.
Menambahkan migrasi untuk data pengguna kustom
Di Konsol Manajer Paket Visual Studio:
Add-Migration CustomUserData
Update-Database
Menguji buat, lihat, unduh, hapus data pengguna kustom
Uji aplikasi:
- Daftarkan pengguna baru.
- Lihat data pengguna kustom di
/Identity/Account/Manage
halaman. - Unduh dan lihat data pribadi pengguna dari
/Identity/Account/Manage/PersonalData
halaman.
Membuat Razor aplikasi web
- Dari menu File Visual Studio, pilih Proyek Baru>. Beri nama proyek WebApp1 jika Anda ingin cocok dengan namespace kode sampel unduhan.
- Pilih ASP.NET Core Web Application>OK
- Pilih Aplikasi>Web OK
- Buat dan jalankan proyek.
Jalankan Identity perancah
- Dari Penjelajah Solusi, klik kanan proyek >Tambahkan>Item Perancah Baru.
- Dari panel kiri dialog Tambahkan Perancah, pilihIdentity> Tambahkan.
- Dalam dialog TambahkanIdentity, opsi berikut ini:
- Pilih file tata letak yang ada
~/Pages/Shared/_Layout.cshtml
- Pilih file berikut untuk diambil alih:
- Akun/Daftar
- Akun/Kelola/Indeks
- Pilih tombol + untuk membuat kelas konteks Data baru. Terima jenis (WebApp1.Models.WebApp1Context jika proyek bernama WebApp1).
- Pilih tombol + untuk membuat kelas Pengguna baru. Terima jenis (WebApp1User jika proyek bernama WebApp1) >Tambahkan.
- Pilih file tata letak yang ada
- Pilih Tambahkan.
Ikuti instruksi dalam Migrasi, UseAuthentication, dan tata letak untuk melakukan langkah-langkah berikut:
- Buat migrasi dan perbarui database.
- Tambahkan
UseAuthentication
keStartup.Configure
. - Tambahkan
<partial name="_LoginPartial" />
ke file tata letak. - Uji aplikasi:
- Mendaftarkan pengguna
- Pilih nama pengguna baru (di samping tautan Keluar ). Anda mungkin perlu memperluas jendela atau memilih ikon bilah navigasi untuk menampilkan nama pengguna dan tautan lainnya.
- Pilih tab Data Pribadi.
- Pilih tombol Unduh dan periksa
PersonalData.json
file. - Uji tombol Hapus , yang menghapus pengguna yang masuk.
Menambahkan data pengguna kustom ke Identity DB
IdentityUser
Perbarui kelas turunan dengan properti kustom. Jika Anda menamai proyek WebApp1, file diberi nama Areas/Identity/Data/WebApp1User.cs
. Perbarui file dengan kode berikut:
using System;
using Microsoft.AspNetCore.Identity;
namespace WebApp1.Areas.Identity.Data
{
public class WebApp1User : IdentityUser
{
[PersonalData]
public string Name { get; set; }
[PersonalData]
public DateTime DOB { get; set; }
}
}
Properti dengan atribut PersonalData adalah:
- Dihapus saat
Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml
Razor Halaman memanggilUserManager.Delete
. - Disertakan dalam data yang diunduh oleh
Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml
Razor Halaman.
Perbarui halaman Akun/Kelola/Index.cshtml
Perbarui masuk InputModel
dengan kode yang disorot Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
berikut:
public partial class IndexModel : PageModel
{
private readonly UserManager<WebApp1User> _userManager;
private readonly SignInManager<WebApp1User> _signInManager;
public IndexModel(
UserManager<WebApp1User> userManager,
SignInManager<WebApp1User> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
public string Username { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
private async Task LoadAsync(WebApp1User user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
Name = user.Name,
DOB = user.DOB,
PhoneNumber = phoneNumber
};
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound(
$"Unable to load user with 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(
$"Unable to load user with 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)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException(
$"Unexpected error occurred setting phone number for user with ID '{userId}'.");
}
}
if (Input.Name != user.Name)
{
user.Name = Input.Name;
}
if (Input.DOB != user.DOB)
{
user.DOB = Input.DOB;
}
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
}
Areas/Identity/Pages/Account/Manage/Index.cshtml
Perbarui dengan markup yang disorot berikut ini:
@page
@model IndexModel
@{
ViewData["Title"] = "Profile";
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="All" 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.Name"></label>
<input asp-for="Input.Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
</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>
<button id="update-profile-button" type="submit"
class="btn btn-primary">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Memperbarui halaman Akun/Register.cshtml
Perbarui masuk InputModel
dengan kode yang disorot Areas/Identity/Pages/Account/Register.cshtml.cs
berikut:
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<WebApp1User> _signInManager;
private readonly UserManager<WebApp1User> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<WebApp1User> userManager,
SignInManager<WebApp1User> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new WebApp1User {
Name = Input.Name,
DOB = Input.DOB,
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
Areas/Identity/Pages/Account/Register.cshtml
Perbarui dengan markup yang disorot berikut ini:
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
<span asp-validation-for="Input.DOB" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to register.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See
<a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support
logging in via external services.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin"
asp-route-returnUrl="@Model.ReturnUrl" method="post"
class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider"
value="@provider.Name"
title="Log in using your @provider.DisplayName account">
@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Bangun proyek.
Menambahkan migrasi untuk data pengguna kustom
Di Konsol Manajer Paket Visual Studio:
Add-Migration CustomUserData
Update-Database
Menguji buat, lihat, unduh, hapus data pengguna kustom
Uji aplikasi:
- Daftarkan pengguna baru.
- Lihat data pengguna kustom di
/Identity/Account/Manage
halaman. - Unduh dan lihat data pribadi pengguna dari
/Identity/Account/Manage/PersonalData
halaman.
Menambahkan klaim untuk Identity menggunakan IUserClaimsPrincipalFactory<ApplicationUser>
Catatan
Bagian ini bukan ekstensi dari tutorial sebelumnya. Untuk menerapkan langkah-langkah berikut ke aplikasi yang dibuat menggunakan tutorial, lihat masalah GitHub ini.
Klaim tambahan dapat ditambahkan ke ASP.NET Core Identity dengan menggunakan IUserClaimsPrincipalFactory<T>
antarmuka . Kelas ini dapat ditambahkan ke aplikasi dalam Startup.ConfigureServices
metode . Tambahkan implementasi kustom kelas sebagai berikut:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>,
AdditionalUserClaimsPrincipalFactory>();
Kode demo menggunakan ApplicationUser
kelas . Kelas ini menambahkan IsAdmin
properti yang digunakan untuk menambahkan klaim tambahan.
public class ApplicationUser : IdentityUser
{
public bool IsAdmin { get; set; }
}
Mengimplementasikan AdditionalUserClaimsPrincipalFactory
UserClaimsPrincipalFactory
antarmuka. Klaim peran baru ditambahkan ke ClaimsPrincipal
.
public class AdditionalUserClaimsPrincipalFactory
: UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public AdditionalUserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{}
public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
var identity = (ClaimsIdentity)principal.Identity;
var claims = new List<Claim>();
if (user.IsAdmin)
{
claims.Add(new Claim(JwtClaimTypes.Role, "admin"));
}
else
{
claims.Add(new Claim(JwtClaimTypes.Role, "user"));
}
identity.AddClaims(claims);
return principal;
}
}
Klaim tambahan kemudian dapat digunakan di aplikasi. Razor Di Halaman, IAuthorizationService
instans dapat digunakan untuk mengakses nilai klaim.
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
@if ((await AuthorizationService.AuthorizeAsync(User, "IsAdmin")).Succeeded)
{
<ul class="mr-auto navbar-nav">
<li class="nav-item">
<a class="nav-link" asp-controller="Admin" asp-action="Index">ADMIN</a>
</li>
</ul>
}
.NET Core 2.2 SDK atau yang lebih baru
Membuat Razor aplikasi web
- Dari menu File Visual Studio, pilih Proyek Baru>. Beri nama proyek WebApp1 jika Anda ingin cocok dengan namespace kode sampel unduhan.
- Pilih ASP.NET Core Web Application>OK
- Pilih ASP.NET Core 2.2 di menu dropdown
- Pilih Aplikasi>Web OK
- Buat dan jalankan proyek.
Jalankan Identity perancah
- Dari Penjelajah Solusi, klik kanan proyek >Tambahkan>Item Perancah Baru.
- Dari panel kiri dialog Tambahkan Perancah, pilihIdentity> Tambahkan.
- Dalam dialog TambahkanIdentity, opsi berikut ini:
- Pilih file tata letak yang ada
~/Pages/Shared/_Layout.cshtml
- Pilih file berikut untuk diambil alih:
- Akun/Daftar
- Akun/Kelola/Indeks
- Pilih tombol + untuk membuat kelas konteks Data baru. Terima jenis (WebApp1.Models.WebApp1Context jika proyek bernama WebApp1).
- Pilih tombol + untuk membuat kelas Pengguna baru. Terima jenis (WebApp1User jika proyek bernama WebApp1) >Tambahkan.
- Pilih file tata letak yang ada
- Pilih Tambahkan.
Ikuti instruksi dalam Migrasi, UseAuthentication, dan tata letak untuk melakukan langkah-langkah berikut:
- Buat migrasi dan perbarui database.
- Tambahkan
UseAuthentication
keStartup.Configure
. - Tambahkan
<partial name="_LoginPartial" />
ke file tata letak. - Uji aplikasi:
- Mendaftarkan pengguna
- Pilih nama pengguna baru (di samping tautan Keluar ). Anda mungkin perlu memperluas jendela atau memilih ikon bilah navigasi untuk menampilkan nama pengguna dan tautan lainnya.
- Pilih tab Data Pribadi.
- Pilih tombol Unduh dan periksa
PersonalData.json
file. - Uji tombol Hapus , yang menghapus pengguna yang masuk.
Menambahkan data pengguna kustom ke Identity DB
IdentityUser
Perbarui kelas turunan dengan properti kustom. Jika Anda menamai proyek WebApp1, file diberi nama Areas/Identity/Data/WebApp1User.cs
. Perbarui file dengan kode berikut:
using Microsoft.AspNetCore.Identity;
using System;
namespace WebApp1.Areas.Identity.Data
{
public class WebApp1User : IdentityUser
{
[PersonalData]
public string Name { get; set; }
[PersonalData]
public DateTime DOB { get; set; }
}
}
Properti dengan atribut PersonalData adalah:
- Dihapus saat
Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml
Razor Halaman memanggilUserManager.Delete
. - Disertakan dalam data yang diunduh oleh
Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml
Razor Halaman.
Perbarui halaman Akun/Kelola/Index.cshtml
Perbarui masuk InputModel
dengan kode yang disorot Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
berikut:
public partial class IndexModel : PageModel
{
private readonly UserManager<WebApp1User> _userManager;
private readonly SignInManager<WebApp1User> _signInManager;
private readonly IEmailSender _emailSender;
public IndexModel(
UserManager<WebApp1User> userManager,
SignInManager<WebApp1User> signInManager,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
}
public string Username { get; set; }
public bool IsEmailConfirmed { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var userName = await _userManager.GetUserNameAsync(user);
var email = await _userManager.GetEmailAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
Name = user.Name,
DOB = user.DOB,
Email = email,
PhoneNumber = phoneNumber
};
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var email = await _userManager.GetEmailAsync(user);
if (Input.Email != email)
{
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
if (Input.Name != user.Name)
{
user.Name = Input.Name;
}
if (Input.DOB != user.DOB)
{
user.DOB = Input.DOB;
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting phone number for user with ID '{userId}'.");
}
}
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var userId = await _userManager.GetUserIdAsync(user);
var email = await _userManager.GetEmailAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}
Areas/Identity/Pages/Account/Manage/Index.cshtml
Perbarui dengan markup yang disorot berikut ini:
@page
@model IndexModel
@{
ViewData["Title"] = "Profile";
ViewData["ActivePage"] = ManageNavPages.Index;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="All" 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.Email"></label>
@if (Model.IsEmailConfirmed)
{
<div class="input-group">
<input asp-for="Input.Email" class="form-control" />
<span class="input-group-addon" aria-hidden="true"><span class="glyphicon glyphicon-ok text-success"></span></span>
</div>
}
else
{
<input asp-for="Input.Email" class="form-control" />
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
}
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
</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>
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Memperbarui halaman Akun/Register.cshtml
Perbarui masuk InputModel
dengan kode yang disorot Areas/Identity/Pages/Account/Register.cshtml.cs
berikut:
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<WebApp1User> _signInManager;
private readonly UserManager<WebApp1User> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<WebApp1User> userManager,
SignInManager<WebApp1User> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "Full name")]
public string Name { get; set; }
[Required]
[Display(Name = "Birth Date")]
[DataType(DataType.Date)]
public DateTime DOB { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new WebApp1User {
Name = Input.Name,
DOB = Input.DOB,
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
Areas/Identity/Pages/Account/Register.cshtml
Perbarui dengan markup yang disorot berikut ini:
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Name"></label>
<input asp-for="Input.Name" class="form-control" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.DOB"></label>
<input asp-for="Input.DOB" class="form-control" />
<span asp-validation-for="Input.DOB" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Bangun proyek.
Menambahkan migrasi untuk data pengguna kustom
Di Konsol Manajer Paket Visual Studio:
Add-Migration CustomUserData
Update-Database
Menguji buat, lihat, unduh, hapus data pengguna kustom
Uji aplikasi:
- Daftarkan pengguna baru.
- Lihat data pengguna kustom di
/Identity/Account/Manage
halaman. - Unduh dan lihat data pribadi pengguna dari
/Identity/Account/Manage/PersonalData
halaman.
ASP.NET Core