# 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

接下來使用該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

### TablePlus連線設定
點選下方Create a new connection,建立新的資料庫連線,

選擇Microsoft SQL Server...



TablePlus 也有一些Plugins可以使用
如[Diagram Generator](https://github.com/TablePlus/TablePlus/issues/44#issuecomment-668054912)

# 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來源

再依照來源新增package

新增完後就可以不用使用到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>
```

## 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
```




剛註冊完要經過email認證,如果沒有經過認證會無法登入
可以自行於資料庫中修改欄位


# 基礎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檔案

之後就可以執行以下指令來進行更新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://localhost:5001/Job/Create

編輯頁
https://localhost:5001/Job/Edit/2

## 功能擴充
### 於[列表頁]加上分頁、搜尋
參考:
原生作法
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 內要回復)
可以把我們有疑慮的地方回報給他們,請他們進行修復
他們也會定期做第三方掃描及修正
買完後若有更新,可以於一年內可以免費升級
保固過後的重大安全更新就要再查看看