基于MediatR(CQRS)的.NET项目REST PATCH接口实现最佳实践问询
问题背景
我此前一直使用PUT/POST更新所开发的应用,流程一直简单顺畅。现在我正在开发一个需要对外暴露数据库数据更新能力的API,最初的想法是可以全量或部分替换数据行。由于需要对接多个对数据掌握程度不同的应用发起更新,我认为使用PATCH动作比PUT更合适。
我能找到的演示示例都是直接在控制器中实现更新逻辑,这不符合我的需求。
我使用的是jasontaylordev开源CleanArchitecture项目同款的CQRS+MediatR架构模式,示例也会基于该模式展开。
疑问1解决方案:规避JsonPatchDocument传递到Handler层
你完全可以把Patch相关的逻辑全部收敛在控制器层,不需要让Handler感知到Patch的存在,也不需要用到AutoMapper这类动态映射库,符合你的架构约束:
- 调整控制器入参,直接接收
JsonPatchDocument<TodoItemDto>,而不是将其封装到Command中 - 控制器层先通过MediatR发送查询请求,拿到待更新条目对应的DTO(仅读操作,没有直接访问数据库,符合架构规范)
- 将收到的Patch文档应用到查询返回的DTO上,完成字段合法性校验
- 校验通过后,构造纯业务属性的
UpdateTodoItemCommand(仅包含Id、Title、Note三个基础属性,无任何Patch相关依赖),再发送给MediatR - Handler层直接接收纯属性的Command,手动将属性赋值到数据库实体后保存即可,全程和Patch逻辑解耦
调整后的服务端核心代码示例:
// 控制器代码 [HttpPatch("{id}")] public async Task<ActionResult> Update(int id, JsonPatchDocument<TodoItemDto> patchDoc) { // 先查询当前待更新的条目DTO var query = new GetTodoItemByIdQuery { Id = id }; var todoDto = await Mediator.Send(query); if (todoDto == null) { return NotFound(); } // 应用Patch操作 patchDoc.ApplyTo(todoDto, ModelState); if (!ModelState.IsValid) { return ValidationProblem(ModelState); } // 构造纯业务Command,无Patch依赖 var command = new UpdateTodoItemCommand { Id = id, Title = todoDto.Title, Note = todoDto.Note }; await Mediator.Send(command); return NoContent(); } // 纯业务Command,无JsonPatch相关引用 public class UpdateTodoItemCommand : IRequest { public int Id { get; set; } public string Title { get; set; } public string Note { get; set; } } // Handler代码,完全感知不到Patch存在 public class UpdateTodoItemCommandHandler : IRequestHandler<UpdateTodoItemCommand> { private readonly IApplicationDbContext _context; public UpdateTodoItemCommandHandler(IApplicationDbContext context) { _context = context; } public async Task<Unit> Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken) { var entity = await _context.TodoItems.FindAsync(request.Id); if (entity == null) { throw new NotFoundException(nameof(TodoItem), request.Id); } // 手动赋值,无需映射库 entity.Title = request.Title; entity.Note = request.Note; await _context.SaveChangesAsync(cancellationToken); return Unit.Value; } }
注:你原示例代码中存在笔误todoItemDto.Todo.ApplyTo(request.Todo);,参数顺序写反了,正确写法为request.Todo.ApplyTo(todoItemDto);
疑问2解决方案:.NET Core客户端构造JsonPatch请求
直接用微软官方的JsonPatch相关包即可实现你要的效果,不需要复杂的手动映射:
- 客户端项目先安装两个Nuget包:
Microsoft.AspNetCore.JsonPatch、Microsoft.AspNetCore.Mvc.NewtonsoftJson - 写一个通用扩展方法,自动将你定义的DTO转换为JsonPatchDocument,规则为:属性有值时生成Replace操作,属性显式为null时生成Remove操作
客户端核心代码示例:
// 你定义的业务DTO public class UpdateTodoItemDto { public string Title { get; set; } public string Note { get; set; } } // 通用扩展方法,可复用在所有同类DTO转换场景 public static class JsonPatchExtensions { public static JsonPatchDocument<T> ToPatchDocument<T>(this T dto) where T : class { var patchDoc = new JsonPatchDocument<T>(); var properties = typeof(T).GetProperties(); foreach (var prop in properties) { var value = prop.GetValue(dto); if (value == null) { patchDoc.Remove(prop.Name); } else { patchDoc.Replace(prop.Name, value); } } return patchDoc; } } // 调用示例 var updateDto = new UpdateTodoItemDto { Title = "test", Note = null }; var patchDoc = updateDto.ToPatchDocument(); // 调用接口时把patchDoc作为请求体发送即可,会自动生成符合要求的Patch操作:替换Title,删除Note
内容的提问来源于stack exchange,提问作者XRaycat
相关产品推荐
相关产品推荐

