You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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相关包即可实现你要的效果,不需要复杂的手动映射:

  1. 客户端项目先安装两个Nuget包:Microsoft.AspNetCore.JsonPatch、Microsoft.AspNetCore.Mvc.NewtonsoftJson
  2. 写一个通用扩展方法,自动将你定义的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.15 09:36:07