系列导航及源代码

需求

先说明一下关于原本想要去更新的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请求幂等性的介绍

首先明确两个概念:

  1. 什么叫做HTTP请求是否安全:如果我们执行请求后,对应的资源实体不发生改变,我们称这个请求是安全的;
  2. 什么叫做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校验不通过而拒绝继续执行。这种方式就好比短信验证码,只有第一次携带这个验证码请求时会成功,后端判断第一次请求有效后就会把这个验证码置为无效,下次你就无法携带相同的验证码继续发起请求了。例子不是特别恰当,但是可以类比着进行理解。

参考资料

  1. Referential Constraint Action Options
  2. Entity Framework Core 关联删除
  3. 接口的幂等性,如何保证

使用.NET 6开发TodoList应用(10)——实现DELETE请求以及HTTP请求幂等性的更多相关文章

  1. 使用.NET 6开发TodoList应用(3)——引入第三方日志库

    需求 在我们项目开发的过程中,使用.NET 6自带的日志系统有时是不能满足实际需求的,比如有的时候我们需要将日志输出到第三方平台上,最典型的应用就是在各种云平台上,为了集中管理日志和查询日志,通常会选 ...

  2. 使用.NET 6开发TodoList应用(1)——系列背景

    前言 想到要写这样一个系列博客,初衷有两个:一是希望通过一个实践项目,将.NET 6 WebAPI开发的基础知识串联起来,帮助那些想要入门.NET 6服务端开发的朋友们快速上手,对使用.NET 6开发 ...

  3. 使用.NET 6开发TodoList应用(2)——项目结构搭建

    为了不影响阅读的体验,我把系列导航放到文章最后了,有需要的小伙伴可以直接通过导航跳转到对应的文章 : P TodoList需求简介 首先明确一下我们即将开发的这个TodoList应用都需要完成什么功能 ...

  4. 使用.NET 6开发TodoList应用(4)——引入数据存储

    需求 作为后端CRUD程序员(bushi,数据存储是开发后端服务一个非常重要的组件.对我们的TodoList项目来说,自然也需要配置数据存储.目前的需求很简单: 需要能持久化TodoList对象并对其 ...

  5. 使用.NET 6开发TodoList应用(5)——领域实体创建

    需求 上一篇文章中我们完成了数据存储服务的接入,从这一篇开始将正式进入业务逻辑部分的开发. 首先要定义和解决的问题是,根据TodoList项目的需求,我们应该设计怎样的数据实体,如何去进行操作? 长文 ...

  6. 使用.NET 6开发TodoList应用(5.1)——实现Repository模式

    需求 经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景.有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是 ...

  7. 使用.NET 6开发TodoList应用(6)——使用MediatR实现POST请求

    需求 需求很简单:如何创建新的TodoList和TodoItem并持久化. 初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法:实用传入的请求参数new一个数据库实 ...

  8. 使用.NET 6开发TodoList应用文章索引

    系列导航 使用.NET 6开发TodoList应用(1)--系列背景 使用.NET 6开发TodoList应用(2)--项目结构搭建 使用.NET 6开发TodoList应用(3)--引入第三方日志 ...

  9. 使用.NET 6开发TodoList应用(11)——使用FluentValidation和MediatR实现接口请求验证

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 在响应请求处理的过程中,我们经常需要对请求参数的合法性进行校验,如果参数不合法,将不继续进行业务逻辑的处理.我们当然可以将每个 ...

随机推荐

  1. SpringBoot 整合 MyBatis,实现 CRUD 示例

    目录 前言 创建项目/模块 SpringBoot Console Application CommandLineRunner SpringBoot 集成 MyBatis 创建数据库/表 配置数据源/连 ...

  2. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

  3. [云原生]Docker - 安装&卸载

    目录 系统要求 卸载旧版本 安装Docker 方法一:通过repo安装 设置Repository 安装Docker Engine 升级Docker Engine 方法二:通过package安装 方法三 ...

  4. MapReduce的类型与格式

    MapReduce的类型 默认的MR作业 默认的mapper是Mapper类,它将输入的键和值原封不动地写到输出中 默认的partitioner是HashPartitioner,它对每条记录的键进行哈 ...

  5. 4.2 rust 命令行参数

     从命令行读取参数 use std::env; fn main() { let args: Vec<String> = env::args().collect(); println!(&q ...

  6. swift 实现QQ好友列表功能

    最近项目中有类似QQ好友列表功能,整理了一下,话不多说,直接上代码 import UIKit class QQFriend: NSObject { var name: String? var intr ...

  7. [学习总结]5、Android的ViewGroup中事件的传递机制(二)

    下面是第一篇的连接 Android的ViewGroup中事件的传递机制(一) 关于onInterceptTouchEvent和onTouchEvent的详细解释. 1 public class Mai ...

  8. Virtual functions in derived classes

    In C++, once a member function is declared as a virtual function in a base class, it becomes virtual ...

  9. ACE_Message_Block实现浅析

    ACE_Message_Block实现浅析1. 概述ACE_Message_Block是ACE中很重要的一个类,和ACE框架中的重要模式的实现 如ACE_Reactor, ACE_Proactor, ...

  10. 【编程思想】【设计模式】【结构模式Structural】门面模式/外观模式Facade

    Python版 https://github.com/faif/python-patterns/blob/master/structural/facade.py #!/usr/bin/env pyth ...