# TelerikCoreAdmin Base on ASP.NET core 5 and theAdmin, Telerik UI 事前下載: [VScode](https://code.visualstudio.com/) 或 [Visual Studio](https://visualstudio.microsoft.com/zh-hant/) [Docker](https://www.docker.com/get-started) [TablePlus](https://tableplus.com/) [Dotnet SDK](https://dotnet.microsoft.com/download/dotnet/5.0) [Telerik Asp.net core UI](https://www.telerik.com/download-trial-file/v2/aspnet-core-ui) Telerik帳號 (安裝Telerik Asp.net core UI時會用到) Account: hanks.li@hyweb.com.tw Password: 1qazxcde32ws 如果有MSSQL+SSMS環境就可以不用裝Docker+TablePlus 建議先安裝好Docker的mssql images,因為容量要1.37GB Docker images的安裝可以參考[底下文件處](#Docker--TablePlus) --- 參考文件: 鐵人賽: https://ithelp.ithome.com.tw/users/20107461/ironman/1372 MVC概觀 https://docs.microsoft.com/zh-tw/aspnet/core/mvc/overview?view=aspnetcore-5.0 https://docs.microsoft.com/zh-tw/aspnet/core/mvc/overview?view=aspnetcore-5.0#features Life Cycle https://www.c-sharpcorner.com/article/asp-net-core-mvc-request-life-cycle/ https://ithelp.ithome.com.tw/articles/10242725 https://ithelp.ithome.com.tw/articles/10192497 Getting Started https://docs.microsoft.com/zh-tw/aspnet/core/getting-started/?view=aspnetcore-5.0 Entity Framework Core (O/RM) https://docs.microsoft.com/zh-tw/ef/core/ Core Identity https://docs.microsoft.com/zh-tw/aspnet/core/security/authentication/identity?view=aspnetcore-5.0&tabs=visual-studio Identity model customization in ASP.NET Core https://docs.microsoft.com/zh-tw/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-5.0 後臺UI元件 Telerik aspnet core UI https://docs.telerik.com/aspnet-core/introduction 後臺css樣板 TheAdmin http://thetheme.io/theadmin/index.html 建立範本 https://docs.microsoft.com/zh-tw/dotnet/core/tools/custom-templates --- # Docker + TablePlus 參考: https://hub.docker.com/_/microsoft-mssql-server ### 安裝Docker 開啟cmd之後輸入以下指令安裝mssql images ```shell= # mssql版本可自行更換 docker pull mcr.microsoft.com/mssql/server:2017-latest ``` 安裝好後會在以下畫面看到有新增的Images ![](https://i.imgur.com/URGS3dP.png) 接下來使用該images新增containers ```shell= #密碼部分要包含大小寫 符號 docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=yourStrong(!)Password" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest ``` 執行成功會在以下畫面看到新增的containers,並且狀態為RUNNING ![](https://i.imgur.com/itF1k8M.png) ### TablePlus連線設定 點選下方Create a new connection,建立新的資料庫連線, ![](https://i.imgur.com/2Y7Pqps.png) 選擇Microsoft SQL Server... ![](https://i.imgur.com/JQqCfsG.png) ![](https://i.imgur.com/wT1Hwvl.png) ![](https://i.imgur.com/YR8Fzb6.png) TablePlus 也有一些Plugins可以使用 如[Diagram Generator](https://github.com/TablePlus/TablePlus/issues/44#issuecomment-668054912) ![](https://user-images.githubusercontent.com/3654622/82752377-d1239e00-9de7-11ea-9cb2-ccf11135bba5.gif) # Dotnet SDK Version / 多個version? / switch version? https://docs.microsoft.com/zh-tw/dotnet/core/versions/selection https://blog.miniasp.com/post/2018/04/19/How-to-switch-between-DotNet-SDK-versions https://blog.miniasp.com/post/2018/04/19/How-to-switch-between-DotNet-SDK-versions 如何在多個 .NET Core SDK 版本之間進行切換 (global.json) dotnet sdk於CLI執行的時候只會抓其中一個版本 後續步驟操作基本都會直接用CLI,建議要記得更新到5版 # 初始化專案,套用Telerik及其他Package ## Step1. Create project directory and initialize Project 建立一個空的資料夾,然後初始化安裝dotnet core 5 以下的安裝指令會自動建立資料夾,並且安裝包含auth的部分 範例可參考 https://docs.microsoft.com/zh-tw/aspnet/core/getting-started/?view=aspnetcore-5.0&tabs=windows https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-5.0&tabs=visual-studio-code https://docs.microsoft.com/zh-tw/aspnet/core/security/authentication/identity?view=aspnetcore-5.0&tabs=netcore-cli#scaffold-register-login-logout-and-registerconfirmation 於cli上輸入 ```shell= #Razor架構 包含Identity認證部分,後方project name自行修改為想要的 dotnet new webapp --auth Individual -uld -o TelerikCoreAdmin #不要restore可以再加上參數 --no-restore #MVC架構 包含Identity認證部分,後方project name自行修改為想要的 dotnet new MVC --auth Individual -uld --no-restore -o TestMVCProject #之後進到專案如果需要執行可以再重新跑dotnet restore ``` 如果出現 > 'dotnet' 不是內部或外部命令、可執行的程式或批次檔。 代表沒有安裝SDK (https://dotnet.microsoft.com/download/dotnet/5.0) 安裝完後需重開cmd/terminal才可使用 可輸入dotnet測試 完整指令可參考 https://docs.microsoft.com/zh-tw/dotnet/core/tools/ https://docs.microsoft.com/zh-tw/dotnet/core/tools/dotnet-new 於cmd中移動至專案中,測試是否可以run ```shell= dotnet watch run # #成功執行會出現以下 # watch : Started 正在建置... info: Microsoft.Hosting.Lifetime[0] Now listening on: https://localhost:5000 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. ... ``` 不安全? 來來來 加上安全憑證 https://docs.microsoft.com/zh-tw/aspnet/core/getting-started/?view=aspnetcore-5.0&tabs=windows#trust-the-development-certificate 先按Ctrl+C中斷原本的watch run 在執行以下command ```shell= dotnet dev-certs https --trust ``` 如果還是顯示紅色不安全,要重開瀏覽器及頁面 ## Step2. Integrating Telerik UI for ASP.NET Core 參考: https://docs.telerik.com/aspnet-core/getting-started/first-steps https://docs.telerik.com/aspnet-core/getting-started/first-steps-cli#integrating-ui-for-aspnet-core 下載telerik aspnet-core-ui https://www.telerik.com/download-trial-file/v2/aspnet-core-ui telerik帳號 Account: hanks.li@hyweb.com.tw Password: 1qazxcde32ws ### 新增Nuget Package ### 方法1: 使用Visual Studio Package Manager 安裝Telerik UI會需要使用新增Telerik nuget來源,建議使用Visual Studio,VScode不太支援 先新增nuget來源 ![](https://i.imgur.com/8yc9BEp.png) 再依照來源新增package ![](https://i.imgur.com/YP0IUVD.png) 新增完後就可以不用使用到Visual Studio了,可以改回用VScode ### 方法2: 使用CLI 參考:https://docs.telerik.com/aspnet-core/getting-started/first-steps-cli 於專案路徑下執行 ``` dotnet new nugetconfig ``` 會建立出一份基本的nuget.config 再將其修改為如下 ```xml= <?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <!--To inherit the global NuGet package sources remove the <clear/> line below --> <clear /> <add key="nuget" value="https://api.nuget.org/v3/index.json" /> <add key="telerik.com" value="https://nuget.telerik.com/v3/index.json" /> </packageSources> <packageSourceCredentials> <telerik.com> <add key="Username" value="hanks.li@hyweb.com.tw" /> <add key="ClearTextPassword" value="BJ)#jo3d93" /> </telerik.com> </packageSourceCredentials> </configuration> #基本上上面在做的就是於Project中修改NuGet設定,新增Nuget來源 ``` 確認目前來源 ``` dotnet nuget list source ``` 再以CLI執行,安裝Telerik的套件 ```shell= #正式版 dotnet add package Telerik.UI.for.AspNet.Core ``` 安裝完後也可以使用以下指令確定安裝完的package有哪些 ``` dotnet list package ``` ## Step3. 設定Startup 於Startup.cs 的ConfigureServices新增以下code (隨便找個縫塞就好,前後順序不影響) ```csharp= public void ConfigureServices(IServiceCollection services) { ... // Add the Kendo UI services to the services container. services.AddKendo(); } ``` ## Step4. 設定ViewImports.cshtml 於~/Views/_ViewImports.cshtml新增Kendo相關設定(TagHelper等等) (有用到的_ViewImports在塞就好,像是前台如果用不到kendo,前台的ViewImports就可以不用塞) ```csharp= @using MyASPNETCoreProject @using MyASPNETCoreProject.Models @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers //加底下這兩行 @addTagHelper *, Kendo.Mvc @using Kendo.Mvc.UI ``` ## Step5. 載入css及js 這邊需要注意的是 以下js需要放在jquery之後,並且要放於<head>...</head>中 會需要放置在JQuery後,是因為kendo中有許多js都是基於jquery開發 會需要放置在head中,應該是因為要替換其內容,需要在還未創立dom之前就轉換(? ```htmlembedded= <head> <meta charset="utf-8" /> ... <link rel="stylesheet" href="https://kendo.cdn.telerik.com/2021.2.511/styles/kendo.bootstrap-v4.min.css" /> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="https://kendo.cdn.telerik.com/2021.2.511/js/kendo.all.min.js"></script> <script src="https://kendo.cdn.telerik.com/2021.2.511/js/kendo.aspnetmvc.min.js"></script> ... </head> ``` ## Step6. 測試是否套用成功 完成後就可以在任意html地方載入kendo元件 範例如下 ```htmlmixed= <div class="text-center"> <h2>Kendo UI DatePicker</h2> @(Html.Kendo().DatePicker() .Name("my-picker") ) </div> ``` ![](https://i.imgur.com/f9Ho8rO.png) ## Step7. 套用其他Package 如果需要套用其他Package 可以直接用CLI安裝,只有Telerik比較麻煩,因為他要套用自己的NuGet source 如Email的常用Package [MailKit](https://github.com/jstedfast/MailKit) 在安裝時只需要在CLI上輸入 ```shell= dotnet add package MimeKit --version 2.13.0 ``` 也可透過其他方式(如package manager)安裝 https://www.nuget.org/packages/MimeKit/ # 擴增Identity ## Step1. Scrffold Identity 參考: https://docs.microsoft.com/zh-tw/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-5.0&tabs=netcore-cli#scaffold-identity-into-an-mvc-project-with-authorization --- 在接下來的步驟你會遇到一個比較特別的單字Razor 類別庫(RCL - Razor class library),源自於[這段](https://docs.microsoft.com/zh-tw/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-5.0&tabs=visual-studio)及[這裡](https://docs.microsoft.com/zh-tw/aspnet/core/razor-pages/ui-class?view=aspnetcore-5.0&tabs=visual-studio) ASP.NET Core Identity的部分,基本上是以Razor Pages方式所寫的,並以Package方式載入,如果要修改原有的程式碼,就會需要產生原有的程式碼,再進行修改,也就是接下來要做的步驟"Scrffold"。 Razor及MVC的架構寫法基本上都要會 ! Razor語法(主要用於view)可參考[這篇](https://docs.microsoft.com/zh-tw/aspnet/core/mvc/views/razor?view=aspnetcore-5.0) --- 安裝 ASP.NET Core scaffolder所需 ```shell= dotnet tool install -g dotnet-aspnet-codegenerator ``` 新增相關Package ```shell= dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.AspNetCore.Identity.UI dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools ``` 可以用 ```shell= dotnet aspnet-codegenerator identity -lf ``` 查看能夠並scaffold identity哪些東西 底下的指令主要是先將常用的功能給generate出來 包含了以下功能,可再自行調整 * Account.Register * Account.Login * Account.Logout * Account.RegisterConfirmation * Account.AccessDenied * Account.ResetPassword ```shell= # 指令中的TelerikCoreAdmin.Data.ApplicationDbContext # 要把前面的TelerikCoreAdmin 改為自己的Project Name dotnet aspnet-codegenerator identity -dc TelerikCoreAdmin.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation;Account.AccessDenied;Account.ResetPassword;Account.ResetPasswordConfirmation;Account.ConfirmEmail;" dotnet aspnet-codegenerator identity -dc APHI.Data.ApplicationDbContext --files "Account.ResetPasswordConfirmation;" dotnet aspnet-codegenerator identity -dc AEC.Web.Data.ApplicationDbContext --files "Account.ConfirmEmail;" ``` 使用localDB 參考:https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/first-mvc-app/working-with-sql?view=aspnetcore-5.0&tabs=visual-studio ```csharp= // 目前的ConfigureServices應該會長這樣 // scaffold後,他會新增相關的Services, // 最後只需要修改appsettings.json中的ConnectionString即可 public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); services.AddDatabaseDeveloperPageExceptionFilter(); services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); services.AddRazorPages(); services.AddKendo(); } ``` 修改appsettings.json中 DefaultConnection的使用資料庫名稱,IP及連線帳密 ```json= // appsettings.json { "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=TodoList;User Id=sa;password=1qazxcde3@WS;MultipleActiveResultSets=true" }, ... ``` 密碼怎麼跑出乃惹!! 來加個密ㄅ https://docs.microsoft.com/zh-tw/aspnet/core/security/app-secrets?view=aspnetcore-5.0&tabs=windows#set-a-secret 將DB password改為secrets https://docs.microsoft.com/zh-tw/aspnet/core/security/app-secrets?view=aspnetcore-5.0&tabs=windows#string-replacement-with-secrets 如果是要將單純文字做加/解密,可以使用資料保護API https://docs.microsoft.com/zh-tw/aspnet/core/security/data-protection/using-data-protection?view=aspnetcore-5.0 ## Step2. 建立資料庫並測試 ```shell= dotnet tool install --global dotnet-ef dotnet ef database update ``` ```shell= dotnet watch run ``` ![](https://i.imgur.com/dvAGd4H.png) ![](https://i.imgur.com/jfUYW21.png) ![](https://i.imgur.com/7Rljpw5.png) ![](https://i.imgur.com/y4w0uWB.png) 剛註冊完要經過email認證,如果沒有經過認證會無法登入 可以自行於資料庫中修改欄位 ![](https://i.imgur.com/9JBEYZQ.png) ![](https://i.imgur.com/nBkhZIO.png) # 基礎CURD (MVC) - 以TodoList為例 參考: https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-5.0&tabs=visual-studio https://docs.microsoft.com/zh-tw/aspnet/core/data/ef-mvc/crud?view=aspnetcore-5.0 MVC: https://ithelp.ithome.com.tw/articles/10193590 --- 接下來我們來做一個簡單的TodoList的CURD 會依序執行以下步驟 * 新增Model * 建立Dbcontext並修改(或新增DBset) * 產生Migration並更新DB * 新增Controller及View ## 新增Model 參考: https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/first-mvc-app/adding-model?view=aspnetcore-5.0&tabs=visual-studio https://docs.microsoft.com/zh-tw/aspnet/core/data/ef-mvc/intro?view=aspnetcore-5.0#create-the-data-model https://docs.microsoft.com/zh-tw/ef/core/modeling/ --- 在 [ Models ] 資料夾中新增Entity Class ```csharp= using System; namespace TodoList.Models { public class Job { public int ID { get; set; } public string JobName { get; set; } public DateTime CreateDate { get; set; } public DateTime UpdateDate { get; set; } } } ``` Model的詳細設定請參考 https://docs.microsoft.com/zh-tw/ef/core/modeling/ 非常重要!! 特別是實體屬性(data-annotations)部分 ## 建立並修改Dbcontext 參考: https://docs.microsoft.com/zh-tw/aspnet/core/tutorials/first-mvc-app/adding-model?view=aspnetcore-5.0&tabs=visual-studio#examine-the-generated-database-context-class-and-registration https://docs.microsoft.com/zh-tw/aspnet/core/data/ef-mvc/intro?view=aspnetcore-5.0#register-the-schoolcontext https://docs.microsoft.com/zh-tw/ef/core/dbcontext-configuration/ https://ithelp.ithome.com.tw/articles/10196856 --- DbContext 是 EF Core 跟資料庫溝通的主要類別,透過繼承 DbContext 可以跟資料庫溝通的行為 首先我們先建立一個類別並繼承 DbContext,同時建立 DbSet。 於 [ Data ] 資料夾中新增新的Class,名為[XXXDbContext]。 ```csharp= using Microsoft.EntityFrameworkCore; using TodoList.Models; namespace TodoList.Data { public class TodoDbContext : DbContext { public TodoDbContext(DbContextOptions<TodoDbContext> options) : base(options) { } public DbSet<Job> Job { get; set; } } } ``` 修改[Startup.cs], 將 EF Core 加入至這個設定 AddDbContext ```csharp= public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); //加入以下Dbcontext,使用DefaultConnection的連線設定 services.AddDbContext<TodoDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); ... } ``` ## 產生Migration並更新DB 參考: https://docs.microsoft.com/zh-tw/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli --- **Migrate[v.] 移轉/遷移** **Migrate[n.] 移轉的文件?** 基本上就是將Model轉成migration,之後可以依照Model及Dbcontext來產生Migration 並用Migration來新增DB中Table,或更新其中的欄位 未來就是維護Model,修改Dbcontext 然後不斷產生migartion,再執行migration來更新DB 再進行migration前,先確認以下動作 1. 已經新增了一個Model,或者是Model有做了修改 2. 有Dbcontext,並於Dbcontext中以設定好DBset 3. 資料庫確定有開,並有連線成功 4. **等等執行migration時要記得指定特定Dbcontext!** ```shell= dotnet ef migrations add AddTodo --context TodoDbContext # 要注意參數 --context # 是指定你要依照哪一個Dbcontext去執行產生migration ``` 執行成功後會在[Migrations]資料夾中看到新產生的migration檔案 ![](https://i.imgur.com/HNz3zFa.png) 之後就可以執行以下指令來進行更新DB ```shell= dotnet ef database update --context TodoDbContext # 要注意參數 --context # 是指定你要依照哪一個Dbcontext去執行產生migration ``` ## 新增Controller及View CRUD參考: https://docs.microsoft.com/zh-tw/aspnet/core/data/ef-mvc/crud?view=aspnetcore-5.0 ef - querying https://docs.microsoft.com/zh-tw/ef/core/querying/ ef - saving https://docs.microsoft.com/zh-tw/ef/core/saving/ --- Controller ```csharp= using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TodoList.Data; using TodoList.Models; //using TodoList.Models; namespace TodoList.Controllers { public class JobController : Controller { protected readonly TodoDbContext _context; public JobController(TodoDbContext context) { _context = context; } public async Task<IActionResult> Index() { var TodoList = await _context.Job.ToListAsync(); return View(TodoList); } public IActionResult Create() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("JobName,CreateDate,UpdateDate")] Job job) { if (ModelState.IsValid) { _context.Add(job); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } return View(job); } public async Task<IActionResult> Edit(int? id) { if (id == null) { return NotFound(); } var Job = await _context.Job .AsNoTracking() .FirstOrDefaultAsync(m => m.ID == id); if (Job == null) { return NotFound(); } return View(Job); } [HttpPost, ActionName("Edit")] [ValidateAntiForgeryToken] public async Task<IActionResult> EditPost(int? id) { if (id == null) { return NotFound(); } var JobToUpdate = await _context.Job .FirstOrDefaultAsync(j => j.ID == id); if (await TryUpdateModelAsync<Job>(JobToUpdate, "", j => j.JobName, j => j.CreateDate, j => j.UpdateDate)) { try { await _context.SaveChangesAsync(); } catch (DbUpdateException /* ex */) { //Log the error (uncomment ex variable name and write a log.) ModelState.AddModelError("", "Unable to save changes. " + "Try again, and if the problem persists, " + "see your system administrator."); } return RedirectToAction(nameof(Index)); } return View(JobToUpdate); } // POST: Courses/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<IActionResult> Delete(int id) { var job = await _context.Job.FindAsync(id); _context.Job.Remove(job); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } } } ``` View index.cshtml ```csharp= @model IEnumerable<TodoList.Models.Job> @{ ViewData["Title"] = "Todo List"; } <h1>Todo List</h1> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th> ID </th> <th> 任務名 </th> <th> 建立時間 </th> <th> 更新時間 </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.ID) </td> <td> @Html.DisplayFor(modelItem => item.JobName) </td> <td> @Html.DisplayFor(modelItem => item.CreateDate) </td> <td> @Html.DisplayFor(modelItem => item.UpdateDate) </td> <td> <a asp-action="Edit" asp-route-id="@item.ID" class="btn btn-primary">Edit</a> | <form class="d-inline" asp-action="Delete" asp-route-id="@item.ID"> @Html.AntiForgeryToken() <button type="submit" class="btn btn-danger">Delete</button> </form> </td> </tr> } </tbody> </table> ``` Create.cshtml ```csharp= @model TodoList.Models.Job @{ ViewData["Title"] = "Create"; } <h1>Create</h1> <h4>Course</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Create"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="JobName" class="control-label"></label> <input asp-for="JobName" class="form-control" /> <span asp-validation-for="JobName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="CreateDate" class="control-label"></label> @(Html.Kendo().DatePicker() .Name("CreateDate") ) <span asp-validation-for="CreateDate" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="UpdateDate" class="control-label"></label> @(Html.Kendo().DatePicker() .Name("UpdateDate") ) <span asp-validation-for="UpdateDate" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} } ``` Edit.cshtml ```csharp= @model TodoList.Models.Job @{ ViewData["Title"] = "Edit"; } <h1>Edit</h1> <h4>Course</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Edit"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <input type="hidden" asp-for="ID" /> <div class="form-group"> <label asp-for="JobName" class="control-label"></label> <input asp-for="JobName" class="form-control" /> <span asp-validation-for="JobName" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="CreateDate" class="control-label"></label> @(Html.Kendo().DatePicker() .Name("CreateDate") .Value(@Model.CreateDate) ) <span asp-validation-for="CreateDate" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="UpdateDate" class="control-label"></label> @(Html.Kendo().DatePicker() .Name("UpdateDate") .Value(@Model.UpdateDate) ) <span asp-validation-for="UpdateDate" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Save" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} } ``` 列表頁 https://localhost:5001/Job ![](https://i.imgur.com/vSrU8BH.png) 建立頁 https://localhost:5001/Job/Create ![](https://i.imgur.com/wwee1sU.png) 編輯頁 https://localhost:5001/Job/Edit/2 ![](https://i.imgur.com/mI3QC26.png) ## 功能擴充 ### 於[列表頁]加上分頁、搜尋 參考: 原生作法 https://docs.microsoft.com/zh-tw/aspnet/core/data/ef-mvc/sort-filter-page?view=aspnetcore-5.0 DataTable https://datatables.net/ Telerik - Grid https://demos.telerik.com/aspnet-core/grid ### 刪除時加上跳警示 https://sweetalert2.github.io/ ### 表單擴充 - 檔案上傳 原生: https://docs.microsoft.com/zh-tw/aspnet/core/mvc/models/file-uploads?view=aspnetcore-5.0 Telerik - upload https://demos.telerik.com/aspnet-core/upload ### 表單擴充 - 編輯器 Telerik - editor https://demos.telerik.com/aspnet-core/editor Base Management有另外依照Telerik的editor重新客製化了一版本 功能包含編輯器+圖片上傳管理 可以直接使用,建議不要用原本的 # 套用theAdmin + Area management 首先要擁有一包Hanks正在開發中還沒做完的Base Management --- 新增 Models/ApplicationRole.cs Models/ApplicationUser.cs Models/ApplicationUserRole.cs 並修改 Data/ApplicationDbContext.cs 因為要客製化Identity裡面的欄位 基本上會需要需要重新寫IdentityDbContext並使用自訂的Models(ApplicationRole,ApplicationUser,ApplicationUserRole) 在客製化之後,要注意若原先有使用IdentityUser、IdentityRole處,要都修改為ApplicationUser及ApplicationRole 搜尋 UserManager<IdentityUser> 並取代為 UserManager<ApplicationUser> 搜尋 SignInManager<IdentityUser> 並取代為 SignInManager<ApplicationUser> 搜尋 RoleManager<IdentityRole> 並取代為 RoleManager<ApplicationRole> 原先的new IdentityUser會不能用 要改為new ApplicationUser ```csharp= // 於register時創立使用者的new IdentityUser會無法使用 var user = new IdentityUser { UserName = Input.Email, Email = Input.Email }; // 修正為 var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, Discriminator = "ApplicationUser" }; ``` ```csharp= // Models/ApplicationRole.cs using System.Collections.Generic; using Microsoft.AspNetCore.Identity; using Newtonsoft.Json; namespace BaseAdmin.Models { public class ApplicationRole : IdentityRole<string> { public ApplicationRole() : base() { } public ApplicationRole(string name) : base(name) { } [JsonIgnore] public virtual ICollection<ApplicationUserRole> UserRoles { get; set; } } } ``` ```csharp= // Models/ApplicationUser.cs using System.Collections.Generic; using BaseAdmin.Models; using Microsoft.AspNetCore.Identity; using Newtonsoft.Json; namespace BaseAdmin.Models { public class ApplicationUser : IdentityUser<string> { public ApplicationUser() : base() { } public ApplicationUser(string name) : base(name) { } public string Discriminator { get; set; } public virtual ICollection<ApplicationUserRole> UserRoles { get; set; } } } ``` ```csharp= // Models/ApplicationUserRole.cs using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; namespace BaseAdmin.Models { [Keyless] public class ApplicationUserRole : IdentityUserRole<string> { [JsonIgnore] public virtual ApplicationUser User { get; set; } public virtual ApplicationRole Role { get; set; } } } ``` --- 搬移wwwroot內檔案 並於wwwroot中先新增資料夾/shared/hyweb/ --- 搬移整個Areas/Admin 要記得改namespace --- 搬移自製的role permission,於areas/identity中 修改startup.cs 修正所有的 <IdentityUser> 改為 <ApplicationUser> 修正於Register.cshtml的OnPostAsync ```shell= var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, Discriminator = "ApplicationUser" }; ``` ## 更新資料庫 ```shell= sudo dotnet ef migrations add CreateIdentity --context ApplicationDbContext sudo dotnet ef migrations add CreateManagement --context HywebContext sudo dotnet ef database update --context ApplicationDbContext sudo dotnet ef database update --context HywebContext ``` ## 功能: 所見及所得編輯器+圖片管理(Editor and image management) 基本上是基於Telerik的Editor+Image Manager去進行修改 原先的版本為檔案都是放在同一處,改版為 個別Controller及ID 單獨一個圖片上傳資料夾 簡單來說就是,一則最新消息,會有它自己的一個圖片上傳的資料夾 以資料夾結構來看的話 就是 /shared/hyweb/ ## 功能: 角色權限管理(Role, Permission and Policy) 首先在功能需求上,主要是要"新增角色並給予功能權限" 關係如下 使用者<->角色<->權限 權限部分是寫死的,角色可以自由新增,角色可以自訂功能權限,而使用者只能有一個角色 功能權限部分因為專案需求,所以只有限定到大項目的部分,例如最新消息管理、XXX管理 寫得更細可以再細到操作(Action)/特定項目(ID) --- 接下來來介紹今天的主軸,如何從資料庫中取得並判斷要不要讓他過 參考 [這篇文章](https://stackoverflow.com/questions/41992845/loading-asp-net-core-authorization-policy-from-database) 基本上是使用到Policy,而原先的Identity Scaffold出來的部分沒有可以讓你記錄到那麼細的,所以我有在自己補了一個表去紀錄Role-Permission的,在這張表中關鍵大概就是Role對應到你可以到的Area, Controller(也可以更細到Action,Id) 這個答主他在後面ThePolicyRequirement Pass的部分沒有全部寫完,那邊基本上就是要你去撈資料庫中的RolePermission,判斷這個使用者是否能進到這個Area,Controller,Action,Id 我在這還有多用到了userManager跟roleManager,然後在自己手寫一個去取得J個使用者的Permission List 在這會有個缺點就是基本上每次都還要去資料庫撈一次,才能確定他有沒有這個權限(大概是好也是壞吧),這邊我有再多做了一個Memory Cache去加強,未來這個取得Permission的部分應該可以做成Service在DI近來,因為還有很多地方都會用到"取得該使用者Permission"的功能,譬如動態的Navigation Bar,要去判斷該用者Permission再去顯示Nav上的功能,不能去的地方就不要顯示給他了。 另外如果修改Role-Permission時,也要記得對該cache進行Update或直接remove掉。 --- 再來就是Identity自訂的部分,可以參考[這篇](https://docs.microsoft.com/zh-tw/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-5.0#the-identity-model) 最需要注意的坑是,之前很多地方都是用IdentityUser 特別是像是在UserManager<IdentityUser>這種,都要全部改掉,包含SignInManager, RoleManager 如果你沒改掉,會出現這個錯誤 > InvalidOperationException: No service for type 'Microsoft.AspNetCore.Identity.UserManager [Microsoft.AspNetCore.Identity.IdentityUser]' has been registered. 解法源自 [這篇](https://stackoverflow.com/questions/52568264/no-service-for-type-microsoft-aspnetcore-identity-usermanager1microsoft-aspne?fbclid=IwAR3PzwDljeF_86qQOl-0pm0oIqgu0J2JC-93jA-6g08zFnExBxg4BdeXEBw) # Q&A ## 什麼是SDK ## 什麼是Package, Package source ## 為什麼跑 dotnet watch run就可以架設起來專案? ## 什麼是interface ## 什麼是service ## 什麼是DI ## Model / DBcontext / Dbset / Migrations 之間的關係是...? ## MVC架構 vs Razor Pages架構 ## 於controller中,如何接收get參數、post參數、files ## PartialView vs ViewComponent ## ViewData vs ViewBag vs TempData ## Code-First vs DB-First Code-First指的是... DB-First指的是 ## DB seed 資料植入 https://docs.microsoft.com/zh-tw/ef/core/modeling/data-seeding ## Why use migrations? 使用EF Core 走 migrations 升級就目前我們來講就四個好處 1. 每次異動 Model 並產製 migrations 時可以看實際 db 會怎麼被異動 2. 透過 git commit 可以知道 table or column 的異動歷史 (雖然 model 也可以) 3. 假設 db 都放在各自的 localdb 時可以快速同步 schema 4. 有 migrations 可以快速的升降到特定版本 ( 剛剛 hanks 的範例裡面的 Up() Down() ) 但衍生問題是 假設兩個人都在 localdb 開發 但又對同一張表同一個欄位做異動時 產出來的 migrations 的確是有可能有問題 (migrations 執行順序可能要查一下官方文件) 但目前尚未遇到此情況 遇到應該也是把會異動的人+PM 抓出來開會確認 ## 如果是code first,但客戶的DB方面不允許直接操作到DB的話怎麼辦? 關於 Code first 的部分 目前有 .net 這邊的案子是都允許我們手動或透過程式去異動 db schema 假設正式環境的帳號權限不夠時 那一樣回到 code first 更早之前的做法 自己寫 script 或是透過 SSMS 產生 script 後交由對方的 DBA 確認並執行異動 至於怎麼知道要包哪些 script 給對方則可以透過 git 去查這段期間的異動 ## 權限部分,為什麼要再加上Policy做擴充? 原生的 Identity 提供最簡單的授權方式是角色授權(寫死 無法動態調整) 實際上不太符合需求 所以 Identity 有留 Policy 的洞讓開發者可以自行去擴充、調整 也就是 Hanks 現在自己改良的版本 多了 Role <> Permissions 這層關係 程式先定義好有哪些 Permissions 後 讓管理者可以在後台直接調整權限後立即生效 如果覺得 Identity 提供的 Policy 或 middleware 等洞都覺得不好用 自己打造一套 cookie based authentication 也是可以 就看各專案的使用情境 ## Telerik Kendo 資安的部分 買完後telerik會給我們所有 component 的 source code 另外他們也有 support ticket service (我們買的等級應該是 24hr 內要回復) 可以把我們有疑慮的地方回報給他們,請他們進行修復 他們也會定期做第三方掃描及修正 買完後若有更新,可以於一年內可以免費升級 保固過後的重大安全更新就要再查看看