# 第6章：ASP.NET Core MVC

#### 任务40：介绍 <a href="#ren-wu-40-jie-shao" id="ren-wu-40-jie-shao"></a>

* 1.Individual authentication 模板
* 2.EF Core Migration
* 3.Identity MVC：UI
* 4.Identity MVC： EF + Identity实现
* 5.Identity MVC：注册逻辑实现
* 6.Identity MVC：登录逻辑实现
* 7.Identity MVC：ReturnUrl实现
* 8.Identity MVC：Model后端验证
* 9.Identity MVC：Model前端验证
* 10.Identity MVC：DbContextSeed初始化

#### 任务41：Individual authentication 模板 <a href="#ren-wu-41individualauthentication-mo-ban" id="ren-wu-41individualauthentication-mo-ban"></a>

```
dotnet new mvc --help
```

```
Options:
  -au|--auth                      The type of authentication to use
                                      None             - No authentication
                                      Individual       - Individual authentication
                                      IndividualB2C    - Individual authentication with Azure AD B2C
                                      SingleOrg        - Organizational authentication for a single tenant
                                      MultiOrg         - Organizational authentication for multiple tenants
                                      Windows          - Windows authentication
                                  Default: None
```

```
  -uld|--use-local-db             Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual or --auth IndividualB2C is specified.      
                                  bool - Optional
                                  Default: false / (*) true
```

解决VScode终端乱码

```
chcp 65001
```

```
dotnet new mvc -au Individual -uld --name IdentitySample
```

默认创建localdb，Identity

appsettings.json

```
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-IdentitySample-40453418-3C8F-43D7-94F8-BD1BD20BDD96;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
```

Startup.cs

```
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
```

初始化数据库，根据Data/Migrations文件夹下的数据库文件创建更新数据库

```
dotnet ef database update
```

报错：

```
无法执行，因为找不到指定的命令或文件。
可能的原因包括:
  *你拼错了内置的 dotnet 命令。
  *你打算执行 .NET Core 程序，但 dotnet-ef 不存在。
  *你打算运行全局工具，但在路径上找不到名称前缀为 dotnet 的可执行文件。
```

在stackoverflow找到解决方法：

<https://stackoverflow.com/questions/45091909/dotnet-ef-database-update-no-executable-found-matching-command-dotnet-ef?r=SearchResults>

在csproj文件的ItemGroup中添加引用

```
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0-preview2-final" />
```

```
dotnet restore
dotnet ef database update
dotnet run
```

访问<https://localhost:5001>

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FdWZFuC4Nk9PSNeQ4uu1O%2F068.png?alt=media\&token=a4309ec3-486d-4040-8580-95d87105a8ca)

**点击Register进入注册页面**

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FOaSejyN6lSH305Kl9cHP%2F069.png?alt=media\&token=cf631caa-af41-419c-9d9b-ebf9a5683ba8)

**输入邮箱密码登陆**

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FUFN0tHJkWy4LvODrDKDl%2F070.png?alt=media\&token=bdce4716-466d-426d-998f-86df6bcef942)

**登陆成功**

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FaaMveOyU6pDoRDCczdOT%2F071.jpg?alt=media\&token=11899d90-51b6-4373-bb11-bfca2b36a44e)

**点击邮箱进入Manage your account**

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2Fj5mNsXH7kRnkxSxmNbC0%2F072.jpg?alt=media\&token=d692221f-2dfc-4e8c-b0b0-1fe19ca128e4)

**通过SSMS连接localdb**

```
dotnet run
```

获取实例管道名称

```
& 'C:\Program Files\Microsoft SQL Server\130\Tools\Binn\SqlLocalDB.exe' info mssqllocaldb
```

解决PowerShell中文乱码问题，勾选UTF-8

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FNYDnrYvW9oA7Zt1SqPyw%2F073.png?alt=media\&token=10a399d9-b508-4b0d-8d45-b28e39b340fd)

通过实例管道名称连接localdb

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FGIDvJ0iludxCr8L84Df6%2F074.jpg?alt=media\&token=97b17084-d224-497a-91cc-199679e98244)

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FaPriblMiwnsqMVVwa6H6%2F075.jpg?alt=media\&token=8af64cdf-af1f-4817-8f1a-b0199a9a1535)

#### 任务42：EF Core Migration <a href="#ren-wu-42efcoremigration" id="ren-wu-42efcoremigration"></a>

```
dotnet ef migrations add InitialCreat

dotnet ef database update

dotnet ef migrations remove

dotnet ef database update LastGoodMigration

dotnet ef migrations scrept
```

**数据库新增**

添加列之前

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FMeBEXHfE9ONqErDXH5Sa%2F076.jpg?alt=media\&token=482ba26d-e49c-439f-b877-9fe71827629a)

在Models文件夹下新增ApplicationUser.cs

```
using System;
using Microsoft.AspNetCore.Identity;

namespace IdentitySample.Models
{
    public class ApplicationUser : IdentityUser
    {
        public string NewColumn{get;set;}
    }
}
```

```
dotnet ef migrations add AddNewColumn
```

自动生成文件

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FYrIxBtgYvuv5LlGOSPHj%2F077.jpg?alt=media\&token=d308c9ee-41f2-489b-8abe-33912771359e)

```
dotnet ef database update
```

执行成功后刷新数据库，可以看到数据库中多了一列NewColumn

在ApplicationUser.cs中新增Address

```
        public string Address{get;set;}
```

```
dotnet ef migrations add AddAddress
dotnet ef database update
```

执行成功后刷新数据库，可以看到数据库中多了一列Address

**数据库回滚**

```
dotnet ef database update AddNewColumn
```

执行成功后刷新数据库，可以看到数据库中Address不见了

```
dotnet ef migrations remove
```

执行成功后移除AddAddress.cs以及AddAddress.Designer.cs文件

**生成sql脚本命令**

```
dotnet ef migrations script
```

拷贝出来后可在数据库执行

#### 任务43：Identity MVC：UI <a href="#ren-wu-43identitymvcui" id="ren-wu-43identitymvcui"></a>

以MvcCookieAuthSample项目为基础，通过ef core以及Identity实现注册登陆UI整个过程

AccountController.cs新增Register，Login

```
        public IActionResult Register()
        {
            return View();
        }

        public IActionResult Login()
        {
            return View();
        }

        public IActionResult MakeLogin()
```

在Views文件夹下新建Account文件夹，在Account文件夹下新增Register.cshtml以及Login.cshtml

Register.cshtml

```
@{
    ViewData["Title"] = "Register";
}

@using MvcCookieAuthSample.ViewModels;
@model RegisterViewModel;

<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>

<p>Use this area to provide additional information.</p>

<div class="row">
    <div class="col-md-4">
        <form id="registerForm" method="post" novalidate="novalidate">
            <h4>Create a new account.</h4>
            <hr>
            

            <div class="form-group">
                <label asp-for="Input_Email">Email</label>
                <input asp-for="Input_Email" class="form-control" type="email">
                
            </div>
            <div class="form-group">
                <label asp-for="Input_Password">Password</label>
                <input asp-for="Input_Password" class="form-control" type="password">
                
            </div>
            <div class="form-group">
                <label asp-for="Input_ConfirmPassword">Confirm password</label>
                <input asp-for="Input_ConfirmPassword" class="form-control" type="password">
                
            </div>
            <button id="registerSubmit" type="submit" class="btn btn-primary">Register</button>
        <input name="__RequestVerificationToken" type="hidden" value="CfDJ8HHmKd6uCEtOsAkKHNEfx50wHX7kOnWmAzVSUOOnXiiks-t4chi5eY9XThPYt70X-X6qtCV55TTEowbXbnCAW-91KSw1XVqXqBd48bMdGuVeGHFeZU61gw9jtNtAUDP7gCYnN9J_9d6o5w9sL12jw1E"></form>
    </div>
    <div class="col-md-6 col-md-offset-2">
        <section>
            <h4>Use another service to register.</h4>
            <hr>
                    <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>
        </section>
    </div>
</div>
```

Login.cshtml

```
@{
    ViewData["Title"] = "Login";
}

@using MvcCookieAuthSample.ViewModels;
@model RegisterViewModel;

<div class="row">
    <div class="col-md-4">
        <section>
            <form id="account" method="post" novalidate="novalidate">
                <h4>Use a local account to log in.</h4>
                <hr>
                

                <div class="form-group">
                    <label asp-for="Input_Email">Email</label>
                    <input  asp-for="Input_Email" class="form-control" type="email">
                    
                </div>
                <div class="form-group">
                    <label asp-for="Input_Password">Password</label>
                    <input asp-for="Input_Password" class="form-control" type="password">
                    
                </div>
                
                <div class="form-group">
                    <button id="login-submit" type="submit" class="btn btn-primary">Log in</button>
                </div>
                
            <input name="__RequestVerificationToken" type="hidden" value="CfDJ8HHmKd6uCEtOsAkKHNEfx514_36YMa9FLgbR-vliay5DWvu05X4yejzvlNz6ULPfevJg9b12mlIjiWYP9ifLswnUdt43dzUmlvJzsanhL7RHmQMDAwrKRRTJWtaHJ4qbHUNyldkz95mHRrvivNTez9I"><input name="Input.RememberMe" type="hidden" value="false"></form>
        </section>
    </div>

</div>
```

新建ViewModels文件夹，在ViewModels文件夹下新建RegisterViewModel.cs

RegisterViewModel.cs

```
namespace MvcCookieAuthSample.ViewModels
{
    public class RegisterViewModel
    {
        public string Input_Email{get;set;}
        public string Input_Password{get;set;}
        public string Input_ConfirmPassword{get;set;}
    }
}
```

在Views/Shared目录下的\_Layout.cshtml中增加Register以及Login

```
            <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>
                <ul class="nav navbar-nav navbar-right">
                    <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
                    <li><a asp-area="" asp-controller="Account" asp-action="Login">Login</a></li>
                </ul>
            </div>
```

```
dotnet run
```

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FqwJrBTjBRp3J66vDZ5TY%2F078.jpg?alt=media\&token=c087e129-bb6b-4ac2-a49c-ba5c7950ae8f)

点击进入Register以及Login页面

#### 任务44：Identity MVC： EF + Identity实现 <a href="#ren-wu-44identitymvcefidentity-shi-xian" id="ren-wu-44identitymvcefidentity-shi-xian"></a>

在Models文件夹新增ApplicationUser.cs以及ApplicationUserRole.cs

ApplicationUser.cs

```
using Microsoft.AspNetCore.Identity;

namespace MvcCookieAuthSample.Models
{
    // 默认主键GUID，可通过泛型修改
    public class ApplicationUser : IdentityUser<int>
    {

    }
}
```

ApplicationUserRole.cs

```
using Microsoft.AspNetCore.Identity;

namespace MvcCookieAuthSample.Models
{
    // 默认主键GUID，可通过泛型修改
    public class ApplicationUserRole : IdentityRole<int>
    {

    }
}
```

新建Data文件夹，在Data文件夹下新建ApplicationDbContext.cs

```
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using MvcCookieAuthSample.Models;

namespace MvcCookieAuthSample.Data
{
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationUserRole, int>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {

        }
    }
}
```

在appsettings.json中添加ConnectionStrings

```
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-IdentitySample-81D77053-883E-44D8-A94D-195B9C54C2B6;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}
```

Startup.cs添加以下内容

```
using MvcCookieAuthSample.Data;
using Microsoft.EntityFrameworkCore;
using MvcCookieAuthSample.Models;
using Microsoft.AspNetCore.Identity;

        public void ConfigureServices(IServiceCollection services)
        {
            // services.Configure<CookiePolicyOptions>(options =>
            // {
            //     // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            //     options.CheckConsentNeeded = context => true;
            //     options.MinimumSameSitePolicy = SameSiteMode.None;
            // });

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

            services.AddIdentity<ApplicationUser, ApplicationUserRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            // Addmvc之前AddAuthentication，AddCookie
            // services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            // .AddCookie();
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = "/Account/Login";
                });

            services.Configure<IdentityOptions>(options =>
            {
                options.Password.RequireLowercase = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;
            });

            //services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddMvc();
        }
```

AccountController.cs添加以下内容

```
using MvcCookieAuthSample.ViewModels;
using Microsoft.AspNetCore.Identity;

    //[Authorize]
    public class AccountController : Controller
    
        private UserManager<ApplicationUser> _userManager;
        private SignInManager<ApplicationUser> _signInManager;

        public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
        {
            _userManager = userManager;
            _signInManager = signInManager;
        }
        
        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
        {
            var identityUser = new ApplicationUser
            {
                Email = registerViewModel.Input_Email,
                UserName = registerViewModel.Input_Email,
                NormalizedEmail = registerViewModel.Input_Email
            };

            var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Input_Password);
            if (identityResult.Succeeded)
            {
                return RedirectToAction("Index", "Home");
            }

            return View();
        }
```

添加nuget包：Microsoft.EntityFrameworkCore.Tools

VSCode报错：Versioning information could not be retrieved from the NuGet package repository. Please try again later.

使用Visual Studio添加nuget包

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2F1LTheicEQ3Oc2KSU1guX%2F079.jpg?alt=media\&token=98918236-290a-452d-a4c2-c0b847689e6e)

```
dotnet ef migrations add VSInit
dotnet ef database update
```

报错：There is already an object named 'AspNetRoles' in the database.

删除之前的数据库实例

```
dotnet ef migrations add VSInit
dotnet ef database update
```

主键为int

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2F1t41xmp31m4nexUml45H%2F080.jpg?alt=media\&token=db58c0fc-cd32-4c42-b2af-54d258afb853)

```
dotnet run
```

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FRYG25ESROI1wmySJlaIM%2F081.jpg?alt=media\&token=c002c498-5679-4c4c-b6ca-2418fb1fe0b8)

点击Register，成功跳回主页

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2Fj7jhg0zIQBlwHSNmw7rT%2F082.jpg?alt=media\&token=4652b553-dfaa-4763-84ee-c2acd3211883)

在数据库中查看数据

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FNtoyYrOteS0x5sopnjOm%2F083.jpg?alt=media\&token=ae5befb8-f736-4211-a2c4-e256965e0842)

#### 任务45：Identity MVC：注册逻辑实现 <a href="#ren-wu-45identitymvc-zhu-ce-luo-ji-shi-xian" id="ren-wu-45identitymvc-zhu-ce-luo-ji-shi-xian"></a>

AccountController.cs

```
        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
        {
            var identityUser = new ApplicationUser
            {
                Email = registerViewModel.Input_Email,
                UserName = registerViewModel.Input_Email,
                NormalizedEmail = registerViewModel.Input_Email
            };

            var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Input_Password);
            if (identityResult.Succeeded)
            {
                // 封装了下面MakeLogin()方法中的HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimIdentity));
                await _signInManager.SignInAsync(identityUser, new AuthenticationProperties {IsPersistent = true});

                return RedirectToAction("Index", "Home");
            }

            return View();
        }
```

启动项目，重新注册一个

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2F5fONm0ZMWd4zONJXc964%2F084.jpg?alt=media\&token=66fc5d25-fd7d-4afd-9eeb-8f224e89935f)

看到Cookie，登陆成功

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FDrkTVyfBwAAqqH6eJ6sg%2F085.jpg?alt=media\&token=fed5841e-2ad2-4d44-a088-24b32b93053e)

修改Views/Shared文件夹下的\_Layout.cshtml

```
            <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>
                <ul class="nav navbar-nav navbar-right">
                    @if (User.Identity.IsAuthenticated)
                    {
                        <li>Welcome, @User.Identity.Name, <a asp-area="" asp-controller="Account" asp-action="Logout">Logout</a></li>
                    }
                    else
                    {
                        <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
                        <li><a asp-area="" asp-controller="Account" asp-action="Login">Login</a></li>
                    }
                </ul>
            </div>
```

启动项目

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FvGF4EXBoAsw3lGawv1NJ%2F086.jpg?alt=media\&token=ab2f0c23-fd71-4df0-b739-fe819a9149e0)

#### 任务46：Identity MVC：登录逻辑实现 <a href="#ren-wu-46identitymvc-deng-lu-luo-ji-shi-xian" id="ren-wu-46identitymvc-deng-lu-luo-ji-shi-xian"></a>

AccountController.cs

```
        [HttpPost]
        public async Task<IActionResult> Login(RegisterViewModel loginViewModel)
        {
            var user = await _userManager.FindByEmailAsync(loginViewModel.Input_Email);
            if (user == null)
            {

            }

            await _signInManager.SignInAsync(user, new AuthenticationProperties {IsPersistent = true});
            return RedirectToAction("Index", "Home");
        }
        
        public async Task<IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }
        
        //public IActionResult Logout()
        //{
        //    HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            
        //    return Ok();
        //}
```

\_Layout.cshtml

```
            <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>
                @if (User.Identity.IsAuthenticated)
                {
                    <form asp-action="Logout" asp-controller="Account" method="post">
                        <ul class="nav navbar-nav navbar-right">
                            <li><a title="Welcome" asp-controller="Admin" asp-action="Index"> @User.Identity.Name, </a></li>
                            <li><button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button></li>
                        </ul>
                    </form>
                }
                else
                {
                    <ul class="nav navbar-nav navbar-right">
                        <li><a asp-area="" asp-controller="Account" asp-action="Register">Register</a></li>
                        <li><a asp-area="" asp-controller="Account" asp-action="Login">Login</a></li>
                    </ul>
                }
            </div>
```

Views/Account文件夹中的Login.cshtml

```
            <form id="account" method="post" asp-controller="Account" asp-action="Login" novalidate="novalidate">
```

启动项目

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FN3FWDikeDdOtWQpSQpp4%2F087.jpg?alt=media\&token=be49bbbf-4668-4f59-a8fe-97d02e80a62c)

点击Log out，回到主页

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FLK9ZIqnaEksQf7OgZzAg%2F088.jpg?alt=media\&token=baf0feb1-98c7-4fad-80a5-98ecd52f0923)

点击Login

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FDcZTN5YXHPV5YcUQ1bR0%2F089.jpg?alt=media\&token=438aa4bc-5ff4-478c-a482-c2977fe02ba1)

登陆成功

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2F9UxHVKtT2yQaavWgpnRT%2F090.jpg?alt=media\&token=953e26de-47b6-4d4d-83cb-b5dde6cb2585)

换另一个邮箱，登陆成功

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2Fle16Sa8V5wqaBA767pbL%2F091.jpg?alt=media\&token=6db1435c-759f-4fe7-bbe4-829756ab1ef8)

#### 任务47：Identity MVC：ReturnUrl实现 <a href="#ren-wu-47identitymvcreturnurl-shi-xian" id="ren-wu-47identitymvcreturnurl-shi-xian"></a>

AccountController.cs

```
        private IActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
                return Redirect(returnUrl);

            return RedirectToAction(nameof(HomeController.Index), "Home");
        }
        
        public IActionResult Register(string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            var identityUser = new ApplicationUser
            {
                Email = registerViewModel.Input_Email,
                UserName = registerViewModel.Input_Email,
                NormalizedEmail = registerViewModel.Input_Email
            };

            var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Input_Password);
            if (identityResult.Succeeded)
            {
                // 封装了下面MakeLogin()方法中的HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimIdentity));
                await _signInManager.SignInAsync(identityUser, new AuthenticationProperties {IsPersistent = true});

                //return RedirectToAction("Index", "Home");
                return RedirectToLocal(returnUrl);
            }

            return View();
        }
        
        public IActionResult Login(string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(RegisterViewModel loginViewModel, string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            var user = await _userManager.FindByEmailAsync(loginViewModel.Input_Email);
            if (user == null)
            {

            }

            await _signInManager.SignInAsync(user, new AuthenticationProperties {IsPersistent = true});
            //return RedirectToAction("Index", "Home");
            return RedirectToLocal(returnUrl);
        }
```

Register.cshtml

```
        <form id="registerForm" method="post" asp-route-returnUrl="@ViewData["ReturnUrl"]" novalidate="novalidate">
```

Login.cshtml

```
            <form id="account" method="post" asp-controller="Account" asp-action="Login" asp-route-returnUrl="@ViewData["ReturnUrl"]" novalidate="novalidate">
```

启动项目，访问：<https://localhost:44387/admin>

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2Fs5DWqy1WAD6KBb0swRpH%2F092.jpg?alt=media\&token=f758fc76-a852-42f9-b3e4-a7b341c45cf0)

点击Log out，再次访问：[https://localhost:44387/admin，跳转到登陆界面](https://localhost:44387/admin%EF%BC%8C%E8%B7%B3%E8%BD%AC%E5%88%B0%E7%99%BB%E9%99%86%E7%95%8C%E9%9D%A2)

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FYerdODMRmKuAH0CAnDUV%2F093.jpg?alt=media\&token=833c8e9c-e748-4845-8a27-eb0146f6f1fa)

登陆之后直接到admin页面

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FU9HTgZyVyjDu84QwBV92%2F094.jpg?alt=media\&token=912efe19-7401-4f3e-be34-934b7609a758)

#### 任务48：Identity MVC：Model后端验证 <a href="#ren-wu-48identitymvcmodel-hou-duan-yan-zheng" id="ren-wu-48identitymvcmodel-hou-duan-yan-zheng"></a>

RegisterViewModel.cs

```
using System.ComponentModel.DataAnnotations;

namespace MvcCookieAuthSample.ViewModels
{
    public class RegisterViewModel
    {
        [Required]
        [DataType(DataType.EmailAddress)]
        public string Input_Email{get;set;}

        [Required]
        [DataType(DataType.Password)]
        public string Input_Password{get;set;}

        [Required]
        [DataType(DataType.Password)]
        public string Input_ConfirmPassword{get;set;}
    }
}
```

在ViewModels文件夹下新增LoginViewModel.cs

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace MvcCookieAuthSample.ViewModels
{
    public class LoginViewModel
    {
        [Required]
        [DataType(DataType.EmailAddress)]
        public string Input_Email { get; set; }

        [Required]
        [DataType(DataType.Password)]
        public string Input_Password { get; set; }
    }
}
```

AccountController.cs第一个参数类型由RegisterViewModel修改为LoginViewModel

```
        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl = null)
        {
            if (ModelState.IsValid)
            {
                ViewData["ReturnUrl"] = returnUrl;
                var user = await _userManager.FindByEmailAsync(loginViewModel.Input_Email);
                if (user == null)
                {

                }

                await _signInManager.SignInAsync(user, new AuthenticationProperties { IsPersistent = true });
                //return RedirectToAction("Index", "Home");
                return RedirectToLocal(returnUrl);
            }

            return View();
        }
```

Login.cshtml

```
                <div class="form-group">
                    <label asp-for="Input_Email">Email</label>
                    <input  asp-for="Input_Email" class="form-control" type="email">
                    <span asp-validation-for="Input_Email" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Input_Password">Password</label>
                    <input asp-for="Input_Password" class="form-control" type="password">
                    <span asp-validation-for="Input_Password" class="text-danger"></span>
                </div>
```

启动项目，不输入邮箱密码直接点击登陆

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FXi3mcvCadaiAoyKH3h2U%2F095.jpg?alt=media\&token=61554650-5423-4d73-a4b1-c70c4db7c14c)

Register.cshtml

```
            <div class="form-group">
                <label asp-for="Input_Email">Email</label>
                <input asp-for="Input_Email" class="form-control" type="email">
                <span asp-validation-for="Input_Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input_Password">Password</label>
                <input asp-for="Input_Password" class="form-control" type="password">
                <span asp-validation-for="Input_Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input_ConfirmPassword">Confirm password</label>
                <input asp-for="Input_ConfirmPassword" class="form-control" type="password">
                <span asp-validation-for="Input_ConfirmPassword" class="text-danger"></span>
            </div>
```

AccountController.cs

```
        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
        {
            if (ModelState.IsValid)
            {
                ViewData["ReturnUrl"] = returnUrl;
                var identityUser = new ApplicationUser
                {
                    Email = registerViewModel.Input_Email,
                    UserName = registerViewModel.Input_Email,
                    NormalizedEmail = registerViewModel.Input_Email
                };

                var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Input_Password);
                if (identityResult.Succeeded)
                {
                    // 封装了下面MakeLogin()方法中的HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimIdentity));
                    await _signInManager.SignInAsync(identityUser, new AuthenticationProperties { IsPersistent = true });

                    //return RedirectToAction("Index", "Home");
                    return RedirectToLocal(returnUrl);
                }
            }

            return View();
        }
```

启动项目，直接点击注册

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FUdnqtk1GtARQmc5e06mQ%2F096.jpg?alt=media\&token=b318afce-e3af-436f-8468-30c52a1c9861)

Startup.cs

```
            services.Configure<IdentityOptions>(options =>
            {
                options.Password.RequireLowercase = true;
                options.Password.RequireNonAlphanumeric = true;
                options.Password.RequireUppercase = true;
                options.Password.RequiredLength = 12;
            });
```

Register.cshtml添加text-danger

```
            <h4>Create a new account.</h4>
            <hr>
            <div class="text-danger" asp-validation-summary="All"></div>
```

AccountController.cs

```
        private void AddErrors(IdentityResult result)
        {
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError(string.Empty, error.Description);
            }
        }
        
        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel registerViewModel, string returnUrl = null)
        {
            if (ModelState.IsValid)
            {
                ViewData["ReturnUrl"] = returnUrl;
                var identityUser = new ApplicationUser
                {
                    Email = registerViewModel.Input_Email,
                    UserName = registerViewModel.Input_Email,
                    NormalizedEmail = registerViewModel.Input_Email
                };

                var identityResult = await _userManager.CreateAsync(identityUser, registerViewModel.Input_Password);
                if (identityResult.Succeeded)
                {
                    // 封装了下面MakeLogin()方法中的HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,new ClaimsPrincipal(claimIdentity));
                    await _signInManager.SignInAsync(identityUser, new AuthenticationProperties { IsPersistent = true });

                    //return RedirectToAction("Index", "Home");
                    return RedirectToLocal(returnUrl);
                }
                else
                {
                    AddErrors(identityResult);
                }
            }

            return View();
        }
```

启动项目，随便输入密码123

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FZ5cJVRUQYjSVcT5alrrJ%2F097.jpg?alt=media\&token=3f7c600c-7ce0-4466-9572-e3bb9ae01f38)

点击注册

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FDh5z9vTGXO3BzT93Pkcs%2F098.jpg?alt=media\&token=be83bcf9-bd12-4385-8e71-80b75474f914)

#### 任务49：Identity MVC：Model前端验证 <a href="#ren-wu-49identitymvcmodel-qian-duan-yan-zheng" id="ren-wu-49identitymvcmodel-qian-duan-yan-zheng"></a>

将Shared文件夹中的\_ValidationScriptsPartial.cshtml的jquery.validate组件添加到Login.cshtml最下面以及Register.cshtml最下面

```
@section Scripts
{
    @await Html.PartialAsync("_ValidationScriptsPartial")
}
```

启动项目，直接点击登陆，注册，不会产生网络请求

#### 任务50：Identity MVC：DbContextSeed初始化 <a href="#ren-wu-50identitymvcdbcontextseed-chu-shi-hua" id="ren-wu-50identitymvcdbcontextseed-chu-shi-hua"></a>

启动的时候判断是否第一次执行，如果第一次执行则添加一个记录，比如用户账号第一次进来为管理员

在Data文件夹新增ApplicationDbContextSeed.cs

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using MvcCookieAuthSample.Models;
using Microsoft.Extensions.DependencyInjection;

namespace MvcCookieAuthSample.Data
{
    public class ApplicationDbContextSeed
    {
        private UserManager<ApplicationUser> _userManager;

        public async Task SeedSync(ApplicationDbContext context, IServiceProvider services)
        {
            if (!context.Users.Any())
            {
                _userManager = services.GetRequiredService<UserManager<ApplicationUser>>();

                var defaultUser = new ApplicationUser
                {
                    UserName = "Administrator",
                    Email = "mingsonzheng003@outlook.com",
                    NormalizedUserName = "admin"
                };

                var result = await _userManager.CreateAsync(defaultUser, "Password$123");
                if (!result.Succeeded)
                    throw new Exception("初始默认用户失败");
            }
        }
    }
}
```

在Data文件夹新增扩展方法调用ApplicationDbContextSeed

WebHostMigrationExtensions.cs

```
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using  Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace MvcCookieAuthSample.Data
{
    public static class WebHostMigrationExtensions
    {
        public static IWebHost MigrateDbContext<TContext>(this IWebHost host, Action<TContext, IServiceProvider> sedder)
            where TContext : DbContext
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                var logger = services.GetRequiredService<ILogger<TContext>>();
                var context = services.GetService<TContext>();

                try
                {
                    context.Database.Migrate();
                    sedder(context, services);

                    logger.LogInformation($"执行DBContext { typeof(TContext).Name } seed执行成功");
                }
                catch (Exception ex)
                {
                    logger.LogInformation($"执行DBContext { typeof(TContext).Name } seed执行失败");
                }
            }

            return host;
        }
    }
}
```

Program.cs

```
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MvcCookieAuthSample.Data;

namespace MvcCookieAuthSample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build()
                .MigrateDbContext<ApplicationDbContext>((context, services) =>
                {
                    new ApplicationDbContextSeed().SeedSync(context, services)
                        .Wait();
                })
                .Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}
```

删除数据库

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2Fy8szsAasDXAWr6KsjF7s%2F099.jpg?alt=media\&token=2d761e0c-2efe-43a8-b373-04aa23c16425)

控制台方式启动项目，先进行数据库初始化，再启动WebHost

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FhIF5GxHP76HSgVcd18an%2F100.jpg?alt=media\&token=a00ad005-79d6-46fb-9872-6f94dac4412c)

数据库自动插入数据

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FWYlgeKzKYlvsibj0Qegx%2F101.jpg?alt=media\&token=237b5c5d-bcbb-43f5-9c5f-02c72ef0f100)

输入邮箱，密码：Password$123

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FVQ6KJXLX84taq7pizZTp%2F102.jpg?alt=media\&token=1c061a59-46e6-4be7-87fe-2e35fcc06087)

登陆

![](https://256343630-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Flyk5lHXG8Cnqf7q1KuaO%2Fuploads%2FYCEachfPnXGYtkbuEpe4%2F103.jpg?alt=media\&token=ee1bba36-551a-4cbe-88d6-4d9db028f596)

end
