EddyTeng
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- tags : .NET --- <style> .red{ background-color:#fff1f0; border-color:#ffa39e; color:#cf1322; padding:4px; font-size:1.75rem; } .blue{ background-color:#e6f7ff; border-color:#91d5ff; color:#096dd9; padding:4px; width:150px; text-align:center; } .green{ background-color:#f6ffed; border-color:#b7eb8f; color:#389e0d; padding:4px; width:150px; text-align:center; } .yellow{ background-color:#fffbe6; border-color:#ffe58f; color:#d48806; padding:4px; width:150px; text-align:center; } .orange{ background-color:#fffbe6; border-color:#ffd591; color:#d46b08; padding:4px; width:150px; text-align:center; } </style> # 7. Identity ### 7-1 新增Identity套件 ***在開始本章節操作之前,需先確認使用的套件版本皆為 .Net 6的最新版*** 1. AspDotNetDemo點擊滑鼠右鍵 -> 管理 NutGet 套件 -> 更新,將所有套件更新到6的最新版(須注意不要更新到 7 以上,那是 .Net 7使用的版本,在這邊更新會導致專案報錯) ![](https://i.imgur.com/JWcRChf.png) AspDotNetDemo.DataAccess點擊滑鼠右鍵 -> 管理 NutGet 套件 -> 更新,將所有套件更新到6的最新版(須注意不要更新到 7 以上,那是 .Net 7使用的版本,在這邊更新會導致專案報錯) ![](https://i.imgur.com/g19jzDn.png) ****** 2. AspDotNetDemo.DataAccess上點擊滑鼠右鍵 -> 管理NuGet套件 -> 選擇瀏覽分頁 -> 搜尋欄輸入 Microsoft.AspNetCore.Identity.EntityFrameworkCore -> 選擇版本後點選安裝(版本須跟上面一樣,選擇安裝最新版) ![](https://i.imgur.com/uhLqX7o.png) 點選I Accept ![](https://i.imgur.com/QtS8tzd.png) 3. 開啟AspDotNetDemo.DataAccess/Data/ApplicationDbContext.cs,修改程式碼 ```C#= using AspDotNetDemo.Models; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace AspDotNetDemo.DataAccess { public class ApplicationDbContext : IdentityDbContext //本次修改程式碼 { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Category> Categories { get; set; } public DbSet<CoverType> CoverTypes { get; set; } public DbSet<Product> Products { get; set; } } } ``` 修改完後如果發現第7行有錯誤提示,一樣將滑鼠移至紅底線部分,出現燈泡後點選```using Microsoft.AspNetCore.Identity.EntityFrameworkCore;```,錯誤提示就會消失。 4. 接著點擊AspDotNetDemo滑鼠右鍵 -> 加入 -> 新增Scaffold 項目 點擊識別 -> 加入 ![](https://i.imgur.com/TcjdiMD.png) 勾選覆寫所有檔案 -> 資料內容類別選擇 ApplicationDbContext (AspDotNetDemo.DataAccess) -> 新增 ![](https://i.imgur.com/buET7D1.png) 完成後會出現以下畫面 ![](https://i.imgur.com/N7zoFLU.png) Areas 中也會多一個Identity資料夾 ![](https://i.imgur.com/oMtu8NE.png) 可以在AspDotNetDemo點擊滑鼠右鍵 -> 重建,如果建置成功,就代表到目前都沒有問題! 接著開啟AspDotNetDemo/Program.cs,修改部分程式碼 ```C#= using AspDotNetDemo.DataAccess; using AspDotNetDemo.DataAccess.Repository; using AspDotNetDemo.DataAccess.Repository.IRepository; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Identity; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("DefaultConnection") )); //本次修改部分 builder.Services.AddDefaultIdentity<IdentityUser>() .AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddScoped<IUnitOfWork, UnitOfWork>(); builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { 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.UseAuthentication(); ; app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{area=Customer}/{controller=Home}/{action=Index}/{id?}"); app.Run(); ``` 完成後打開AspDotNetDemo/Views/Shared/_LoginPartial.cshtml,將```text-dark```的部分刪除,完成後如下 ```html= @using Microsoft.AspNetCore.Identity @inject SignInManager<IdentityUser> SignInManager @inject UserManager<IdentityUser> UserManager <ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { <li class="nav-item"> <a id="manage" class="nav-link" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a> </li> <li class="nav-item"> <form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })"> <button id="logout" type="submit" class="nav-link btn btn-link">Logout</button> </form> </li> } else { <li class="nav-item"> <a class="nav-link" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a> </li> <li class="nav-item"> <a class="nav-link" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a> </li> } </ul> ``` 接下來要讓首頁的右上方出現註冊及登入的按鈕 開啟AspDotNetDemo/Views/Shared/_Layout.cshtml ```html= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - AspDotNetDemo</title> @*<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />*@ <link rel="stylesheet" href="~/css/bootswatchTheme.css" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/AspDotNetDemo.styles.css" asp-append-version="true" /> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css"/> <link rel="stylesheet" href="//cdn.datatables.net/1.13.1/css/jquery.dataTables.min.css" /> </head> <body> <header> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div class="container-fluid"> <a class="navbar-brand" href="#">Double Drinks</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor02" aria-controls="navbarColor02" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarColor02"> <ul class="navbar-nav me-auto"> <li class="nav-item"> <a class="nav-link" asp-area="Customer" asp-controller="Home" asp-action="Index">首頁</a> </li> <li class="nav-item"> <a class="nav-link" asp-area="Admin" asp-controller="Category" asp-action="Index">類別</a> </li> <li class="nav-item"> <a class="nav-link" asp-area="Admin" asp-controller="Product" asp-action="Index">產品</a> </li> @*<li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a> <div class="dropdown-menu"> <a class="dropdown-item" href="#">Action</a> <a class="dropdown-item" href="#">Another action</a> <a class="dropdown-item" href="#">Something else here</a> <div class="dropdown-divider"></div> <a class="dropdown-item" href="#">Separated link</a> </div> </li>*@ </ul> @*本次修改部分*@ <partial name="_LoginPartial" /> @*<form class="d-flex"> <input class="form-control me-sm-2" type="text" placeholder="Search"> <button class="btn btn-secondary my-2 my-sm-0" type="submit">Search</button> </form>*@ </div> </div> </nav> </header> <div class="container content"> <main role="main" class="py-3"> @RenderBody() </main> </div> <footer class="footer text-white bg-primary p-1"> <div class="container text-center"> &copy; 2022 - AspDotNetDemo - <a asp-area="" asp-controller="Home" asp-action="Privacy" class="text-warning">Privacy</a> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> @*<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>*@ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script> <script src="~/js/site.js" asp-append-version="true"></script> <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="//cdn.datatables.net/1.13.1/js/jquery.dataTables.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html> ``` 完成後執行應用程式會發現他卡住了,並會出現以下的錯誤訊息 ![](https://i.imgur.com/Pysf3gC.png) 會有這個問題是因為 .Net Core在建立Identity時,會建立一個新的ApplicationDbContext,導致在執行時會產生錯誤,只需要將其註解或刪除就可以解決問題。 開啟AspDotNetDemo/Areas/Identity/Data/ApplicationDbContext.cs,並將其註解 ![](https://i.imgur.com/cKNcaJg.png) 完成後就可以再次執行應用程式囉,這次就不會有錯誤訊息了,並且首頁的右上方有出現註冊及登入的按鈕,就代表到目前為止都沒問題。 ![](https://i.imgur.com/6ok88lx.png) 接著點擊註冊按鈕,會發現他找不到該網頁 ![](https://i.imgur.com/Rc7kjBv.png) 開啟AspDotNetDemo/Program.cs,這裡需要新增一行程式碼 ```C#= using AspDotNetDemo.DataAccess; using AspDotNetDemo.DataAccess.Repository; using AspDotNetDemo.DataAccess.Repository.IRepository; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Identity; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("DefaultConnection") )); builder.Services.AddDefaultIdentity<IdentityUser>() .AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddScoped<IUnitOfWork, UnitOfWork>(); builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { 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.UseAuthentication(); ; app.UseAuthorization(); //本次新增部分 app.MapRazorPages(); app.MapControllerRoute( name: "default", pattern: "{area=Customer}/{controller=Home}/{action=Index}/{id?}"); app.Run(); ``` 完成後就可以打開註冊頁面囉。 ![](https://i.imgur.com/3lOWs1p.png) ### 7-2 建立Identity Table 因為我們使用了Idenity這個套件,裡面具有註冊用戶跟所有用戶的詳細資訊。裡面還具有身分所需的其他規則跟表格,一切都是預設完成的,因此會節省很多開發時間。 1. 上方工具列點選工具 -> NuGet 套件管理員 -> 套件管理主控台,主控台出現後記得更改預設專案,改為AspDotNetDemo.DataAccess,完成後輸入指令 ``` add-migration AddIdentityToDb ``` 接著更新資料庫,完成後就會產生資料表了。 ``` update-database ``` ![](https://i.imgur.com/yVtl2u4.png) 2. 自定義User資料 ![](https://i.imgur.com/qRn8zq8.png) 在Program.cs中可以找到,Identity有預設的使用者資料表,但是他並沒有包含太多的屬性,因此我們想擴展我們的使用者資料並為我們的網站創建一個具有更多屬性的使用者Model,像是使用者名稱、地址、城市等等。 AspDotNetDemo.Models點擊滑鼠右鍵 -> 加入 -> 類別,將其命名為ApplicationUser後新增 ![](https://i.imgur.com/OcJBEhp.png) 將剛建立的ApplicationUser修改為下方程式碼 ```C#= using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.Models { public class ApplicationUser : IdentityUser { } } ``` 修改完後會發現有錯誤提示,將滑鼠移至紅底線部分,出現燈泡後點選安裝套件 'Microsoft.Extensions.Identity.Stores' -> 選擇使用本機版本,完成後就會看到錯誤提示消失了 ![](https://i.imgur.com/GprW3NN.png) 接著再加上欄位型態與名稱 ```C#= using Microsoft.AspNetCore.Identity; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.Models { public class ApplicationUser : IdentityUser { [Required] public string Name { get; set; } public string? StreetAddress { get; set; } public string? City { get; set; } public string? State { get; set; } public string? PostalCode { get; set; } } } ``` * 在string後面加上?是因為要讓欄位變成非必填 * 第14行出現紅色底線時,點擊該行出現的燈泡,並引入套件即可 3. 接著開啟AspDotNetDemo.DataAccess/Data/ApplicationDbContext.cs,新增一行程式碼 ```C# using AspDotNetDemo.Models; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace AspDotNetDemo.DataAccess { public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Category> Categories { get; set; } public DbSet<CoverType> CoverTypes { get; set; } public DbSet<Product> Products { get; set; } public DbSet<ApplicationUser> ApplicationUsers { get; set; } //本次新增部分 } } ``` 完成後就要下指令新增Migration,打開套件管理主控台,輸入下方指令(確認預設專案須為AspDotNetDemo.DataAccess) ``` add-migration extendIdentityUser ``` 接著更新資料庫 ``` update-database ``` 完成後去資料庫打開AspNetUser資料表,就可以看到剛剛新增的欄位出現在資料表上了 ![](https://i.imgur.com/MxdSaXV.png) 4. 測試註冊功能 接著我們可以到我們的註冊頁面測試我們的註冊功能。 ![](https://i.imgur.com/fUaddKn.png) 註冊完成後,就可以看到使用者被加進我們的使用者資料表中了,可以觀察到這邊有一個Discriminator欄位,.net core 非常聰明,在這邊幫我們新增了一個判斷的欄位,他可以判斷我們新增的是甚麼身分的用戶。 ![](https://i.imgur.com/G55SRrR.png) ### 7-3 建立Company CRUD 因為我們的網站中,會有使用者、公司、跟管理者等不同角色,因此,我們在這邊想要創建一個公司的資料表,裡面包含了Id、Name、StreetAddress等不同的欄位,當然也會使用到Repository、UnitOfWork的部分,這個小節將會帶大家創建公司的資料。 #### 7-3-1 建立 Company Model AspDotNetDemo.Models點擊滑鼠右鍵 -> 加入 -> 類別,將其命名為Company後新增 ![](https://i.imgur.com/OcJBEhp.png) 將剛建立的ApplicationUser修改為下方程式碼 ```C#= using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.Models { public class Company { public int Id { get; set; } [Required] public string Name { get; set; } public string? StreetAddress { get; set; } public string? City { get; set; } public string? State { get; set; } public string? PostalCode { get; set; } public string? PhoneNumber { get; set; } } } ``` * Id是我們的主鍵,Name 為必填欄位 #### 7-3-2 修改 ApplicationDbContext 開啟AspDotNetDemo.DataAccess/Data/ApplicationDbContext.cs,新增一行程式碼 ```C# using AspDotNetDemo.Models; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace AspDotNetDemo.DataAccess { public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Category> Categories { get; set; } public DbSet<CoverType> CoverTypes { get; set; } public DbSet<Product> Products { get; set; } public DbSet<ApplicationUser> ApplicationUsers { get; set; } public DbSet<Company> Companies { get; set; } //本次新增部分 } } ``` 完成後就要下指令新增Migration,打開套件管理主控台,輸入下方指令(確認預設專案須為AspDotNetDemo.DataAccess) ``` add-migration addCompanyToDb ``` 接著更新資料庫 ``` update-database ``` #### 7-3-3 建立 Company 的 Repository 以及 IRepository 在AspDotNetDemo.DataAccess/Repository/IRepository新增檔案(資料夾點擊右鍵 -> 加入 -> 類別),將其命名為ICompanyRepository.cs 然後將ICompanyRepository.cs的程式碼修改 ```C#= using AspDotNetDemo.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.DataAccess.Repository.IRepository { public interface ICompanyRepository : IRepository<Company> { void Update(Company obj); } } ``` 在AspDotNetDemo.DataAccess/Repository下新增檔案(資料夾點擊右鍵 -> 加入 -> 類別),將其命名為CompanyRepository.cs 然後將CompanyRepository.cs的程式碼修改 ```C#= using AspDotNetDemo.DataAccess.Repository.IRepository; using AspDotNetDemo.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.DataAccess.Repository { public class CompanyRepository : Repository<Company>, ICompanyRepository { private ApplicationDbContext _db; public CompanyRepository(ApplicationDbContext db) : base(db) { _db = db; } public void Update(Company obj) { _db.Companies.Update(obj); } } } ``` #### 7-3-4 建立 Company 的 UnitOfWork 開啟AspDotNetDemo.DataAccess/Repository/IRepository下的IUnitOfWork.cs,新增程式碼 ```C#= using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.DataAccess.Repository.IRepository { public interface IUnitOfWork { ICategoryRepository Category { get; } ICoverTypeRepository CoverType { get; } IProductRepository Product { get; } ICompanyRepository Company { get; } //本次新增程式碼 void Save(); } } ``` 開啟AspDotNetDemo.DataAccess/Repository下的UnitOfWork.cs,新增程式碼 ```C#= using AspDotNetDemo.DataAccess.Repository.IRepository; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.DataAccess.Repository { public class UnitOfWork : IUnitOfWork { private ApplicationDbContext _db; public UnitOfWork(ApplicationDbContext db) { _db = db; Category = new CategoryRepository(_db); CoverType = new CoverTypeRepository(_db); Product = new ProductRepository(_db); Company = new CompanyRepository(_db); //本次新增程式碼 } public ICategoryRepository Category { get; private set; } public ICoverTypeRepository CoverType { get; private set; } public IProductRepository Product { get; private set; } public ICompanyRepository Company { get; private set; } //本次新增程式碼 public void Save() { _db.SaveChanges(); } } } ``` #### 7-3-5 建立 Company 的 Controller 在AspDotNetDemo/Areas/Admin/Controllers下新增檔案(資料夾點擊右鍵 -> 加入 -> 類別),將其命名為CompanyController.cs,然後將ProductController.cs的內容全部複製過來,並修改程式碼。 ```C#= using AspDotNetDemo.DataAccess; using AspDotNetDemo.DataAccess.Repository.IRepository; using AspDotNetDemo.Models; using AspDotNetDemo.Models.ViewModels; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; namespace AspDotNetDemo.Areas.Admin.Controllers { [Area("Admin")] public class CompanyController : Controller { private readonly IUnitOfWork _unitOfWork; public CompanyController(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public IActionResult Index() { return View(); } //GET public IActionResult Upsert(int? id) { Company company = new(); if (id == null || id == 0) { return View(company); } else { //update company company = _unitOfWork.Company.GetFirstOrDefault(u => u.Id == id); return View(company); } } //POST [HttpPost] [ValidateAntiForgeryToken] public IActionResult Upsert(Company obj, IFormFile? file) { if(ModelState.IsValid) { if (obj.Id == 0) { _unitOfWork.Company.Add(obj); TempData["success"] = "Company createed successfully!"; } else { _unitOfWork.Company.Update(obj); TempData["success"] = "Company updated successfully!"; } _unitOfWork.Save(); return RedirectToAction("Index"); } return View(obj); } #region API CALLS [HttpGet] public IActionResult GetAll() { var companyList = _unitOfWork.Company.GetAll(); return Json(new { data = companyList }); } //POST [HttpDelete] public IActionResult Delete(int? id) { var obj = _unitOfWork.Company.GetFirstOrDefault(u => u.Id == id); if (obj == null) { return Json(new { success = false, message = "Error while deleting" }); } _unitOfWork.Company.Remove(obj); _unitOfWork.Save(); return Json(new { success = true, message = "Delete Successfully!!" }); } #endregion } } ``` #### 7-3-6 建立 Company 的 View 複製AspDotNetDemo/Areas/Admin/Views/Product資料夾,並在AspDotNetDemo/Areas/Admin/Views中貼上重新命名為Company 修改AspDotNetDemo/Areas/Admin/Views/Company/Index.cshtml的程式碼。 ```html= <div class="container p-3"> <div class="row pt-4"> <div class="col-6"> <h2 class="text-primary">Company List</h2> </div> <div class="col-6 text-end"> <a asp-controller="Company" asp-action="Upsert" class="btn btn-primary"> 新增公司 </a> </div> </div> <table id="tblData" class="table table-bordered table-striped" style="width:100%"> <thead> <tr class="align-middle"> <th>Name</th> <th>Address</th> <th>City</th> <th>State</th> <th>Phone Number</th> <th></th> </tr> </thead> </table> </div> @section Scripts{ <script src="~/js/company.js"></script> } ``` #### 7-3-7 修改 Company 的 Upsert.cshtml 修改AspDotNetDemo/Areas/Admin/Views/Company/Upsert.cshtml的程式碼。 ```htmlembedded= @model Company <form method="post" asp-action="Upsert" enctype="multipart/form-data"> <input asp-for="Id" hidden /> <div class="row"> <div class="col-10"> <div class="border p-3 mt-4 row"> <div class="col-12 pb-2"> <h2 class="text-primary">@(Model.Id!=0?"Update": "Create") Company</h2> <hr /> </div> <div class="mb-3 col-6"> <label asp-for="Name"></label> <input asp-for="Name" class="form-control" /> <span asp-validation-for="Name" class="text-danger"></span> </div> <div class="mb-3 col-6"> <label asp-for="PhoneNumber"></label> <input asp-for="PhoneNumber" class="form-control" /> <span asp-validation-for="PhoneNumber" class="text-danger"></span> </div> <div class="mb-3 col-6"> <label asp-for="StreetAddress"></label> <input asp-for="StreetAddress" class="form-control" /> <span asp-validation-for="StreetAddress" class="text-danger"></span> </div> <div class="mb-3 col-6"> <label asp-for="City"></label> <input asp-for="City" class="form-control" /> <span asp-validation-for="City" class="text-danger"></span> </div> <div class="mb-3 col-6"> <label asp-for="State"></label> <input asp-for="State" class="form-control" /> <span asp-validation-for="State" class="text-danger"></span> </div> <div class="mb-3 col-6"> <label asp-for="PostalCode"></label> <input asp-for="PostalCode" class="form-control" /> <span asp-validation-for="PostalCode" class="text-danger"></span> </div> <div class="col-12"> @if (Model.Id != 0) { <button type="submit" class="btn btn-primary" style="width:150px">更新</button> } else { <button type="submit" class="btn btn-primary" style="width:150px">新增</button> } <a asp-controller="Company" asp-action="Index" class="btn btn-secondary" style="width:150px">返回</a> </div> </div> </div> </div> </form> @section Scripts{ @{ <partial name="_ValidationScriptsPartial" /> } } ``` #### 7-3-8 建立 Company 的 JavaScript 複製AspDotNetDemo/wwwroot/js/product.js檔案並在AspDotNetDemo/wwwroot/js貼上重新命名為company.js 修改AspDotNetDemo/wwwroot/js/company.js的程式碼。 ```javascript= var dataTable; $(document).ready(function () { loadDataTable(); }); function loadDataTable() { dataTable = $('#tblData').DataTable({ "ajax": { "url": "/Admin/Company/GetAll", }, "columns": [//注意這裡的欄位數量要跟html的欄位數量相同,否則會報錯 { "data": "name", "width": "15%" }, { "data": "streetAddress", "width": "15%" }, { "data": "city", "width": "15%" }, { "data": "state", "width": "15%" }, { "data": "phoneNumber", "width": "15%" }, { "data": "id", "render": function (data) { return ` <div class="w-75 btn-group" role="group"> <a href="/Admin/Company/Upsert?id=${data}" class="btn btn-primary mx-2"><i class="bi bi-pencil-square"></i>Edit</a> <a onClick=Delete('/Admin/Company/Delete/${data}') class="btn btn-danger mx-2"><i class="bi bi-trash"></i>Delete</a> </div> ` }, "width": "15%" }, ] }); } function Delete(url) { Swal.fire({ title: 'Are you sure?', text: "You won't be able to revert this!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Yes, delete it!' }).then((result) => { if (result.isConfirmed) { $.ajax({ url: url, type: "DELETE", success: function (data) { if (data.success) { toastr.success(data.message); dataTable.ajax.reload(); } else { toastr.error(data.message); } } }) } }) } ``` #### 7-3-9 修改_Layout.cshtml 修改AspDotNetDemo/Views/Shared/_Layout.cshtml的程式碼。 ```htmlembedded= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - AspDotNetDemo</title> @*<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />*@ <link rel="stylesheet" href="~/css/bootswatchTheme.css" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/AspDotNetDemo.styles.css" asp-append-version="true" /> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css"/> <link rel="stylesheet" href="//cdn.datatables.net/1.13.1/css/jquery.dataTables.min.css" /> </head> <body> <header> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div class="container-fluid"> <a class="navbar-brand" href="#">Double Drinks</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor02" aria-controls="navbarColor02" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarColor02"> <ul class="navbar-nav me-auto"> // 本次修改程式碼 <li class="nav-item"> <a class="nav-link" asp-area="Customer" asp-controller="Home" asp-action="Index">首頁</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">內容管理</a> <div class="dropdown-menu"> <a class="dropdown-item" asp-area="Admin" asp-controller="Category" asp-action="Index">類別</a> <a class="dropdown-item" asp-area="Admin" asp-controller="Product" asp-action="Index">產品</a> <div class="dropdown-divider"></div> <a class="dropdown-item" asp-area="Admin" asp-controller="Company" asp-action="Index">公司</a> </div> </li> </ul> <partial name="_LoginPartial" /> @*<form class="d-flex"> <input class="form-control me-sm-2" type="text" placeholder="Search"> <button class="btn btn-secondary my-2 my-sm-0" type="submit">Search</button> </form>*@ </div> </div> </nav> </header> <div class="container content"> <main role="main" class="py-3"> @RenderBody() </main> </div> <footer class="footer text-white bg-primary p-1"> <div class="container text-center"> &copy; 2022 - AspDotNetDemo - <a asp-area="" asp-controller="Home" asp-action="Privacy" class="text-warning">Privacy</a> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> @*<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>*@ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script> <script src="~/js/site.js" asp-append-version="true"></script> <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="//cdn.datatables.net/1.13.1/js/jquery.dataTables.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html> ``` ![](https://i.imgur.com/VP7zA3O.png) 以上步驟都完成後就可以執行應用程式,測試Company的新增、編輯、刪除等功能是否正常,如果都可以正常執行那就代表到目前這邊都完成囉。 ### 7-4 建立 Role 在上一個章節我們已經完成了基本的公司運作,接下來讓我們回到用戶方面,因為任何的網站都有多種類型的角色。我們會有公司的客戶、公司、管理者等等。 #### 7-4-1 修改 Program.cs ![](https://i.imgur.com/bTd5LsO.png) 可以到我們的資料庫中查看,預設情況沒有產生角色的規則,所以在這個章節我們必須創建我們的規則。 因此,接下來讓我們修改Program的程式碼,讓Identity多經過一層角色的判斷,因為我們這邊使用了自定義的角色,因此這邊需要產生Token,原本預設的程式碼可以到Identity/Pages/Account/Manage/Register.cshtml.cs中的OnPostAsync的Function查看Token產生的過程。 開啟AspDotNetDemo/Program.cs,並修改部分程式碼 ```C#= using AspDotNetDemo.DataAccess; using AspDotNetDemo.DataAccess.Repository; using AspDotNetDemo.DataAccess.Repository.IRepository; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Identity; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("DefaultConnection") )); // 本次修改部分 builder.Services.AddIdentity<IdentityUser, IdentityRole>().AddDefaultTokenProviders() .AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddScoped<IUnitOfWork, UnitOfWork>(); builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { 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.UseAuthentication();; app.UseAuthorization(); app.MapRazorPages(); app.MapControllerRoute( name: "default", pattern: "{area=Customer}/{controller=Home}/{action=Index}/{id?}"); app.Run(); ``` #### 7-4-2 修改 Register.cshtml.cs 及建立 SD.cs 開啟AspDotNetDemo.Utility/SD.cs,並新增程式碼 ```C#= using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.Utility { public static class SD // 修改成 public static { public const string Role_User_Cust = "Customer"; public const string Role_Employee = "Employee"; public const string Role_User_Comp = "Company"; public const string Role_Admin = "Admin"; } } ``` 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml/Register.cshtml.cs,並修改程式碼 ```C#= // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Text.Encodings.Web; using System.Threading; using System.Threading.Tasks; using AspDotNetDemo.Utility; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; namespace AspDotNetDemo.Areas.Identity.Pages.Account { public class RegisterModel : PageModel { private readonly SignInManager<IdentityUser> _signInManager; private readonly UserManager<IdentityUser> _userManager; private readonly IUserStore<IdentityUser> _userStore; private readonly IUserEmailStore<IdentityUser> _emailStore; private readonly ILogger<RegisterModel> _logger; private readonly IEmailSender _emailSender; // 本次新增部分 private readonly RoleManager<IdentityRole> _roleManager; public RegisterModel( UserManager<IdentityUser> userManager, IUserStore<IdentityUser> userStore, SignInManager<IdentityUser> signInManager, ILogger<RegisterModel> logger, IEmailSender emailSender, RoleManager<IdentityRole> roleManager) // 本次新增部分 { _roleManager = roleManager; // 本次新增部分 _userManager = userManager; _userStore = userStore; _emailStore = GetEmailStore(); _signInManager = signInManager; _logger = logger; _emailSender = emailSender; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [BindProperty] public InputModel Input { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public string ReturnUrl { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public IList<AuthenticationScheme> ExternalLogins { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> public class InputModel { /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } public async Task OnGetAsync(string returnUrl = null) { // 本次新增部分 if (!_roleManager.RoleExistsAsync(SD.Role_Admin).GetAwaiter().GetResult()) { _roleManager.CreateAsync(new IdentityRole(SD.Role_Admin)).GetAwaiter().GetResult(); _roleManager.CreateAsync(new IdentityRole(SD.Role_Employee)).GetAwaiter().GetResult(); _roleManager.CreateAsync(new IdentityRole(SD.Role_User_Cust)).GetAwaiter().GetResult(); _roleManager.CreateAsync(new IdentityRole(SD.Role_User_Comp)).GetAwaiter().GetResult(); } ReturnUrl = returnUrl; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); } public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); var result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl }, 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>."); if (_userManager.Options.SignIn.RequireConfirmedAccount) { return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl }); } else { await _signInManager.SignInAsync(user, isPersistent: false); return LocalRedirect(returnUrl); } } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } // If we got this far, something failed, redisplay form return Page(); } private IdentityUser CreateUser() { try { return Activator.CreateInstance<IdentityUser>(); } catch { throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " + $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " + $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml"); } } private IUserEmailStore<IdentityUser> GetEmailStore() { if (!_userManager.SupportsUserEmail) { throw new NotSupportedException("The default UI requires a user store with email support."); } return (IUserEmailStore<IdentityUser>)_userStore; } } } ``` 為了創建規則,我們在這裡有一個叫做Role Manager的東西,可以注意到這邊我們在依賴注入的幫助下獲得的Manager。因此我們可以在這邊創建一個私有的只允許讀取的Role Manager,通過IdentityRole來取得角色。 #### 7-4-3 修復 Bug 完成後執行應用程式,點擊註冊按鈕會發現Error ![](https://i.imgur.com/0k9n98r.png) 點擊AspDotNetDemo.Utility滑鼠右鍵 -> 加入 -> 類別,將其命名為EmailSender後新增 ![](https://i.imgur.com/kMSS6S8.png) 修改為下方程式碼 ```C#= using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.Utility { public class EmailSender : IEmailSender { } } ``` 會發現```IEmailSender```的部分有錯誤提示,將滑鼠移至紅線部分,出現燈泡後點選安裝套件 Microsoft.AspNetCore.Identity.UI -> 選擇使用本機版本 ![](https://i.imgur.com/BJrGGaf.png) 會發現紅底線部分沒有消失,因為還沒有新增實作介面,一樣將滑鼠移至紅底線部分,出現燈泡後點選實作介面,並修改程式碼 ![](https://i.imgur.com/N8RHjSA.png) ```C#= using Microsoft.AspNetCore.Identity.UI.Services; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.Utility { public class EmailSender : IEmailSender { public Task SendEmailAsync(string email, string subject, string htmlMessage) { return Task.CompletedTask; } } } ``` 開啟 Program.cs,新增部分程式碼,出現紅底線的部分一樣點擊燈泡後引入套件即可。 ```C#= using AspDotNetDemo.DataAccess; using AspDotNetDemo.DataAccess.Repository; using AspDotNetDemo.DataAccess.Repository.IRepository; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Identity; // 本次新增部分 using Microsoft.AspNetCore.Identity.UI.Services; using AspDotNetDemo.Utility; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( builder.Configuration.GetConnectionString("DefaultConnection") )); builder.Services.AddIdentity<IdentityUser, IdentityRole>().AddDefaultTokenProviders() .AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddScoped<IUnitOfWork, UnitOfWork>(); // 本次新增部分 builder.Services.AddSingleton<IEmailSender, EmailSender>(); builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { 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.UseAuthentication();; app.UseAuthorization(); app.MapRazorPages(); app.MapControllerRoute( name: "default", pattern: "{area=Customer}/{controller=Home}/{action=Index}/{id?}"); app.Run(); ``` 完成以上步驟後,執行應用程式,點擊註冊按鈕,如果能正常顯示畫面跟資料庫的Role的表有新增我們剛剛增加的角色,那就代表成功了。 ![](https://i.imgur.com/5B97Hy2.png) #### 7-4-4 編輯註冊頁面 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml/Register.cshtml.cs,並修改程式碼 ```C#= namespace AspDotNetDemo.Areas.Identity.Pages.Account { public class RegisterModel : PageModel { . . . public class InputModel { . . . /// <summary> /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// </summary> [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } // 本次新增部分 [Required] public string Name { get; set; } public string? StreetAddress { get; set; } public string? City { get; set; } public string? State { get; set; } public string? PostalCode { get; set; } public string? PhoneNumber { get; set; } } . . . public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); //本次新增部分 user.StreetAddress = Input.StreetAddress; user.City = Input.City; user.State = Input.State; user.PostalCode = Input.PostalCode; user.Name = Input.Name; user.PhoneNumber = Input.PhoneNumber; var result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl }, 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>."); if (_userManager.Options.SignIn.RequireConfirmedAccount) { return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl }); } else { await _signInManager.SignInAsync(user, isPersistent: false); return LocalRedirect(returnUrl); } } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } // If we got this far, something failed, redisplay form return Page(); } // 本次修改部分 private ApplicationUser CreateUser() { try { return Activator.CreateInstance<ApplicationUser>(); } catch { throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " + $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " + $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml"); } } private IUserEmailStore<IdentityUser> GetEmailStore() { if (!_userManager.SupportsUserEmail) { throw new NotSupportedException("The default UI requires a user store with email support."); } return (IUserEmailStore<IdentityUser>)_userStore; } } } ``` 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml,並修改程式碼 ```html= @page @model RegisterModel @{ ViewData["Title"] = "Register"; } <h1 class="pt-4">@ViewData["Title"]</h1> <div class="row pt-4"> <div class="col-md-7 row"> <form id="registerForm" class="row" asp-route-returnUrl="@Model.ReturnUrl" method="post"> <h2>Create a new account.</h2> <hr /> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-floating py-2 col-12"> <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" /> <label asp-for="Input.Email"></label> <span asp-validation-for="Input.Email" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.Name" class="form-control" aria-required="true" /> <label asp-for="Input.Name"></label> <span asp-validation-for="Input.Name" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.PhoneNumber" class="form-control" aria-required="true" /> <label asp-for="Input.PhoneNumber"></label> <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.StreetAddress" class="form-control" aria-required="true" /> <label asp-for="Input.StreetAddress"></label> <span asp-validation-for="Input.StreetAddress" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.City" class="form-control" aria-required="true" /> <label asp-for="Input.City"></label> <span asp-validation-for="Input.City" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.State" class="form-control" aria-required="true" /> <label asp-for="Input.State"></label> <span asp-validation-for="Input.State" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.PostalCode" class="form-control" aria-required="true" /> <label asp-for="Input.PostalCode"></label> <span asp-validation-for="Input.PostalCode" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" /> <label asp-for="Input.Password"></label> <span asp-validation-for="Input.Password" class="text-danger"></span> </div> <div class="form-floating py-2 col-6"> <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" /> <label asp-for="Input.ConfirmPassword"></label> <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> </div> <button id="registerSubmit" type="submit" class="w-100 btn btn-primary">Register</button> </form> </div> <div class="col-md-5"> <section> <h3>Use another service to register.</h3> <hr /> @{ if ((Model.ExternalLogins?.Count ?? 0) == 0) { <div> <p> There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article about setting up this ASP.NET application to support logging in via external services</a>. </p> </div> } else { <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> <div> <p> @foreach (var provider in Model.ExternalLogins!) { <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> } </p> </div> </form> } } </section> </div> </div> @section Scripts { <partial name="_ValidationScriptsPartial" /> } ``` 完成後註冊頁面會如下圖。 ![](https://i.imgur.com/e94ke64.png) #### 7-4-5 建立 Role 的下拉式選單 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml/Register.cshtml.cs,並修改程式碼 ```C#= namespace AspDotNetDemo.Areas.Identity.Pages.Account { public class RegisterModel : PageModel { . . . public class InputModel { . . . [Required] public string Name { get; set; } public string? StreetAddress { get; set; } public string? City { get; set; } public string? State { get; set; } public string? PostalCode { get; set; } public string? PhoneNumber { get; set; } // 本次新增部分 public string? Role { get; set; } [ValidateNever] public IEnumerable<SelectListItem> RoleList { get; set; } } public async Task OnGetAsync(string returnUrl = null) { if (!_roleManager.RoleExistsAsync(SD.Role_Admin).GetAwaiter().GetResult()) { _roleManager.CreateAsync(new IdentityRole(SD.Role_Admin)).GetAwaiter().GetResult(); _roleManager.CreateAsync(new IdentityRole(SD.Role_Employee)).GetAwaiter().GetResult(); _roleManager.CreateAsync(new IdentityRole(SD.Role_User_Indi)).GetAwaiter().GetResult(); _roleManager.CreateAsync(new IdentityRole(SD.Role_User_Comp)).GetAwaiter().GetResult(); } ReturnUrl = returnUrl; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); // 本次新增部分 Input = new InputModel() { RoleList = _roleManager.Roles.Select(x => x.Name).Select(i => new SelectListItem { Text = i, Value = i }) }; } . . . } } ``` 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml,找到下方ConfirmPassword的標籤,並在下方新增下拉選單 ```C#= <div class="form-floating py-2 col-6"> <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" /> <label asp-for="Input.ConfirmPassword"></label> <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> </div> <!-- 本次新增部分--> <div class="form-floating py-2 col-6"> <select asp-for="Input.Role" asp-items="@Model.Input.RoleList" class="form-select"> <option disabled selected>-Select Role-</option> </select> </div> <button id="registerSubmit" type="submit" class="w-100 btn btn-primary">Register</button> </form> ``` 完成後就會看到出現了 Role 的下拉式選單 ![](https://i.imgur.com/P9mkg2C.png) 接著要把使用者選取的 Role 寫進資料庫 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml/Register.cshtml.cs,找到下方程式碼並修改,若有出現紅底線報錯,將滑鼠移至紅底線程式碼部分,點擊燈泡 using 套件即可解決 ```C#= public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var user = CreateUser(); await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); user.StreeAddress = Input.StreeAddress; user.City = Input.City; user.State = Input.State; user.PostalCode = Input.PostalCode; user.Name = Input.Name; user.PhoneNumber = Input.PhoneNumber; var result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); // 本次新增部分 if(Input.Role == null) { await _userManager.AddToRoleAsync(user, SD.Role_User_Cust); } else { await _userManager.AddToRoleAsync(user, Input.Role); } var userId = await _userManager.GetUserIdAsync(user); . . . } ``` * 這部分主要是在處理如果使用者沒有選取角色,我們將會預設顧客的身分給使用者 接下來測試註冊功能,欄位填完後選取 Role 送出 ![](https://i.imgur.com/0rQwvvm.png) 會發現資料庫寫入了ApplicationUser ![](https://i.imgur.com/AtbNp33.png) 開啟AspNetUserRoles資料表,這張表是使用者跟 Role 的中介表,會發現剛剛建立的使用者ID綁定了Admin ![](https://i.imgur.com/ZKyHY5I.png) ![](https://i.imgur.com/BTliyvz.png) #### 7-4-6 建立 Company 的下拉式選單 現在我們需要在使用者的資料表中新增一個CompanyId的欄位, 開啟AspDotNetDemo.Models/ApplicationUser.cs,並新增部分程式碼 ```C#= using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AspDotNetDemo.Models { public class ApplicationUser : IdentityUser { [Required] public string Name { get; set; } public string? StreetAddress { get; set; } public string? City { get; set; } public string? State { get; set; } public string? PostalCode { get; set; } // 本次新增部分 public int? CompanyId { get; set; } [ForeignKey("CompanyId")] [ValidateNever] public Company Company { get; set; } } } ``` 完成後開啟套件管理主控台,確認預設專案為AspDotNetDemo.DataAccess,並輸入以下指令 ``` add-migration addCompanyIdToUser update-database ``` 完成後使用者的資料表就會新增CompanyId的欄位了。 接下來要完成當使用者身分為公司時,會出現公司的下拉式選單的功能。 首先開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml/Register.cshtml.cs,並修改程式碼,若有出現紅底線報錯,將滑鼠移至紅底線程式碼部分,點擊燈泡 using 套件即可解決 ```C#= namespace AspDotNetDemo.Areas.Identity.Pages.Account { public class RegisterModel : PageModel { private readonly SignInManager<IdentityUser> _signInManager; private readonly UserManager<IdentityUser> _userManager; private readonly IUserStore<IdentityUser> _userStore; private readonly IUserEmailStore<IdentityUser> _emailStore; private readonly ILogger<RegisterModel> _logger; private readonly IEmailSender _emailSender; private readonly RoleManager<IdentityRole> _roleManager; // 本次新增部分 private readonly IUnitOfWork _unitOfWork; public RegisterModel( UserManager<IdentityUser> userManager, IUserStore<IdentityUser> userStore, SignInManager<IdentityUser> signInManager, ILogger<RegisterModel> logger, IEmailSender emailSender, RoleManager<IdentityRole> roleManager, IUnitOfWork unitOfWork) // 本次新增部分 { _unitOfWork = unitOfWork; // 本次新增部分 _roleManager = roleManager; _userManager = userManager; _userStore = userStore; _emailStore = GetEmailStore(); _signInManager = signInManager; _logger = logger; _emailSender = emailSender; } . . [中間部分省略] . [Required] public string Name { get; set; } public string? StreetAddress { get; set; } public string? City { get; set; } public string? State { get; set; } public string? PostalCode { get; set; } public string? PhoneNumber { get; set; } public string? Role { get; set; } //本次新增部分 public int? CompanyId { get; set; } [ValidateNever] public IEnumerable<SelectListItem> RoleList { get; set; } //本次新增部分 [ValidateNever] public IEnumerable<SelectListItem> CompanyList { get; set; } } public async Task OnGetAsync(string returnUrl = null) { . . [中間部分省略] . Input = new InputModel() { RoleList = _roleManager.Roles.Select(x => x.Name).Select(i => new SelectListItem { Text = i, Value = i }), // 本次新增部分 CompanyList = _unitOfWork.Company.GetAll().Select(i => new SelectListItem { Text = i.Name, Value = i.Id.ToString() }) }; } ``` 接下來要在註冊頁面新增一個公司的下拉式選單,開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml,找到下方程式碼並新增程式碼 ```C#= . . [中間部分省略] . <div class="form-floating py-2 col-6"> <select asp-for="Input.Role" asp-items="@Model.Input.RoleList" class="form-select"> <option disabled selected>-Select Role-</option> </select> </div> <!-- 本次新增部分--> <div class="form-floating py-2 col-6"> <select asp-for="Input.CompanyId" asp-items="@Model.Input.CompanyList" class="form-select"> <option disabled selected>-Select Company-</option> </select> </div> <button id="registerSubmit" type="submit" class="w-100 btn btn-primary">Register</button> </form> </div> ``` 完成後在下拉式選單就會出現先前建立的公司選項囉。 ![](https://i.imgur.com/K8dB30V.png) ![](https://i.imgur.com/FoVpzUs.png) 但公司的下拉式選單應該在使用者選取 Role 為 Company 時才會出現,所以頁面的部分要再做修改。 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml,並新增程式碼 ```html= <!-- 本次修改部分--> <div class="form-floating py-2 col-6"> <select asp-for="Input.CompanyId" style="display:none;" asp-items="@Model.Input.CompanyList" class="form-select"> <option disabled selected>-Select Company-</option> </select> </div> ``` 下方的部分也要修改 ```html= @section Scripts { <partial name="_ValidationScriptsPartial" /> @*本次新增部分*@ <script> $(document).ready(function() { $('#Input_Role').change(function() { var selection = $('#Input_Role Option:Selected').text(); if (selection != 'Company') { $('#Input_CompanyId').hide(); } else { $('#Input_CompanyId').show(); } }) }) </script> } ``` 完成後執行應用程式,就會發現如果使用者在註冊時選取的 Role 不為 Comapny,則下拉式選單就不會出現。 接下來要將使用者選取的 CompanyId 在註冊時寫入使用者表 開啟AspDotNetDemo/Areas/Identity/Pages/Account/Register.cshtml/Register.cshtml.cs,並修改部分程式碼 ```C#= var user = CreateUser(); await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); user.StreetAddress = Input.StreetAddress; user.City = Input.City; user.State = Input.State; user.PostalCode = Input.PostalCode; user.Name = Input.Name; user.PhoneNumber = Input.PhoneNumber; // 本次新增部分 if (Input.Role == SD.Role_User_Comp) { user.CompanyId = Input.CompanyId; } ``` 完成就會發現註冊完成後,使用者表的 CompanyId 欄位出現對應的 Id 了,之後可以嘗試註冊看看公司的角色,看看所有功能是否都能符合預期。 ![](https://i.imgur.com/4XPqVNw.png)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully