1. 程式人生 > >asp.net core identity學習1

asp.net core identity學習1

ASP.NET Identity 學習

  1. 建立一個Asp.net core mvc專案
    新增Nuget包:

    • Microsoft.EntityFrameworkCore.SqlServer 3.1.3
    • Microsoft.EntityFrameworkCore.Tools 3.1.3
    • Microsoft.AspNetCore.Identity.EntityFrameworkCore 3.1.3
  2. 更改HomeController類的內容

public IActionResult Index()
      {
         return View(new Dictionary<string, object> { ["Placeholder"]="Placeholder"});
      }

更改HomeController的Index檢視內容:

@{
    ViewData["Title"] = "首頁";
}
@model Dictionary<string, object>
<div class="bg-primary m-1 p-1 text-white"><h4>使用者詳情</h4></div>
<table class="table table-sm table-bordered m-1 p-1">
    @foreach (var kvp in Model)
    {
        <tr><th>@kvp.Key</th><td>@kvp.Value</td></tr>
    }
</table>
  1. 在Models資料夾下建立AppUser類,繼承自IdentityUser
using Microsoft.AspNetCore.Identity;

namespace IdentityDemo.Models
{
    public class AppUser:IdentityUser
    {
    }
}

  1. 在Model資料夾下建立DbContext:AppIdentityDbContext
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace IdentityDemo.Models
{
    public class AppIdentityDbContext:IdentityDbContext<AppUser>
    {
        public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options):base(options)
        {

        }
    }
}

  1. 配置資料庫連線
    在appsettings.json中配置資料庫連線
{
  "Data": {
    "IdentityDemo": {
      "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=IdentityDemo;Trusted_Connection=True;MultipleActiveResultSets=true"
    },
    "Logging": {
      "LogLevel": {
        "Default": "Information",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    "AllowedHosts": "*"
  }

  1. 在Startup.cs中讀取配置檔案中的資料庫連結地址
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityDemo.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

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

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //配置資料庫連線
            services.AddDbContext<AppIdentityDbContext>(
                options => options.UseSqlServer(Configuration["Data:IdentityDemo:ConnectionString"])
                );

            //配置Identity
            services.AddIdentity<AppUser, IdentityRole>()
                .AddEntityFrameworkStores<AppIdentityDbContext>()
                .AddDefaultTokenProviders();

            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseStatusCodePages();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

  1. 新增資料庫遷移
add-migrations InitialCreate  
update database
  1. 列舉使用者賬戶
using IdentityDemo.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace IdentityDemo.Controllers
{
    public class AdminController : Controller
    {
        private UserManager<AppUser> userManager;
        //建構函式注入,獲取UserManager
        public AdminController(UserManager<AppUser> usrMgr)
        {
            userManager = usrMgr;
        }
        public IActionResult Index()
        {
            return View(userManager.Users);
        }
    }
}

檢視修改 Index.cshtml:

@model IEnumerable<AppUser>

@{
    ViewData["Title"] = "Index";
}
<div class="bg-primary m-1 p-1 text-white"><h4>使用者賬號</h4></div>
<table class="table table-sm table-bordered">
    <tr><th>ID</th><th>使用者名稱</th><th>郵箱</th></tr>
    @if (Model.Count() == 0)
    {
        <tr><td colspan="3" class="text-center">沒有</td></tr>
    }
    else
    {
        foreach (AppUser user in Model)
        {
            <tr>
                <td>@user.Id</td>
                <td>@user.UserName</td>
                <td>@user.Email</td>
            </tr>
        }
    }
</table>
<a class="btn btn-primary" asp-action="Create">建立</a>

  1. 建立使用者
    1). 建立使用者檢視模型 UserViewModels
public class CreateModel
    {
        [Required]
        [Display(Name = "使用者名稱")]
        public string Name { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "電子郵箱")]
        public string Email { get; set; }
        
        [Required]
        [DataType(DataType.Password)]
        [Display(Name="密碼")]
        public string Password { get; set; }
    }
  1. 新增Action方法
    在AdminController中新增Create方法:
/// <summary>
/// 建立使用者
/// </summary>
/// <returns></returns>
public ViewResult Create() => View();

[HttpPost]
public async Task<IActionResult> Create(CreateModel model)
{
   if (ModelState.IsValid)
   {
         AppUser user = new AppUser
         {
            UserName = model.Name,
            Email = model.Email
         };
         ///建立使用者
         IdentityResult result = await userManager.CreateAsync(user, model.Password);
         if (result.Succeeded) //成功則返回列表頁
         {
            return RedirectToAction("Index");
         }
         else
         {
            foreach (IdentityError error in result.Errors)
            {
               ModelState.AddModelError("", error.Description);
            }
         }
   }
   return View(model);
}
  1. 建立使用者測試
  1. ctrl+F5執行程式,訪問 "https://localhost:44382/Admin/Create" ,填寫使用者名稱Joe,郵箱:[email protected],密碼:Secret123$。點選"建立",建立使用者成功。
  2. 再次輸入相同的使用者名稱,提示“User name 'Joe' is already taken.”
  3. 更改第一步中的使用者名稱為Alice,密碼為secret,建立使用者時會提示密碼強度不夠的資訊。因為Asp.net內建密碼驗證規則。
  1. 更改密碼驗證規則
    在Startup類的ConfigureServices方法中,配置密碼驗證規則:
public void ConfigureServices(IServiceCollection services)
{
   //配置資料庫連線
   services.AddDbContext<AppIdentityDbContext>(
         options => options.UseSqlServer(Configuration["Data:IdentityDemo:ConnectionString"])
         );

   //配置Identity的密碼驗證規則
   //規則為至少六位
   services.AddIdentity<AppUser, IdentityRole>(opts =>
   {
         opts.Password.RequiredLength = 6;
         opts.Password.RequireNonAlphanumeric = false;
         opts.Password.RequireLowercase = false;
         opts.Password.RequireUppercase = false;
         opts.Password.RequireDigit = false;
   }).AddEntityFrameworkStores<AppIdentityDbContext>()
         .AddDefaultTokenProviders();

   services.AddControllersWithViews();
}
  1. 自定義密碼驗證器類
    自定義密碼驗證規則除了上面一種方法,還可以自定義類,實現IPasswordValidator介面或者繼承自UserValidator,介面定義:
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Identity {
public interface IPasswordValidator<TUser> where TUser : class {
   Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
      TUser user, string password);
   }
}

CustomPasswordValidator類:

using Microsoft.AspNetCore.Identity;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityDemo.Models;

namespace IdentityDemo.Infrastructure
{
    /// <summary>
    /// 自定義使用者密碼驗證規則
    /// </summary>
    public class CustomPasswordValidator : UserValidator<AppUser>
    {
        public override async Task<IdentityResult> ValidateAsync(UserManager<AppUser> manager, AppUser user)
        {
            IdentityResult result = await base.ValidateAsync(manager, user);
            List<IdentityError> errors = result.Succeeded ? new List<IdentityError>() : result.Errors.ToList();
            if (!user.Email.ToLower().EndsWith("@example.com"))
            {
                errors.Add(new IdentityError
                {
                    Code = "EmailIdentityError",
                    Description = "只允許example.com的郵箱地址註冊賬號"
                });
            }
            return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
        }
    }
}

在Startup類的ConfigureServices中注入服務:

public void ConfigureServices(IServiceCollection services)
{
   //自定義密碼驗證服務
   services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordValidator>();
   
   //配置資料庫連線
   services.AddDbContext<AppIdentityDbContext>(
         options => options.UseSqlServer(Configuration["Data:IdentityDemo:ConnectionString"])
         );

   //配置Identity
   services.AddIdentity<AppUser, IdentityRole>(opts =>
   {
         opts.Password.RequiredLength = 6;
         opts.Password.RequireNonAlphanumeric = false;
         opts.Password.RequireLowercase = false;
         opts.Password.RequireUppercase = false;
         opts.Password.RequireDigit = false;
   }).AddEntityFrameworkStores<AppIdentityDbContext>()
         .AddDefaultTokenProviders();

   services.AddControllersWithViews();
}
  1. 使用者名稱驗證碼規則
    使用者名稱驗證規則有兩種:通過配置和自定義驗證類。

1). 通過配置,在Startup類的ConfigureServices方法中配置

//配置Identity
services.AddIdentity<AppUser, IdentityRole>(opts =>
{
      opts.User.RequireUniqueEmail = true;
      opts.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz";
      opts.Password.RequiredLength = 6;
      opts.Password.RequireNonAlphanumeric = false;
      opts.Password.RequireLowercase = false;
      opts.Password.RequireUppercase = false;
      opts.Password.RequireDigit = false;
}).AddEntityFrameworkStores<AppIdentityDbContext>()
      .AddDefaultTokenProviders();

2). 自定義驗證規則類,實現IUserValidator介面

using Microsoft.AspNetCore.Identity;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityDemo.Models;

namespace IdentityDemo.Infrastructure
{
    /// <summary>
    /// 自定義使用者名稱或者郵箱驗證規則
    /// </summary>
    public class CustomUserValidator : UserValidator<AppUser>
    {
        public override async Task<IdentityResult> ValidateAsync(UserManager<AppUser> manager, AppUser user)
        {
            IdentityResult result = await base.ValidateAsync(manager, user);
            List<IdentityError> errors = result.Succeeded ? new List<IdentityError>() : result.Errors.ToList();
            if (!user.Email.ToLower().EndsWith("@example.com"))
            {
                errors.Add(new IdentityError
                {
                    Code = "EmailIdentityError",
                    Description = "只允許example.com的郵箱地址註冊賬號"
                });
            }
            return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
        }
    }
}

在Startup類中,配置依賴注入:

services.AddTransient<IUserValidator<AppUser>,CustomUserValidator>();
  1. 編輯、刪除使用者功能
    1). 在使用者列表頁,新增編輯、刪除按鈕:Index.cshtml:
@model IEnumerable<AppUser>

@{
    ViewData["Title"] = "Index";
}
<div class="bg-primary m-1 p-1 text-white"><h4>使用者賬號</h4></div>
<table class="table table-sm table-bordered">
    <tr><th>ID</th><th>使用者名稱</th><th>郵箱</th><th>操作</th></tr>
    @if (Model.Count() == 0)
    {
        <tr><td colspan="3" class="text-center">沒有</td></tr>
    }
    else
    {
        foreach (AppUser user in Model)
        {
            <tr>
                <td>@user.Id</td>
                <td>@user.UserName</td>
                <td>@user.Email</td>
                <td>
                    <form asp-action="Delete" asp-route-id="@user.Id" method="post">
                        <a class="btn btn-sm btn-primary" asp-action="Edit"
                           asp-route-id="@user.Id">編輯</a>
                        <button type="submit"
                                class="btn btn-sm btn-danger">
                            刪除
                        </button>
                    </form>
                </td>
            </tr>
        }
    }
</table>
<a class="btn btn-primary" asp-action="Create">建立</a>

2). 刪除使用者

//刪除使用者
[HttpPost]
public async Task<IActionResult> Delete(string id)
{
   AppUser user = await userManager.FindByIdAsync(id);
   if (user != null)
   {
         IdentityResult result = await userManager.DeleteAsync(user);
         if (result.Succeeded)
         {
            return RedirectToAction("Index");
         }
         else
         {
            AddErrorsFromResult(result);
         }
   }
   else
   {
         ModelState.AddModelError("", "User Not Found");
   }
   return View("Index", userManager.Users);
}

private void AddErrorsFromResult(IdentityResult result)
{
   foreach (IdentityError error in result.Errors)
   {
         ModelState.AddModelError("", error.Description);
   }
}

3). 編輯使用者

/// <summary>
/// 編輯使用者
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<IActionResult> Edit(string id)
{
   AppUser user = await userManager.FindByIdAsync(id);
   if (user != null)
   {
         return View(user);
   }
   else
   {
         return RedirectToAction("Index");
   }
}

/// <summary>
/// 編輯使用者
/// </summary>
/// <param name="id"></param>
/// <param name="email"></param>
/// <param name="password"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> Edit(string id, string email,
string password)
{
   AppUser user = await userManager.FindByIdAsync(id);
   if (user != null)
   {
         user.Email = email;
         //使用者郵箱校驗
         IdentityResult validEmail = await userValidator.ValidateAsync(userManager, user);
         if (!validEmail.Succeeded)
         {
            AddErrorsFromResult(validEmail);
         }
         IdentityResult validPass = null;
         if (!string.IsNullOrEmpty(password))
         {
            //使用者密碼校驗
            validPass = await passwordValidator.ValidateAsync(userManager, user, password);
            if (validPass.Succeeded)
            {
               //使用者密碼加密
               user.PasswordHash = passwordHasher.HashPassword(user, password);
            }
            else
            {
               AddErrorsFromResult(validPass);
            }
         }
         //1. 只修改了郵箱,2. 修改了郵箱和密碼
         if ((validEmail.Succeeded && validPass == null) || (validEmail.Succeeded && password != string.Empty && validPass.Succeeded))
         {
            IdentityResult result = await userManager.UpdateAsync(user); //更新使用者資訊s
            if (result.Succeeded)
            {
               return RedirectToAction("Index");
            }
            else
            {
               AddErrorsFromResult(result);
            }
         }
   }
   else
   {
         ModelState.AddModelError("", "User Not Found");
   }
   return View(user);
}

編輯使用者的檢視:
Edit.cshtml:

@model AppUser

@{
    ViewData["Title"] = "修改使用者資訊";
}

<div class="bg-primary m-1 p-1"><h4>修改使用者資訊</h4></div>
<div asp-validation-summary="All" class="text-danger"></div>
<form asp-action="Edit" method="post">
    <div class="form-group">
        <label asp-for="Id"></label>
        <input asp-for="Id" class="form-control" disabled />
    </div>
    <div class="form-group">
        <label asp-for="Email"></label>
        <input asp-for="Email" class="form-control" />
    </div>
    <div class="form-group">
        <label for="password">Password</label>
        <input name="password" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">儲存</button>
    <a asp-action="Index" class="btn btn-secondary">取消</a>
</form>