DELETE請求以及HTTP請求冪等性 使用.NET 6實現DELETE請求以及HTTP請求冪等性
使用.NET 6實現DELETE請求以及HTTP請求冪等性
系列導航及原始碼
需求
先說明一下關於原本想要去更新的PATCH
請求的文章,從目前試驗的情況來看,如果是按照.NET 6
的專案結構(即只使用一個Program.cs
完成程式初始化),那微軟官方給出的文件目前還沒有對應地更新,按照之前的方式進行JsonPatch
的配置是不行的,目前已經有人在Github微軟的官方文件Repo下提了ISSUE: .NET 6: JsonPatch in ASP.NET Core web API。並且因為PATCH
的使用頻率並不高,所以我暫時跳過那篇,先把進度繼續往後走,看微軟什麼時候把這個issue解決一下我再看情況把PATCH
本文我們來看最後一個常用HTTP請求型別:DELETE
。
目標
實現並驗證應用正確處理DELETE
請求。並對HTTP請求的冪等性做簡單的介紹。
原理與思路
經過關於Create、Update、Get的實現,對於Delete的實現我們的思路是很清晰的。我們需要建立Delete的Command及其Handler,然後在Controller中通過Mediatr傳送請求即可。
實現
在Application/TodoList
下新建DeleteTodoList
資料夾,並新建DeleteTodoListCommand
:
DeleteTodoListCommand.cs
using MediatR;
using TodoList.Application.Common.Exceptions;
using TodoList.Application.Common.Interfaces;
namespace TodoList.Application.TodoLists.Commands.DeleteTodoList;
public class DeleteTodoListCommand : IRequest
{
public Guid Id { get; set; }
}
public class DeleteTodoListCommandHandler : IRequestHandler<DeleteTodoListCommand>
{
private readonly IRepository<Domain.Entities.TodoList> _repository;
public DeleteTodoListCommandHandler(IRepository<Domain.Entities.TodoList> repository)
{
_repository = repository;
}
public async Task<Unit> Handle(DeleteTodoListCommand request, CancellationToken cancellationToken)
{
var entity = await _repository.GetAsync(request.Id);
if (entity == null)
{
throw new NotFoundException(nameof(TodoList), request.Id);
}
await _repository.DeleteAsync(entity,cancellationToken);
// 對於Delete操作,演示中並不返回任何實際的物件,可以結合實際需要返回特定的物件。Unit物件在MediatR中表示Void
return Unit.Value;
}
}
在Controller中新增Delete的介面處理:
TodoListController.cs
// 省略其他...
[HttpDelete("{id:guid}")]
public async Task<ApiResponse<object>> Delete(Guid id)
{
return ApiResponse<object>.Success(await _mediator.Send(new DeleteTodoListCommand { Id = id }));
}
這裡可能值得強調的是關於EntityFrameworkCore
中對於關聯實體DELETE
開啟Infrastructure/Migrations
資料夾,我們可以在遷移領域實體的那次Migration生成的.Designer.cs
檔案中發現這樣一段配置:
// 省略其他...
modelBuilder.Entity("TodoList.Domain.Entities.TodoItem", b =>
{
b.HasOne("TodoList.Domain.Entities.TodoList", "List")
.WithMany("Items")
.HasForeignKey("ListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("List");
});
可以看到在OnDelete
中配置的是DeleteBehavior.Cascade
行為模式,關於DeleteBehavior
,可以參考Referential Constraint Action Options。實際上總共有七種可以設定的行為模式:
DeleteBehavior.Cascade
DeleteBehavior.NoAction
DeleteBehavior.Restrict
DeleteBehavior.SetNull
DeleteBehavior.ClientCascade
DeleteBehavior.ClientNoAction
DeleteBehavior.ClientSetNull
關於這七種DeleteBehavior
,可以參考這篇博文:Entity Framework Core 關聯刪除,博主在其中進行了比較詳細的實驗和總結。
可以根據實際需要去顯式地配置DeleteBehavior
。另外,習慣觀察生成的Migrations檔案,也是學習EFCore一些慣例或者說預設配置的很好的方法。
驗證
啟動Api
專案,傳送DELETE
請求:
-
請求
-
響應
並且從資料庫裡我們以可以發現,這條TodoList
下包含的TodoItem
也被一同刪除了。
總結
到此為止HTTP常用的四大請求我們已經通過幾個例子講完了,關於HEAD
請求OPTION
請求以及遺留的PATCH
請求後面會寫完。下一篇文章開始我們一起學習如何使用FluentValidation
來進行請求引數校驗。
關於HTTP請求冪等性的介紹
首先明確兩個概念:
- 什麼叫做HTTP請求是否安全:如果我們執行請求後,對應的資源實體不發生改變,我們稱這個請求是安全的;
- 什麼叫做HTTP請求是否冪等:對於執行請求後產生的副作用(即指如果請求不安全,則稱其會產生副作用),請求執行一次和執行多次的副作用是相同的,我們稱這個請求是冪等的,很顯然安全的請求一定是冪等的。
瞭解了這兩個概念後,我們直接來看這張表格,快速地對HTTP請求的安全性和冪等性有一個認識。
HTTP方法 | 是否安全? | 是否具有冪等性? |
---|---|---|
GET | 是 | 是 |
POST | 否 | 否 |
PUT | 否 | 是* |
DELETE | 否 | 是 |
OPTIONS | 是 | 是 |
HEAD | 是 | 是 |
PATCH | 否 | 否 |
鑑於PATCH方法並不常用,那麼重點需要關注的主要就是POST
請求,以及一部分的PUT
請求(比如更新的欄位是在當前欄位值的基礎上進行計算而新得出這種場景,實際就不是冪等的,但是我們一般不推薦在Update時做這種型別的操作,更推薦的是把計算邏輯前置到介面響應處理前,以整體設定值的方式去Update實體)。一般而言POST
請求是用來建立資源的,如果不採取某種方式來保證執行結果的實際冪等性,那麼該請求產生的副作用將是難以控制和處理的。
如何保證介面的冪等性?
正式因為並非所有的HTTP請求(在這裡我們可以泛化到任意型別的介面請求)都是冪等的,而不管是應用程式內的容錯還是服務之間因為分割槽導致的對請求的冪等性更為嚴格的要求(尤其是在分散式系統中,對於分割槽導致的請求重試的場景),我們需要在設計和實現介面的時候,把冪等性設計考慮進來,提高介面的魯棒性。
總體來說,實現介面的冪等性有兩種思路:一種是通過程式碼邏輯去限制重複調用出現的副作用;第二種是通過Token唯一性來保證同一個請求不會被呼叫多次。
通過程式碼邏輯去限制副作用的實現方式有很多種:從前端/介面的層次上去人為限制請求的重複傳送(比如按鈕置灰禁止點選之類的),通過在資料庫層面應用鎖或使用唯一索引,通過在邏輯執行過程中應用鎖等方式。這種方式只能針對一些通過滿足某些判斷進行的邏輯實現,有其侷限性。
通過Token唯一性保證冪等的思路大致是這樣:生成全域性唯一的Token儲存下來,並在前端頁面獲取儲存,傳送請求時連同Token一起發到後端,後端先進行Token校驗,校驗通過傳送實際請求或執行邏輯,完成後刪除舊Token並生成新Token,那麼前端下次攜帶儲存的舊Token來請求時,因為Token校驗不通過而拒絕繼續執行。這種方式就好比簡訊驗證碼,只有第一次攜帶這個驗證碼請求時會成功,後端判斷第一次請求有效後就會把這個驗證碼置為無效,下次你就無法攜帶相同的驗證碼繼續發起請求了。例子不是特別恰當,但是可以類比著進行理解。
參考資料
系列導航及原始碼
需求
先說明一下關於原本想要去更新的PATCH
請求的文章,從目前試驗的情況來看,如果是按照.NET 6
的專案結構(即只使用一個Program.cs
完成程式初始化),那微軟官方給出的文件目前還沒有對應地更新,按照之前的方式進行JsonPatch
的配置是不行的,目前已經有人在Github微軟的官方文件Repo下提了ISSUE: .NET 6: JsonPatch in ASP.NET Core web API。並且因為PATCH
的使用頻率並不高,所以我暫時跳過那篇,先把進度繼續往後走,看微軟什麼時候把這個issue解決一下我再看情況把PATCH
那一節補上。
本文我們來看最後一個常用HTTP請求型別:DELETE
。
目標
實現並驗證應用正確處理DELETE
請求。並對HTTP請求的冪等性做簡單的介紹。
原理與思路
經過關於Create、Update、Get的實現,對於Delete的實現我們的思路是很清晰的。我們需要建立Delete的Command及其Handler,然後在Controller中通過Mediatr傳送請求即可。
實現
在Application/TodoList
下新建DeleteTodoList
資料夾,並新建DeleteTodoListCommand
:
DeleteTodoListCommand.cs
using MediatR;
using TodoList.Application.Common.Exceptions;
using TodoList.Application.Common.Interfaces;
namespace TodoList.Application.TodoLists.Commands.DeleteTodoList;
public class DeleteTodoListCommand : IRequest
{
public Guid Id { get; set; }
}
public class DeleteTodoListCommandHandler : IRequestHandler<DeleteTodoListCommand>
{
private readonly IRepository<Domain.Entities.TodoList> _repository;
public DeleteTodoListCommandHandler(IRepository<Domain.Entities.TodoList> repository)
{
_repository = repository;
}
public async Task<Unit> Handle(DeleteTodoListCommand request, CancellationToken cancellationToken)
{
var entity = await _repository.GetAsync(request.Id);
if (entity == null)
{
throw new NotFoundException(nameof(TodoList), request.Id);
}
await _repository.DeleteAsync(entity,cancellationToken);
// 對於Delete操作,演示中並不返回任何實際的物件,可以結合實際需要返回特定的物件。Unit物件在MediatR中表示Void
return Unit.Value;
}
}
在Controller中新增Delete的介面處理:
TodoListController.cs
// 省略其他...
[HttpDelete("{id:guid}")]
public async Task<ApiResponse<object>> Delete(Guid id)
{
return ApiResponse<object>.Success(await _mediator.Send(new DeleteTodoListCommand { Id = id }));
}
這裡可能值得強調的是關於EntityFrameworkCore
中對於關聯實體DELETE
操作的處理方式:
開啟Infrastructure/Migrations
資料夾,我們可以在遷移領域實體的那次Migration生成的.Designer.cs
檔案中發現這樣一段配置:
// 省略其他...
modelBuilder.Entity("TodoList.Domain.Entities.TodoItem", b =>
{
b.HasOne("TodoList.Domain.Entities.TodoList", "List")
.WithMany("Items")
.HasForeignKey("ListId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("List");
});
可以看到在OnDelete
中配置的是DeleteBehavior.Cascade
行為模式,關於DeleteBehavior
,可以參考Referential Constraint Action Options。實際上總共有七種可以設定的行為模式:
DeleteBehavior.Cascade
DeleteBehavior.NoAction
DeleteBehavior.Restrict
DeleteBehavior.SetNull
DeleteBehavior.ClientCascade
DeleteBehavior.ClientNoAction
DeleteBehavior.ClientSetNull
關於這七種DeleteBehavior
,可以參考這篇博文:Entity Framework Core 關聯刪除,博主在其中進行了比較詳細的實驗和總結。
可以根據實際需要去顯式地配置DeleteBehavior
。另外,習慣觀察生成的Migrations檔案,也是學習EFCore一些慣例或者說預設配置的很好的方法。
驗證
啟動Api
專案,傳送DELETE
請求:
-
請求
-
響應
並且從資料庫裡我們以可以發現,這條TodoList
下包含的TodoItem
也被一同刪除了。
總結
到此為止HTTP常用的四大請求我們已經通過幾個例子講完了,關於HEAD
請求OPTION
請求以及遺留的PATCH
請求後面會寫完。下一篇文章開始我們一起學習如何使用FluentValidation
來進行請求引數校驗。
關於HTTP請求冪等性的介紹
首先明確兩個概念:
- 什麼叫做HTTP請求是否安全:如果我們執行請求後,對應的資源實體不發生改變,我們稱這個請求是安全的;
- 什麼叫做HTTP請求是否冪等:對於執行請求後產生的副作用(即指如果請求不安全,則稱其會產生副作用),請求執行一次和執行多次的副作用是相同的,我們稱這個請求是冪等的,很顯然安全的請求一定是冪等的。
瞭解了這兩個概念後,我們直接來看這張表格,快速地對HTTP請求的安全性和冪等性有一個認識。
HTTP方法 | 是否安全? | 是否具有冪等性? |
---|---|---|
GET | 是 | 是 |
POST | 否 | 否 |
PUT | 否 | 是* |
DELETE | 否 | 是 |
OPTIONS | 是 | 是 |
HEAD | 是 | 是 |
PATCH | 否 | 否 |
鑑於PATCH方法並不常用,那麼重點需要關注的主要就是POST
請求,以及一部分的PUT
請求(比如更新的欄位是在當前欄位值的基礎上進行計算而新得出這種場景,實際就不是冪等的,但是我們一般不推薦在Update時做這種型別的操作,更推薦的是把計算邏輯前置到介面響應處理前,以整體設定值的方式去Update實體)。一般而言POST
請求是用來建立資源的,如果不採取某種方式來保證執行結果的實際冪等性,那麼該請求產生的副作用將是難以控制和處理的。
如何保證介面的冪等性?
正式因為並非所有的HTTP請求(在這裡我們可以泛化到任意型別的介面請求)都是冪等的,而不管是應用程式內的容錯還是服務之間因為分割槽導致的對請求的冪等性更為嚴格的要求(尤其是在分散式系統中,對於分割槽導致的請求重試的場景),我們需要在設計和實現介面的時候,把冪等性設計考慮進來,提高介面的魯棒性。
總體來說,實現介面的冪等性有兩種思路:一種是通過程式碼邏輯去限制重複調用出現的副作用;第二種是通過Token唯一性來保證同一個請求不會被呼叫多次。
通過程式碼邏輯去限制副作用的實現方式有很多種:從前端/介面的層次上去人為限制請求的重複傳送(比如按鈕置灰禁止點選之類的),通過在資料庫層面應用鎖或使用唯一索引,通過在邏輯執行過程中應用鎖等方式。這種方式只能針對一些通過滿足某些判斷進行的邏輯實現,有其侷限性。
通過Token唯一性保證冪等的思路大致是這樣:生成全域性唯一的Token儲存下來,並在前端頁面獲取儲存,傳送請求時連同Token一起發到後端,後端先進行Token校驗,校驗通過傳送實際請求或執行邏輯,完成後刪除舊Token並生成新Token,那麼前端下次攜帶儲存的舊Token來請求時,因為Token校驗不通過而拒絕繼續執行。這種方式就好比簡訊驗證碼,只有第一次攜帶這個驗證碼請求時會成功,後端判斷第一次請求有效後就會把這個驗證碼置為無效,下次你就無法攜帶相同的驗證碼繼續發起請求了。例子不是特別恰當,但是可以類比著進行理解。