65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2018 年 12 月 22 日

MIT

3分钟阅读

viewsIcon

60437

downloadIcon

1066

在新的 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 项目。

New Project

创建一个新的 ASP.NET Core Web 应用程序,并将身份验证更改为“Individual User Accounts”(个人用户账户)。

New Web App

点击“确定”。

步骤 2 - 初始化数据库

该项目使用 SQL Server Express。

编辑 appsettings.json > ConnectionStrings > DefaultConnection 来设置数据库。

在 VS 2017 的“程序包管理器控制台”中运行命令“Update-Database”。

步骤 3 - 生成 Identity

右键单击项目名称 > 添加 > 新建生成项。

New Scaffolded Item

在左侧菜单中选择 **Identity**。

Add Scaffolded Identity

单击“添加”。

勾选“Override all files”(覆盖所有文件)并选择 ApplicationDbContext

Override Identity

单击“添加”。

步骤 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.csForgotPassword.cshtml.csManage\Index.cshtml.cs 以使用新的 EmailSendernamespace

//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 页面。

Add New Item

那么

New Razor Page

编辑 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 添加 ShowResendUserId 属性。

    public bool ShowResend { get; set; }
    public string UserId { get; set; }

添加到 Login.cshtml.cs > OnPostAsyncresult.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.cshtmlasp-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
© . All rights reserved.