# [112]天方科技 ASP.net core 教育訓練 1120425(瀏覽器儲存空間、身分驗證方式比較、角色驗證設定、Stored Procedure建立) ## 瀏覽器儲存空間 ### Cookie ─ 預設是關閉瀏覽器後失效 HTTP cookie(web cookie、browser cookie)為伺服器傳送給使用者瀏覽器的一個小片段資料。瀏覽器可能儲存並在下一次請求回傳 cookie 至相同的伺服器。Cookie 通常被用來保持使用者的登入狀態 — — 如果兩次請求都來自相同的瀏覽器。有個數限制(各瀏覽器不同),一般不能超過20個 ### LocalStorage ─ 一直將資料儲存在客戶端本地,不會過期,除非手動清除 ### SessionStorage ─ 將資料儲存在session中,每次分頁或瀏覽器關掉後就會清除 生命周期只存在瀏覽器的單一分頁,也就是另開新分頁的話,又是一個新的sessionStorage,預設無逾期時間,除非關閉該分頁、關閉瀏覽器等,sessionStorage就會消失。 ### 差異 ![](https://i.imgur.com/xG3UDZk.png) 參考: [**[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 ![](https://i.imgur.com/mKVhVmI.png) ### 點擊上方按鈕或是`Ctrl+Shift+M`,可以開啟Specify Values for Template Parameters視窗,可修改需要的參數 ![](https://i.imgur.com/lV7euUR.png) ![](https://i.imgur.com/IeOWiop.png) ### 建立`up_getCmparea` Tables的物件可以被拖曳至語法中,生成Tables或是Columns名稱 ![](https://i.imgur.com/amGIhgX.png) ```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 ``` ### 執行 ![](https://i.imgur.com/K1YBjpk.png) ### 成功時會在Messages視窗出現completed successfully訊息 ![](https://i.imgur.com/prrG6iI.png) ![](https://i.imgur.com/I3R10b0.png) ## 使用EF Tool在專案建立Stored Procedure相關物件 ### 執行反向工程,先使用原先建立的Demo專案 ![](https://i.imgur.com/dLrV44P.png) ### 在設定的`Advanced...`可以讓DBContext文件放在別的資料夾,但是在sub-namespace欄位還是要跟EntityTypes的名稱一樣,避免使用上的混亂 ![](https://i.imgur.com/bSX8WOf.png) ![](https://i.imgur.com/wyRgggZ.png) ### Datas多出了三個與Stored Procedure相關的DBContext文件,Models新增先前建立的Stored Procedure ![](https://i.imgur.com/SK8RuZ0.png) ### 在原先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` ![](https://i.imgur.com/Ybovgwp.png) ### 新增`[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)