# [112]天方科技 ASP.net core 教育訓練 1120425(瀏覽器儲存空間、身分驗證方式比較、角色驗證設定、Stored Procedure建立)
## 瀏覽器儲存空間
### Cookie ─ 預設是關閉瀏覽器後失效
HTTP cookie(web cookie、browser cookie)為伺服器傳送給使用者瀏覽器的一個小片段資料。瀏覽器可能儲存並在下一次請求回傳 cookie 至相同的伺服器。Cookie 通常被用來保持使用者的登入狀態 — — 如果兩次請求都來自相同的瀏覽器。有個數限制(各瀏覽器不同),一般不能超過20個
### LocalStorage ─ 一直將資料儲存在客戶端本地,不會過期,除非手動清除
### SessionStorage ─ 將資料儲存在session中,每次分頁或瀏覽器關掉後就會清除
生命周期只存在瀏覽器的單一分頁,也就是另開新分頁的話,又是一個新的sessionStorage,預設無逾期時間,除非關閉該分頁、關閉瀏覽器等,sessionStorage就會消失。
### 差異

參考:
[**[JavaScript] Cookie、LocalStorage、SessionStorage 差異**](https://medium.com/@bebebobohaha/cookie-localstorage-sessionstorage-%E5%B7%AE%E7%95%B0-9e1d5df3dd7f)
## 身分驗證方式比較
### Cookie驗證 VS Session驗證
1. 安全性:Session儲存在Server端,Cookie儲存在Client端,Session較為安全
2. 儲存類型:Cookie只支持字符串,Session可存為任意資料
3. 有效期限:Cookie可設為長時間保持,Session時效較短
4. 儲存大小:單個Cookie資料保存不能超過4K,Session儲存資料遠超Cookie,但是存取量過多會占用Server端資源
### Token VS Session
1. Session會記錄Server端和Client端的通訊狀態,以及通訊紀錄;Token是存取API的標記,不會儲存通訊紀錄
2. 系統可同時擁有Session和Token,Token在身分驗證上安全性較佳,Session可以記錄通訊狀態
**所以當使用者資料需要和第三方共享,或是允許第三方存取API,就使用Token**
### JWT VS Token
1. Server端驗證Client端發送的Token時,需要查詢DB內的使用者訊息,驗證是否有效
2. JWT將Token和Payload加密儲存於Client端,Server端只需要透過金鑰解密,不需要查詢DB
參考:
[**Cookie、Session、Token、JWT 看一篇就够了**](https://blog.51cto.com/u_15300443/3167467#%E4%BB%80%E4%B9%88%E6%98%AF%E8%AE%A4%E8%AF%81%EF%BC%88Authentication%EF%BC%89)
## API角色驗證設定
### 依登入使用者不同給予不同角色
建議搭配`Switch`語法,或是使用`Linq`語法,避免一直`new`型別物件
```csharp=
[HttpPost]
[AllowAnonymous]
public IActionResult Login(DtoLogin dtoLogin)
{
if((dtoLogin.Name != "clone" && dtoLogin.Name != "eric") || dtoLogin.Password != "pwd") {
return BadRequest("Login Fail");
}
var roles = new List<string> //login的角色身分
{
"Admins",
"clone"
};
if(dtoLogin.Name == "eric") roles = new List<string>{ "Users"}; //使用者不同時給予不同身分
var token = CreateToken(dtoLogin, roles);
return Ok(token);
}
```
### 可以透過設定角色在同一個Action裡面做出權限分別
```csharp=
[HttpGet]
[Authorize(Roles = "Admins, Users")] //身分驗證,如果沒有指定角色,只要通過驗證就能使用API,有區分大小寫
public async Task<ActionResult<IEnumerable<s30_student>>> Gets30_student()
{
//依角色做不同的事情
if (User.IsInRole("Admins") || User.IsInRole("Users"))
{
return await _context.s30_student.Include(a => a.cls).ToListAsync();
}
if (_context.s30_student == null)
{
return NotFound();
}
//return await _context.s30_student.ToListAsync();
return await _context.s30_student.Skip(1).Take(2).ToListAsync(); //省略1筆,取2筆
}
```
## SQL Server Stored Procedure建立
### DB"edu"→Programmability→Stored Procedures→New→Stored Procedures...
建立新的Stored Procedure

### 點擊上方按鈕或是`Ctrl+Shift+M`,可以開啟Specify Values for Template Parameters視窗,可修改需要的參數


### 建立`up_getCmparea`
Tables的物件可以被拖曳至語法中,生成Tables或是Columns名稱

```sql=
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
ALTER PROCEDURE [dbo].[up_getCmparea]
AS
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT [cmp_area], [cmp_name_abr], [cmp_name_full], [upd_name], [upd_dt]
From s90_cmparea
```
### 執行

### 成功時會在Messages視窗出現completed successfully訊息


## 使用EF Tool在專案建立Stored Procedure相關物件
### 執行反向工程,先使用原先建立的Demo專案

### 在設定的`Advanced...`可以讓DBContext文件放在別的資料夾,但是在sub-namespace欄位還是要跟EntityTypes的名稱一樣,避免使用上的混亂


### Datas多出了三個與Stored Procedure相關的DBContext文件,Models新增先前建立的Stored Procedure

### 在原先DBContext的`OnModelCreating()`裡,最下方多出`OnModelCreatingGeneratedProcedures();`,此方法建立了Stored Procedure的EntityType
```csharp=
//eduContext.cs
OnModelCreatingGeneratedProcedures(modelBuilder);
//eduContextProcedures.cs
protected void OnModelCreatingGeneratedProcedures(ModelBuilder modelBuilder)
{
modelBuilder.Entity<up_getCmpareaResult>().HasNoKey().ToView(null);
modelBuilder.Entity<up_getStudentResult>().HasNoKey().ToView(null);
}
```
### `IeduContextProcedures.cs`創建`Interface`
如果Stored Procedure沒有設定參數`function(參數數量 型別, 可否取消Token(預設為不行,非必要設定))`
```csharp=
public partial interface IeduContextProcedures
{
//使用了Task類別,代表是非同步方法
Task<List<up_getCmpareaResult>> up_getCmpareaAsync(OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default);
//有參數
Task<List<up_getCmpareaResult>> up_getCmpareaAsync(int? p1, string? p2, OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default);
}
```
### `DbContextExtensions.cs`,DbContext擴充功能
`SqlQueryAsync()`是EF Tool產生的方法,依不同狀態執行不同的SQL操作指令
`FromSqlRaw()`:根據原始 SQL 查詢建立 LINQ 查詢,來源要是`DbSet<TEntity>`(需Model class),回傳`<TEntity>`資料
`ExecuteSqlRaw()`:針對資料庫執行指定的 SQL,並傳回受影響的資料列**數目**,來源是DB物件(不需Model class)
```csharp=
public static async Task<List<T>> SqlQueryAsync<T>(this DbContext db, string sql, object[] parameters = null, CancellationToken cancellationToken = default) where T : class
{
if (parameters is null)
{
parameters = new object[] { };
}
if (typeof(T).GetProperties().Any())
{
return await db.Set<T>().FromSqlRaw(sql, parameters).ToListAsync(cancellationToken);
}
else
{
await db.Database.ExecuteSqlRawAsync(sql, parameters, cancellationToken);
return default;
}
}
```
參考:
[**ExecuteSqlRaw 方法**](https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.executesqlraw?view=efcore-7.0)
[**FromSqlRaw< TEntity > 方法**](https://learn.microsoft.com/zh-tw/dotnet/api/microsoft.entityframeworkcore.relationalqueryableextensions.fromsqlraw?view=efcore-7.0#microsoft-entityframeworkcore-relationalqueryableextensions-fromsqlraw-1(microsoft-entityframeworkcore-dbset((-0))-system-string-system-object()))
[**SQL查詢**](https://learn.microsoft.com/zh-tw/ef/core/querying/sql-queries)
### `eduContextProcedures.cs`,將IeduContextProcedures`實體化
```csharp=
public partial class eduContext
{
private IeduContextProcedures _procedures;
public virtual IeduContextProcedures Procedures
{
get
{
if (_procedures is null) _procedures = new eduContextProcedures(this);
return _procedures;
}
set
{
_procedures = value;
}
}
public IeduContextProcedures GetProcedures()
{
return Procedures;
}
protected void OnModelCreatingGeneratedProcedures(ModelBuilder modelBuilder)
{
modelBuilder.Entity<up_getCmpareaResult>().HasNoKey().ToView(null);
}
}
public partial class eduContextProcedures : IeduContextProcedures
{
private readonly eduContext _context;
public eduContextProcedures(eduContext context)
{
_context = context;
}
public virtual async Task<List<up_getCmpareaResult>> up_getCmpareaAsync(OutputParameter<int> returnValue = null, CancellationToken cancellationToken = default)
{
//interface參數實體設定
var parameterreturnValue = new SqlParameter
{
ParameterName = "returnValue",
Direction = System.Data.ParameterDirection.Output,
SqlDbType = System.Data.SqlDbType.Int,
};
var sqlParameters = new []
{
parameterreturnValue,
};
var _ = await _context.SqlQueryAsync<up_getCmpareaResult>("EXEC @returnValue = [dbo].[up_getCmparea]", sqlParameters, cancellationToken);
returnValue?.SetValue(parameterreturnValue.Value);
return _;
}
}
```
### 創建`cmpareaController.cs`

### 新增`[HttpGet("sp")]`Action,呼叫Stored Procedure
```csharp=
[HttpGet("sp")]
public async Task<ActionResult<IEnumerable<up_getCmpareaResult>>> up_getCompare()
{
return await _context.Procedures.up_getCmpareaAsync();
}
```
## MERGE語法
根據與來源資料表聯結的結果,在目標資料表上執行插入、更新或刪除作業,當兩個資料表有複雜的比對的特性時,MERGE 陳述式的條件式行為表現最佳
參考:
[**MERGE (Transact-SQL)**](https://learn.microsoft.com/zh-tw/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver16)