ASP.NET Core 2.2 中要求验证电子邮件 - 第一部分





5.00/5 (8投票s)
在新的 ASP.NET Core 2.2 Razor 页面模板中生成并修改 Identity
引言
这是 ASP.NET Core 2.2 Web 应用程序更新已验证电子邮件的第 1 部分,共 2 部分。以下是生成和修改 Identity 以在登录前要求验证电子邮件的步骤。
ASP.NET Core 2.2 中要求验证电子邮件 - 第二部分
Using the Code
必备组件
- .NET Core 2.2 SDK
- 以下任一:
- Visual Studio 2017 版本 15.9 或更高版本
- Visual Studio for Mac 版本 7.7 或更高版本
- Visual Studio Code C# 扩展版本 1.17.1 或更高版本
您可以下载 VS 2017 项目或遵循以下步骤修改您自己的项目。
步骤 1 - 创建 Web 应用程序
创建一个新的 VS 2017 项目。
创建一个新的 ASP.NET Core Web 应用程序,并将身份验证更改为“Individual User Accounts”(个人用户账户)。
点击“确定”。
步骤 2 - 初始化数据库
该项目使用 SQL Server Express。
编辑 appsettings.json > ConnectionStrings
> DefaultConnection
来设置数据库。
在 VS 2017 的“程序包管理器控制台”中运行命令“Update-Database
”。
步骤 3 - 生成 Identity
右键单击项目名称 > 添加 > 新建生成项。
在左侧菜单中选择 **Identity**。
单击“添加”。
勾选“Override all files”(覆盖所有文件)并选择 ApplicationDbContext
。
单击“添加”。
步骤 4 - 替换默认 EmailSender
编辑 appsettings.json,添加 EmailSettings
并填写您的电子邮件服务器设置
"EmailSettings": {
"MailServer": "smtp.some_server.com",
"MailPort": 587,
"SenderName": "some name",
"Sender": "some_email@some_server.com",
"Password": "some_password"
}
在项目中添加一个名为 Entities 的新文件夹。
在 Entities
中添加一个名为 EmailSettings 的新类。
public class EmailSettings
{
public string MailServer { get; set; }
public int MailPort { get; set; }
public string SenderName { get; set; }
public string Sender { get; set; }
public string Password { get; set; }
}
在项目中添加一个名为 Services 的新文件夹。
在 Services 中添加一个名为 EmailSender
的新类。
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string htmlMessage);
}
public class EmailSender : IEmailSender
{
private readonly EmailSettings _emailSettings;
public EmailSender(IOptions<emailsettings> emailSettings)
{
_emailSettings = emailSettings.Value;
}
public Task SendEmailAsync(string email, string subject, string message)
{
try
{
// Credentials
var credentials = new NetworkCredential(_emailSettings.Sender, _emailSettings.Password);
// Mail message
var mail = new MailMessage()
{
From = new MailAddress(_emailSettings.Sender, _emailSettings.SenderName),
Subject = subject,
Body = message,
IsBodyHtml = true
};
mail.To.Add(new MailAddress(email));
// Smtp client
var client = new SmtpClient()
{
Port = _emailSettings.MailPort,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = _emailSettings.MailServer,
EnableSsl = true,
Credentials = credentials
};
// Send it...
client.Send(mail);
}
catch (Exception ex)
{
// TODO: handle exception
throw new InvalidOperationException(ex.Message);
}
return Task.CompletedTask;
}
}
向 EmailSender.cs 添加命名空间。
using Microsoft.Extensions.Options;
using <YourProjectName>.Entities;
using System.Net;
using System.Net.Mail;
编辑 Startup.cs > ConfigureServices
,添加 EmailSettings
选项。
services.AddOptions();
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
添加到 Startup.cs > ConfigureServices
的底部。
services.AddSingleton<IEmailSender, EmailSender>();
向 Startup.cs 添加命名空间。
using <YourProjectName>.Entities;
using <YourProjectName>.Services;
编辑 Register.cshtml.cs、ForgotPassword.cshtml.cs 和 Manage\Index.cshtml.cs 以使用新的 EmailSender
的 namespace
。
//using Microsoft.AspNetCore.Identity.UI.Services;
using <YourProjectName>.Services;
步骤 5 - 要求验证和唯一的电子邮件
编辑 Startup.cs > ConfigureServices
,使用 AddIdentity<IdentityUser, IdentityRole>
替代 AddDefaultIdentity<IdentityUser>
。
//services.AddDefaultIdentity<IdentityUser>()
services.AddIdentity<IdentityUser, IdentityRole>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
config.User.RequireUniqueEmail = true;
})
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
.AddDefaultTokenProviders();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorPagesOptions(options =>
{
options.AllowAreas = true;
options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
});
services.ConfigureApplicationCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.LogoutPath = $"/Identity/Account/Logout";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});
在 Areas\Identity\Pages\Account 中添加一个名为 CheckEmail
的 Razor 页面。
那么
编辑 CheckEmail.cshtml。
@page
@model CheckEmailModel
@{
ViewData["Title"] = "Check email";
}
<h2>@ViewData["Title"]</h2>
<p>
Please check your inbox to confirm your account.
</p>
编辑 CheckEmail.cshtml.cs,添加 AllowAnonymous
装饰。
[AllowAnonymous]
public class CheckEmailModel : PageModel
{
public void OnGet()
{
}
}
向 CheckEmail.cshtml.cs 添加命名空间。
using Microsoft.AspNetCore.Authorization;
编辑 Register.cshtml.cs > OnPostAsync
。
//await _signInManager.SignInAsync(user, isPersistent: false);
//return LocalRedirect(returnUrl);
return RedirectToPage("./CheckEmail");
步骤 6 - 为用户名添加登录名
编辑 Areas\Identity\Pages\Account\Register.cshtml.cs,向 Inputmodel
添加 UserName
属性。
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and
at max {1} characters long.", MinimumLength = 6)]
[Display(Name = "Login Name")]
public string UserName { get; set; }
编辑 Register.cshtml,添加 UserName
输入框。
<div class="form-group">
<label asp-for="Input.UserName"></label>
<input asp-for="Input.UserName" class="form-control" />
<span asp-validation-for="Input.UserName" class="text-danger"></span>
</div<
编辑 Register.cshtml.cs > OnPostAsync
,在新的 IdentityUser
构造函数中使用 Input.UserName
。
var user = new IdentityUser { UserName = Input.UserName, Email = Input.Email };
编辑 Login.cshtml.cs > InputModel
,将 Email
替换为 UserName
。
public class InputModel
{
[Required]
[Display(Name = "Login Name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
编辑 Login.cshtml.cs > OnPostAsync
,将 Input.Email
替换为 Input.UserName
。
var result = await _signInManager.PasswordSignInAsync
(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: true);
编辑 Login.cshtml,将 asp-for
上的 Email
替换为 UserName
。
<div class="form-group">
<label asp-for="Input.UserName"></label>
<input asp-for="Input.UserName" class="form-control" />
<span asp-validation-for="Input.UserName" class="text-danger"></span>
</div<
步骤 7 - 添加未验证电子邮件页面
在 Areas\Identity\Pages\Account 中添加一个名为 UnconfirmedEmail
的 Razor 页面。
编辑 UnconfirmedEmail.cshtml。
@page "{userId}"
@model UnconfirmedEmailModel
@{
ViewData["Title"] = "Confirm your email.";
}
<h2>@ViewData["Title"]</h2>
<h4>Enter your email.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger"></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>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
编辑 UnconfirmedEmail.cshtml.cs。
using <YourProjectName>.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace <YourProjectName>.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class UnconfirmedEmailModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IEmailSender _emailSender;
public UnconfirmedEmailModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
{
_userManager = userManager;
_emailSender = emailSender;
}
[TempData]
public string UserId { get; set; }
[BindProperty(SupportsGet = true)]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
}
public async Task OnGetAsync(string userId)
{
UserId = userId;
var user = await _userManager.FindByIdAsync(userId);
Input.Email = user.Email;
ModelState.Clear();
}
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByIdAsync(UserId);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToPage("./CheckEmail");
}
if (user.Email != Input.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();
}
}
await _userManager.SetEmailAsync(user, Input.Email);
}
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>.");
return RedirectToPage("./CheckEmail");
}
return Page();
}
}
}
步骤 8 - 修改登录
将 UserManager
注入到 Areas\Identity\Pages\Account\Login.cshtml.cs。
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<LoginModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
向 Login.cshtml.cs 添加 ShowResend
和 UserId
属性。
public bool ShowResend { get; set; }
public string UserId { get; set; }
添加到 Login.cshtml.cs > OnPostAsync
的 result.IsLockedOut
之后。
if (result.IsNotAllowed)
{
_logger.LogWarning("User email is not confirmed.");
ModelState.AddModelError(string.Empty, "Email is not confirmed.");
var user = await _userManager.FindByNameAsync(Input.UserName);
UserId = user.Id;
ShowResend = true;
return Page();
}
编辑 Login.cshtml 的 asp-validation-summary
之后。
@{
if (Model.ShowResend)
{
<p>
<a asp-page="./UnconfirmedEmail"
asp-route-userId="@Model.UserId">Resend verification?</a>
</p>
}
}
步骤 9 - 修改确认电子邮件
向 Areas\Identity\Pages\Account\ConfirmEmail.cshtml.cs 添加 ShowInvalid
属性。
public bool ShowInvalid { get; set; }
编辑 ConfirmEmail.cshtml.cs > OnGetAsync
。
if (!result.Succeeded)
{
//throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':");
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
ShowInvalid = true;
}
编辑 ConfirmEmail.cshtml。
<div asp-validation-summary="All" class="text-danger"></div>
@{
if (Model.ShowInvalid)
{
<p>
Error confirming your email.
</p>
<p>
If you can login, try updating your email again.<br />
If you cannot login, try resend verification.
</p>
}
else
{
<p>
Thank you for confirming your email.
</p>
}
}
构建并测试项目。
关注点
请注意,在 GenerateEmailConfirmationTokenAsync(user)
之前调用 UpdateSecurityStampAsync(user)
。这将使先前发送给用户的任何代码失效。
忘记密码和重置密码仍然使用 Email
来查找用户。现在您还可以选择通过 UserName
来查找用户。
历史
- 2018-12-21:初始发布
- 2018-12-22:更新了 SetCompatibilityVersion