要求在ASP.NET Core 2.2中確認電子郵件——第2部分
目錄
步驟1——將UnconfirmedEmail屬性新增到IdentityUser
在新的ASP.NET Core 2.2 Razor頁面模板中支援並修改Identity
介紹
ASP.NET Core 2.2 Web應用程式2部分中的第2部分,允許使用者更新已確認的電子郵件。以下是允許使用者更新其電子郵件的步驟。
要求在ASP.NET Core 2.2中確認電子郵件——第1部分
使用程式碼
先決條件
- .NET Core 2.2 SDK
- 以下VS中的一個:
- Visual Studio版本2017年15.9或更高版本
- Visual Studio for Mac 7.7或更高版本
- Visual Studio Code C#擴充套件版本1.17.1或更高版本
您可以在完成第1部分中的步驟後下載VS 2017專案或按照以下步驟修改您自己的專案。
步驟1——將UnconfirmedEmail屬性新增到IdentityUser
在Entities資料夾中新增命名為ApplicationUser的新類:
using Microsoft.AspNetCore.Identity;
namespace <YourProjectName>.Entities
{
public class ApplicationUser : IdentityUser
{
[PersonalData]
public string UnconfirmedEmail { get; set; }
}
}
使用查詢和替換,以當前工程中替換<IdentityUser>為<ApplicationUser>。
使用ApplicationUser
services.AddIdentity<ApplicationUser, IdentityRole>
編輯Areas\Identity\Pages\Account\Manage\EnableAuthenticator.cshtml.cs:
private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user)
編輯Areas\Identity\Pages\Account\Manage\DownloadPersonalData.cshtml.cs:
var personalDataProps = typeof(ApplicationUser).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
編輯Areas\Identity\Pages\Account\ExternalLogin.cshtml.cs:
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
編輯Register.cshtml.cs:
var user = new ApplicationUser { UserName = Input.UserName, Email = Input.Email };
解決替換IdentityUser的名稱空間問題。
using <YourProjectName>.Entities;
或者用於cshtml:
@using <YourProjectName>.Entities;
構建專案並檢查錯誤。
第2步——更新資料庫
編輯在Data資料夾中的ApplicationDbContext,新增ApplicationUser:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
從VS 2017中的程式包管理器控制檯執行命令“ Add-Migration UnconfirmedEmail ”。
執行命令“ Update-Database”。
第3步——新增更改電子郵件頁面
編輯ManageNavPages.cs,在上面新增ChangePassword屬性:
public static string ChangeEmail => "ChangeEmail";
和:
public static string ChangeEmailNavClass(ViewContext viewContext) =>
PageNavClass(viewContext, ChangeEmail);
編輯_ManageNav.cshtml,在下面添加個人資料項:
<li class="nav-item">
<a class="nav-link @ManageNavPages.ChangeEmailNavClass(ViewContext)"
id="change-email" asp-page="./ChangeEmail">Email</a></li>
在Areas\Identity\Pages\Account\Manage中建立名為ChangeEmail的razer頁面。
編輯ChangeEmail.cshtml:
@page
@model ChangeEmailModel
@{
ViewData["Title"] = "Change Email";
ViewData["ActivePage"] = ManageNavPages.ChangeEmail;
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="change-email-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" disabled />
</div>
<h5>New email needs to be verified.</h5>
<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>
<button type="submit" class="btn btn-primary">Update Email</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
編輯ChangeEmail.cshtml.cs:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using <YourProjectName>.Services;
using <YourProjectName>.Entities;
namespace <YourProjectName>.Areas.Identity.Pages.Account.Manage
{
public class ChangeEmailModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<ChangeEmailModel> _logger;
private readonly IEmailSender _emailSender;
public ChangeEmailModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<ChangeEmailModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
[TempData]
[Display(Name = "Verified Email")]
public string Email { get; set; }
[TempData]
public string StatusMessage { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "New Email")]
public string Email { 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 email = await _userManager.GetEmailAsync(user);
Email = email;
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 errors = new List<IdentityError>();
if (_userManager.Options.User.RequireUniqueEmail)
{
var owner = await _userManager.FindByEmailAsync(Input.Email);
if (owner != null && !string.Equals
(await _userManager.GetUserIdAsync(owner),
await _userManager.GetUserIdAsync(user)))
{
ModelState.AddModelError(string.Empty,
new IdentityErrorDescriber().DuplicateEmail(Input.Email).Description);
return Page();
}
}
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.Email.ToUpper() != email.ToUpper())
{
var result = await _userManager.UpdateSecurityStampAsync(user);
if (!result.Succeeded)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
return Page();
}
}
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>.");
_logger.LogInformation("User updated their UnconfirmedEmail.");
StatusMessage = "Please check your inbox to confirm the new email.";
}
else
{
_logger.LogInformation("User updated their Email.");
StatusMessage = "Your email has been updated.";
}
}
return RedirectToPage();
}
}
}
第4步——修改配置檔案
在Areas\Identity\Pages\Account\Manage中編輯Index.cshtml.cs使用新的ChangeEmail頁面。
新增:
public string Email { get; set; }
去掉:
public bool IsEmailConfirmed { get; set; }
從InputModel中刪除:
[Required]
[EmailAddress]
public string Email { get; set; }
從OnGetAsync>Input中刪除:
Email = email,
從OnGetAsync中刪除:
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
從OnPostAsync中刪除:
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}'.");
}
}
刪除:
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();
}
編輯Index.cshtml。
更換:
@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>
使用:
<input asp-for="Email" class="form-control" disabled />
第5步——重寫UserManager
在Services資料夾中新增命名為ApplicationUserManager的新類:
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using <YourProjectName>.Entities;
namespace <YourProjectName>.Services
{
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<ApplicationUser> passwordHasher,
IEnumerable<IUserValidator<ApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<ApplicationUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators,
passwordValidators, keyNormalizer, errors, services, logger)
{
}
/// <summary>
/// Sets the <paramref name="email"/> address for a <paramref name="user"/>.
/// </summary>
/// <param name="user">The user whose email should be set.</param>
/// <param name="email">The email to set.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation,
/// containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
public override async Task<IdentityResult> SetEmailAsync(ApplicationUser user, string email)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
if (user.EmailConfirmed && user.Email.ToUpper() != email.ToUpper())
user.UnconfirmedEmail = email;
else
user.Email = email;
return await UpdateUserAsync(user);
}
/// <summary>
/// Validates that an email confirmation token matches the specified
/// <paramref name="user"/> and if successful sets
/// EmailConfirmed to true and if UnconfirmedEmail is not NULL or Empty,
/// copies the user's UnconfirmedEmail to user's
/// Email and sets UnconfirmedEmail to NULL.
/// </summary>
/// <param name="user">The user to validate the token against.</param>
/// <param name="token">The email confirmation token to validate.</param>
/// <returns>
/// The <see cref="Task"/> that represents the asynchronous operation,
/// containing the <see cref="IdentityResult"/>
/// of the operation.
/// </returns>
public override async Task<IdentityResult>
ConfirmEmailAsync(ApplicationUser user, string token)
{
ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
IdentityResult result;
var provider = Options.Tokens.EmailConfirmationTokenProvider;
var isValidToken = await base.VerifyUserTokenAsync
(user, provider, "EmailConfirmation", token);
if (isValidToken)
{
if (!string.IsNullOrEmpty(user.UnconfirmedEmail))
{
user.Email = user.UnconfirmedEmail;
user.UnconfirmedEmail = null;
}
user.EmailConfirmed = true;
result = await UpdateUserAsync(user);
}
else
{
result = IdentityResult.Failed(new IdentityErrorDescriber().InvalidToken());
}
return result;
}
}
}
編輯Startup.cs > ConfigureServices,新增.AddUserManager<ApplicationUserManager>():
services.AddIdentity<ApplicationUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.User.RequireUniqueEmail = true;
})
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserManager<ApplicationUserManager>()
.AddDefaultTokenProviders();
構建並測試專案。
原文地址:https://www.codeproject.com/Articles/1272510/Require-Confirmed-Email-in-ASP-NET-Core-2-2-Part-2