ASP.NET Core 项目中的基架标识Scaffold Identity in ASP.NET Core projects

作者:Rick AndersonBy Rick Anderson

ASP.NET Core 2.1 及更高版本提供了ASP.NET Core 标识作为Razor 类库ASP.NET Core 2.1 and later provides ASP.NET Core Identity as a Razor Class Library. 包含标识的应用程序可以应用 scaffolder 来有选择地添加标识 Razor 类库中包含的源代码(RCL)。Applications that include Identity can apply the scaffolder to selectively add the source code contained in the Identity Razor Class Library (RCL). 建议生成源代码,以便修改代码和更改行为。You might want to generate source code so you can modify the code and change the behavior. 例如,可以指示基架生成在注册过程中使用的代码。For example, you could instruct the scaffolder to generate the code used in registration. 生成的代码优先于标识 RCL 中的相同代码。Generated code takes precedence over the same code in the Identity RCL. 若要完全控制 UI,而不使用默认的 RCL,请参阅创建完全标识 UI 源部分。To gain full control of the UI and not use the default RCL, see the section Create full identity UI source.

包含身份验证的应用程序可以应用 scaffolder 来添加 RCL 标识包。Applications that do not include authentication can apply the scaffolder to add the RCL Identity package. 可以选择要生成的标识代码。You have the option of selecting Identity code to be generated.

尽管 scaffolder 生成了大部分必要的代码,但你必须更新项目才能完成此过程。Although the scaffolder generates most of the necessary code, you'll have to update your project to complete the process. 本文档介绍完成标识基架更新所需的步骤。This document explains the steps needed to complete an Identity scaffolding update.

运行标识 scaffolder 时,会在项目目录中创建一个ScaffoldingReadme文件。When the Identity scaffolder is run, a ScaffoldingReadme.txt file is created in the project directory. ScaffoldingReadme文件包含有关完成标识基架更新所需内容的一般说明。The ScaffoldingReadme.txt file contains general instructions on what's needed to complete the Identity scaffolding update. 本文档包含的有关ScaffoldingReadme文件的完整说明。This document contains more complete instructions than the ScaffoldingReadme.txt file.

建议使用显示文件差异的源代码管理系统,并使您能够回退更改。We recommend using a source control system that shows file differences and allows you to back out of changes. 运行标识 scaffolder 后检查更改。Inspect the changes after running the Identity scaffolder.

备注

使用双重身份验证帐户确认和密码恢复,以及使用标识的其他安全功能时,需要提供服务。Services are required when using Two Factor Authentication, Account confirmation and password recovery, and other security features with Identity. 基架标识时不生成服务或服务存根。Services or service stubs aren't generated when scaffolding Identity. 要启用这些功能,必须手动添加服务。Services to enable these features must be added manually. 例如,请参阅需要确认电子邮件For example, see Require Email Confirmation.

将标识基架到空项目中Scaffold identity into an empty project

运行标识 scaffolder:Run the Identity scaffolder:

  • 解决方案资源管理器,右键单击该项目 >添加 > 新基架项From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 从左窗格添加基架对话框中,选择标识 > 添加From the left pane of the Add Scaffold dialog, select Identity > ADD.
  • 在 "添加标识" 对话框中,选择所需的选项。In the ADD Identity dialog, select the options you want.
    • 选择现有的布局页, 否则将用错误的标记覆盖你的布局文件。Select your existing layout page, or your layout file will be overwritten with incorrect markup. 例如,对于 MVC 项目 Razor Pages ~/Views/Shared/_Layout.cshtml ~/Pages/Shared/_Layout.cshtmlFor example ~/Pages/Shared/_Layout.cshtml for Razor Pages ~/Views/Shared/_Layout.cshtml for MVC projects
    • 选择 + 按钮以创建一个新数据上下文类Select the + button to create a new Data context class.
  • 选择添加Select ADD.

将以下突出显示的调用添加到 Startup 类:Add the following highlighted calls to the Startup class:

public class Startup
{        
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseAuthentication();
        app.UseMvc();
    }
}

UseHsts 建议但不是必需的。UseHsts is recommended but not required. 请参阅HTTP 严格传输安全性协议有关详细信息。See HTTP Strict Transport Security Protocol for more information.

生成的标识数据库代码需要Entity Framework Core 迁移The generated Identity database code requires Entity Framework Core Migrations. 创建迁移并更新数据库。Create a migration and update the database. 例如,运行以下命令:For example, run the following commands:

在 Visual Studio程序包管理器控制台:In the Visual Studio Package Manager Console:

Add-Migration CreateIdentitySchema
Update-Database

Add-Migration命令的 "CreateIdentitySchema" 名称参数是任意的。The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema"介绍迁移。"CreateIdentitySchema" describes the migration.

将标识基架到 Razor 项目,而无需现有授权Scaffold identity into a Razor project without existing authorization

运行标识 scaffolder:Run the Identity scaffolder:

  • 解决方案资源管理器,右键单击该项目 >添加 > 新基架项From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 从左窗格添加基架对话框中,选择标识 > 添加From the left pane of the Add Scaffold dialog, select Identity > ADD.
  • 在 "添加标识" 对话框中,选择所需的选项。In the ADD Identity dialog, select the options you want.
    • 选择现有的布局页, 否则将用错误的标记覆盖你的布局文件。Select your existing layout page, or your layout file will be overwritten with incorrect markup. 例如,对于 MVC 项目 Razor Pages ~/Views/Shared/_Layout.cshtml ~/Pages/Shared/_Layout.cshtmlFor example ~/Pages/Shared/_Layout.cshtml for Razor Pages ~/Views/Shared/_Layout.cshtml for MVC projects
    • 选择 + 按钮以创建一个新数据上下文类Select the + button to create a new Data context class.
  • 选择添加Select ADD.

区域/标识/IdentityHostingStartup中配置标识。Identity is configured in Areas/Identity/IdentityHostingStartup.cs. 有关详细信息,请参阅IHostingStartupfor more information, see IHostingStartup.

迁移、UseAuthentication 和布局Migrations, UseAuthentication, and layout

生成的标识数据库代码需要Entity Framework Core 迁移The generated Identity database code requires Entity Framework Core Migrations. 创建迁移并更新数据库。Create a migration and update the database. 例如,运行以下命令:For example, run the following commands:

在 Visual Studio程序包管理器控制台:In the Visual Studio Package Manager Console:

Add-Migration CreateIdentitySchema
Update-Database

Add-Migration命令的 "CreateIdentitySchema" 名称参数是任意的。The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema"介绍迁移。"CreateIdentitySchema" describes the migration.

启用身份验证Enable authentication

在 @no__t 1 类的 Configure 方法中,在 UseStaticFiles 后调用UseAuthenticationIn the Configure method of the Startup class, call UseAuthentication after UseStaticFiles:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseAuthentication();

        app.UseMvc();
    }
}

UseHsts 建议但不是必需的。UseHsts is recommended but not required. 请参阅HTTP 严格传输安全性协议有关详细信息。See HTTP Strict Transport Security Protocol for more information.

布局更改Layout changes

可选:将登录部分(_LoginPartial)添加到布局文件中:Optional: Add the login partial (_LoginPartial) to the layout file:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorNoAuth8</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">RazorNoAuth8</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Contact">Contact</a></li>
                </ul>
                <partial name="_LoginPartial" />
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - RazorNoAuth8</p>
        </footer>
    </div>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>

使用授权将标识基架到 Razor 项目Scaffold identity into a Razor project with authorization

运行标识 scaffolder:Run the Identity scaffolder:

  • 解决方案资源管理器,右键单击该项目 >添加 > 新基架项From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 在 "添加基架" 对话框的左窗格中, 选择 "标识 > " "添加"。From the left pane of the Add Scaffold dialog, select Identity > Add.
  • 在 "添加标识" 对话框中, 选择所需的选项。In the Add Identity dialog, select the options you want.
    • 选择现有的布局页, 否则将用错误的标记覆盖你的布局文件。Select your existing layout page, or your layout file will be overwritten with incorrect markup. 如果选择了现有 _的布局 cshtml文件, 则会覆盖它。When an existing _Layout.cshtml file is selected, it is not overwritten.

例如: ~/Pages/Shared/_Layout.cshtml对于 MVC 项目~/Views/Shared/_Layout.cshtml Razor PagesFor example: ~/Pages/Shared/_Layout.cshtml for Razor Pages ~/Views/Shared/_Layout.cshtml for MVC projects

  • 若要使用现有的数据上下文, 请至少选择一个要重写的文件。To use your existing data context, select at least one file to override. 必须至少选择一个文件以添加数据上下文。You must select at least one file to add your data context.
    • 选择数据上下文类。Select your data context class.
    • 选择 添加Select Add.
  • 创建新的用户上下文, 并可能为标识创建自定义用户类:To create a new user context and possibly create a custom user class for Identity:
    • 选择 + 按钮以创建一个新数据上下文类Select the + button to create a new Data context class.
    • 选择 添加Select Add.

注意:如果要创建新的用户上下文, 则无需选择要重写的文件。Note: If you're creating a new user context, you don't have to select a file to override.

某些标识选项在区域/标识/IdentityHostingStartup中配置。Some Identity options are configured in Areas/Identity/IdentityHostingStartup.cs. 有关详细信息,请参阅IHostingStartupFor more information, see IHostingStartup.

不使用现有授权将标识基架到 MVC 项目Scaffold identity into an MVC project without existing authorization

运行标识 scaffolder:Run the Identity scaffolder:

  • 解决方案资源管理器,右键单击该项目 >添加 > 新基架项From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 从左窗格添加基架对话框中,选择标识 > 添加From the left pane of the Add Scaffold dialog, select Identity > ADD.
  • 在 "添加标识" 对话框中,选择所需的选项。In the ADD Identity dialog, select the options you want.
    • 选择现有的布局页, 否则将用错误的标记覆盖你的布局文件。Select your existing layout page, or your layout file will be overwritten with incorrect markup. 例如,对于 MVC 项目 Razor Pages ~/Views/Shared/_Layout.cshtml ~/Pages/Shared/_Layout.cshtmlFor example ~/Pages/Shared/_Layout.cshtml for Razor Pages ~/Views/Shared/_Layout.cshtml for MVC projects
    • 选择 + 按钮以创建一个新数据上下文类Select the + button to create a new Data context class.
  • 选择添加Select ADD.

可选:将登录名 partial (_LoginPartial)添加到Views/Shared/_Layout文件:Optional: Add the login partial (_LoginPartial) to the Views/Shared/_Layout.cshtml file:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - MvcNoAuth3</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MvcNoAuth3</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
                </ul>
                <partial name="_LoginPartial" />
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 - MvcNoAuth3</p>
        </footer>
    </div>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
        </script>
        <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
        </script>
        <script src="~/js/site.min.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>
  • Pages/shared/_LoginPartial文件移动到Views/shared/_LoginPartialMove the Pages/Shared/_LoginPartial.cshtml file to Views/Shared/_LoginPartial.cshtml

区域/标识/IdentityHostingStartup中配置标识。Identity is configured in Areas/Identity/IdentityHostingStartup.cs. 有关详细信息,请参阅 IHostingStartup。For more information, see IHostingStartup.

生成的标识数据库代码需要Entity Framework Core 迁移The generated Identity database code requires Entity Framework Core Migrations. 创建迁移并更新数据库。Create a migration and update the database. 例如,运行以下命令:For example, run the following commands:

在 Visual Studio程序包管理器控制台:In the Visual Studio Package Manager Console:

Add-Migration CreateIdentitySchema
Update-Database

Add-Migration命令的 "CreateIdentitySchema" 名称参数是任意的。The "CreateIdentitySchema" name parameter for the Add-Migration command is arbitrary. "CreateIdentitySchema"介绍迁移。"CreateIdentitySchema" describes the migration.

UseStaticFiles 后调用UseAuthenticationCall UseAuthentication after UseStaticFiles:

public class Startup
{

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseAuthentication();
        app.UseMvcWithDefaultRoute();
    }
}

UseHsts 建议但不是必需的。UseHsts is recommended but not required. 请参阅HTTP 严格传输安全性协议有关详细信息。See HTTP Strict Transport Security Protocol for more information.

使用授权将标识基架到 MVC 项目Scaffold identity into an MVC project with authorization

运行标识 scaffolder:Run the Identity scaffolder:

  • 解决方案资源管理器,右键单击该项目 >添加 > 新基架项From Solution Explorer, right-click on the project > Add > New Scaffolded Item.
  • 在 "添加基架" 对话框的左窗格中, 选择 "标识 > " "添加"。From the left pane of the Add Scaffold dialog, select Identity > Add.
  • 在 "添加标识" 对话框中, 选择所需的选项。In the Add Identity dialog, select the options you want.
    • 选择现有的布局页, 否则将用错误的标记覆盖你的布局文件。Select your existing layout page, or your layout file will be overwritten with incorrect markup. 如果选择了现有 _的布局 cshtml文件, 则会覆盖它。When an existing _Layout.cshtml file is selected, it is not overwritten.

例如: ~/Pages/Shared/_Layout.cshtml对于 MVC 项目~/Views/Shared/_Layout.cshtml Razor PagesFor example: ~/Pages/Shared/_Layout.cshtml for Razor Pages ~/Views/Shared/_Layout.cshtml for MVC projects

  • 若要使用现有的数据上下文, 请至少选择一个要重写的文件。To use your existing data context, select at least one file to override. 必须至少选择一个文件以添加数据上下文。You must select at least one file to add your data context.
    • 选择数据上下文类。Select your data context class.
    • 选择 添加Select Add.
  • 创建新的用户上下文, 并可能为标识创建自定义用户类:To create a new user context and possibly create a custom user class for Identity:
    • 选择 + 按钮以创建一个新数据上下文类Select the + button to create a new Data context class.
    • 选择 添加Select Add.

注意:如果要创建新的用户上下文, 则无需选择要重写的文件。Note: If you're creating a new user context, you don't have to select a file to override.

删除页面/共享文件夹和该文件夹中的文件。Delete the Pages/Shared folder and the files in that folder.

创建完全标识 UI 源Create full identity UI source

若要保持对标识 UI 的完全控制,请运行标识 scaffolder,并选择 "替代所有文件"。To maintain full control of the Identity UI, run the Identity scaffolder and select Override all files.

以下突出显示的代码显示默认标识 UI 替换标识在 ASP.NET Core 2.1 web 应用的更改。The following highlighted code shows the changes to replace the default Identity UI with Identity in an ASP.NET Core 2.1 web app. 你可能希望执行此操作以对标识 UI 具有完全控制。You might want to do this to have full control of the Identity UI.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<IdentityUser, IdentityRole>()
        // services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
        .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";
    });

    // using Microsoft.AspNetCore.Identity.UI.Services;
    services.AddSingleton<IEmailSender, EmailSender>();
}

在以下代码中,将替换默认标识:The default Identity is replaced in the following code:

services.AddIdentity<IdentityUser, IdentityRole>()
    // services.AddDefaultIdentity<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

下面的代码将设置LoginPathLogoutPathAccessDeniedPathThe following code sets the LoginPath, LogoutPath, and AccessDeniedPath:

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = $"/Identity/Account/Login";
    options.LogoutPath = $"/Identity/Account/Logout";
    options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});

注册 IEmailSender 实现,例如:Register an IEmailSender implementation, for example:

// using Microsoft.AspNetCore.Identity.UI.Services;
services.AddSingleton<IEmailSender, EmailSender>();
public class EmailSender : IEmailSender
{
    public Task SendEmailAsync(string email, string subject, string message)
    {
        return Task.CompletedTask;
    }
}

其他资源Additional resources