如何用.NET 6和MediatR实现长尾词的POST请求改写TodoList应用?

2026-04-01 10:141阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2209个文字,预计阅读时间需要9分钟。

如何用.NET 6和MediatR实现长尾词的POST请求改写TodoList应用?

目录 + 需求 + 目标 + 原理与思路 + CQRS模式 + 中间者Mediator模式 + MediatR实现 + 引入MediatR + 实现Post请求 + 验证 + 创建TodoList验证 + 创建TodoItem验证 + 总结 + 参考资料 + 需求 + 非常简单:如何创建新的TodoList和TodoItem

目录
  • 需求
  • 目标
  • 原理与思路
    • CQRS模式
    • 中介者Mediator模式
    • MediatR
  • 实现
    • 引入MediatR
    • 实现Post请求
  • 验证
    • 创建TodoList验证
    • 创建TodoItem验证
  • 总结
    • 参考资料

      需求

      需求很简单:如何创建新的TodoListTodoItem并持久化。

      初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法;实用传入的请求参数new一个数据库实体对象;调用IRepository<T>完成数据库的写入,最多会在中间加一层Service。这个做法本身没有问题,也是需要从初学阶段开始扎实地掌握开发技能的必经之路,有助于帮助理解逻辑调用的过程。

      对于稍微正式一些的项目,.NET工程上习惯的实现是通过使用一些比较成熟的类库框架,有效地对业务逻辑进行分类管理、消除冗余代码,以达到业务逻辑职责清晰简洁的目的。在这个阶段我们经常使用的两个类库分别是AutoMapper和MediatR,本文结合POST请求,先介绍关于MediatR部分,下一篇关于GET请求,会涉及AutoMapper的部分。

      目标

      合理组织并使用MediatR,完成POST请求。

      原理与思路

      首先来简单地介绍一下这个类库。

      关于CQRS模式、中介者模式和MediatR

      CQRS模式

      CQRS模式全称是“Command Query Responsibility Segregation”,正如字面意思,CQRS模式的目的在于将读取操作和写入操作的指责区分开,并使用不同的Model去表示。从CRUD的角度来说,就是把RCUD区分开来对待。如下图所示:

      这个模式可以有效地应用到具有主从分离的数据库架构中,当需要获取数据时,从只读数据库(一般是从库)中读取数据,当需要写入或更新数据时,向主库进行操作。

      CQRS模式旨在解决的问题是:为了屏蔽数据库层面“写优先”还是“读优先”的优化设计策略,在业务逻辑侧进行解耦。

      任何设计模式都是对解决特定问题的一个Trade off,自然也带来了一些缺点,首先就是服务内部的组件复杂度上升了,因为需要创建额外的类来实现CQRS模式;其次如果数据层是分离的,那么可能会有数据的状态不一致问题。

      中介者Mediator模式

      这是23种基本设计模式中的一个,属于行为型设计模式,它给出了组件之间交互的一种解耦的方式。简单参考下图,具体内容就不过多解释了,任何一篇介绍设计模式的文章都有介绍。

      这种设计模式实际上是一种采用依赖倒置(Inversion of Control, IoC)的方式,实现了图中蓝色组件的松耦合。

      MediatR

      这是在开发中被广泛采用的实现以上两种设计模式的类库,更准确的说法是,它通过应用中介者模式,实现了进程内CQRS。基本思想是所有来自API接口和数据存储之间的逻辑,都需要通过MediatR来组织(即所谓的“中介者”)。

      从实现上看,MediatR提供了几组用于不同场景的接口,我们在本文中处理的比较多的是IRequest<T>/IRequestHandler<T>以及INotification<T>/INotificationHander<T>两组接口,更多的请参考官方文档和例子。

      实现

      所有需要使用MediatR的地方都集中在Application项目中。

      引入MediatR

      $ dotnet add src/TodoList.Application/TodoList.Application.csproj package MediatR.Extensions.Microsoft.DependencyInjection

      为了适配CQRS的模式,我们在Application项目中的TodoLists和TodoItems下相同地创建几个文件夹:

      Commands:用于组织CUD相关的业务逻辑;

      Queries:用于组织R相关的业务逻辑;

      EventHandlers:用于组织领域事件处理的相关业务逻辑。

      Application根目录下同样创建DependencyInjection.cs用于该项目的依赖注入:

      DependencyInjection.cs

      using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace TodoList.Application; public static class DependencyInjection { public static IServiceCollection AddApplication(this IServiceCollection services) { services.AddMediatR(Assembly.GetExecutingAssembly()); return services; } }

      并在Api项目中使用:

      // 省略其他... // 添加应用层配置 builder.Services.AddApplication(); // 添加基础设施配置 builder.Services.AddInfrastructure(builder.Configuration);

      实现Post请求

      在本章中我们只实现TodoListTodoItem的Create接口(POST),剩下的接口后面的文章中逐步涉及。

      POST TodoList

      Application/TodoLists/Commands/下新建一个目录CreateTodoList用于存放创建一个TodoList相关的所有逻辑:

      CreateTodoListCommand.cs

      using MediatR; using TodoList.Application.Common.Interfaces; namespace TodoList.Application.TodoLists.Commands.CreateTodoList; public class CreateTodoListCommand : IRequest<Guid> { public string? Title { get; set; } } public class CreateTodoListCommandHandler : IRequestHandler<CreateTodoListCommand, Guid> { private readonly IRepository<Domain.Entities.TodoList> _repository; public CreateTodoListCommandHandler(IRepository<Domain.Entities.TodoList> repository) { _repository = repository; } public async Task<Guid> Handle(CreateTodoListCommand request, CancellationToken cancellationToken) { var entity = new Domain.Entities.TodoList { Title = request.Title }; await _repository.AddAsync(entity, cancellationToken); return entity.Id; } }

      有一些实践是将RequestRequestHandler分开两个文件,我更倾向于像这样将他俩放在一起,一是保持简洁,二是当你需要顺着一个Command去寻找它对应的Handler时,不需要更多的跳转。

      接下来在TodoListController里实现对应的POST方法,

      using MediatR; using Microsoft.AspNetCore.Mvc; using TodoList.Application.TodoLists.Commands.CreateTodoList; namespace TodoList.Api.Controllers; [ApiController] [Route("/todo-list")] public class TodoListController : ControllerBase { private readonly IMediator _mediator; // 注入MediatR public TodoListController(IMediator mediator) => _mediator = mediator; [HttpPost] public async Task<Guid> Create([FromBody] CreateTodoListCommand command) { var createdTodoList = await _mediator.Send(command); // 出于演示的目的,这里只返回创建出来的TodoList的Id, // 实际使用中可能会选择IActionResult作为返回的类型并返回CreatedAtRoute对象, // 因为我们还没有去写GET方法,返回CreatedAtRoute会报错(找不到对应的Route),等讲完GET后会在那里更新 return createdTodoList.Id; } }

      POST TodoItem

      类似TodoListController和CreateTodoListCommand的实现,这里我直接把代码贴出来了。

      CreateTodoItemCommand.cs

      using MediatR; using TodoList.Application.Common.Interfaces; using TodoList.Domain.Entities; using TodoList.Domain.Events; namespace TodoList.Application.TodoItems.Commands.CreateTodoItem; public class CreateTodoItemCommand : IRequest<Guid> { public Guid ListId { get; set; } public string? Title { get; set; } } public class CreateTodoItemCommandHandler : IRequestHandler<CreateTodoItemCommand, Guid> { private readonly IRepository<TodoItem> _repository; public CreateTodoItemCommandHandler(IRepository<TodoItem> repository) { _repository = repository; } public async Task<Guid> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken) { var entity = new TodoItem { // 这个ListId在前文中的代码里漏掉了,需要添加到Domain.Entities.TodoItem实体上 ListId = request.ListId, Title = request.Title, Done = false }; await _repository.AddAsync(entity, cancellationToken); return entity.Id; } }

      TodoItemController.cs

      using MediatR; using Microsoft.AspNetCore.Mvc; using TodoList.Application.TodoItems.Commands.CreateTodoItem; namespace TodoList.Api.Controllers; [ApiController] [Route("/todo-item")] public class TodoItemController : ControllerBase { private readonly IMediator _mediator; // 注入MediatR public TodoItemController(IMediator mediator) => _mediator = mediator; [HttpPost] public async Task<Guid> Create([FromBody] CreateTodoItemCommand command) { var createdTodoItem = await _mediator.Send(command); // 处于演示的目的,这里只返回创建出来的TodoItem的Id,理由同前 return createdTodoItem.Id; } }

      验证

      运行Api项目,通过Hoppscotch发送对应接口请求:

      创建TodoList验证

      请求

      返回

      如何用.NET 6和MediatR实现长尾词的POST请求改写TodoList应用?

      数据库

      第一条数据是种子数据,第二条是我们刚才创建的。

      创建TodoItem验证

      继续拿刚才创建的这个TodoList的Id来创建新的TodoItem:

      请求

      返回

      数据库

      最后一条是我们新创建的,其余是种子数据。

      总结

      我们已经通过演示在POST请求中实现MediatR库带来的CQRS模式,在这篇文章里我留了一个坑。就是领域事件的Handler并没有任何演示,只是创建了一个文件夹,结合在这篇文章中留下来的发布领域事件的坑,会在DELETE的文章中填完。

      看起来使用CQRS模式使得我们的代码结构变得更加复杂了,但是对于一些再复杂一些的实际项目中,正确使用CQRS模式有助于你分析和整理业务需求,并将相关的业务需求以及相关模型梳理到统一的位置进行管理,包括在后续的文章里我们会陆续向其中加入诸如入参校验、出参类型转换等逻辑。认真思考并运用习惯之后,大家可以自行体会这样做的“权衡”。

      参考资料

      MediatR

      Mediator 

      以上就是.NET 6开发TodoList应用之使用MediatR实现POST请求的详细内容,更多关于.NET 6 MediatR实现POST请求的资料请关注易盾网络其它相关文章!

      本文共计2209个文字,预计阅读时间需要9分钟。

      如何用.NET 6和MediatR实现长尾词的POST请求改写TodoList应用?

      目录 + 需求 + 目标 + 原理与思路 + CQRS模式 + 中间者Mediator模式 + MediatR实现 + 引入MediatR + 实现Post请求 + 验证 + 创建TodoList验证 + 创建TodoItem验证 + 总结 + 参考资料 + 需求 + 非常简单:如何创建新的TodoList和TodoItem

      目录
      • 需求
      • 目标
      • 原理与思路
        • CQRS模式
        • 中介者Mediator模式
        • MediatR
      • 实现
        • 引入MediatR
        • 实现Post请求
      • 验证
        • 创建TodoList验证
        • 创建TodoItem验证
      • 总结
        • 参考资料

          需求

          需求很简单:如何创建新的TodoListTodoItem并持久化。

          初学者按照教程去实现的话,应该分成以下几步:创建Controller并实现POST方法;实用传入的请求参数new一个数据库实体对象;调用IRepository<T>完成数据库的写入,最多会在中间加一层Service。这个做法本身没有问题,也是需要从初学阶段开始扎实地掌握开发技能的必经之路,有助于帮助理解逻辑调用的过程。

          对于稍微正式一些的项目,.NET工程上习惯的实现是通过使用一些比较成熟的类库框架,有效地对业务逻辑进行分类管理、消除冗余代码,以达到业务逻辑职责清晰简洁的目的。在这个阶段我们经常使用的两个类库分别是AutoMapper和MediatR,本文结合POST请求,先介绍关于MediatR部分,下一篇关于GET请求,会涉及AutoMapper的部分。

          目标

          合理组织并使用MediatR,完成POST请求。

          原理与思路

          首先来简单地介绍一下这个类库。

          关于CQRS模式、中介者模式和MediatR

          CQRS模式

          CQRS模式全称是“Command Query Responsibility Segregation”,正如字面意思,CQRS模式的目的在于将读取操作和写入操作的指责区分开,并使用不同的Model去表示。从CRUD的角度来说,就是把RCUD区分开来对待。如下图所示:

          这个模式可以有效地应用到具有主从分离的数据库架构中,当需要获取数据时,从只读数据库(一般是从库)中读取数据,当需要写入或更新数据时,向主库进行操作。

          CQRS模式旨在解决的问题是:为了屏蔽数据库层面“写优先”还是“读优先”的优化设计策略,在业务逻辑侧进行解耦。

          任何设计模式都是对解决特定问题的一个Trade off,自然也带来了一些缺点,首先就是服务内部的组件复杂度上升了,因为需要创建额外的类来实现CQRS模式;其次如果数据层是分离的,那么可能会有数据的状态不一致问题。

          中介者Mediator模式

          这是23种基本设计模式中的一个,属于行为型设计模式,它给出了组件之间交互的一种解耦的方式。简单参考下图,具体内容就不过多解释了,任何一篇介绍设计模式的文章都有介绍。

          这种设计模式实际上是一种采用依赖倒置(Inversion of Control, IoC)的方式,实现了图中蓝色组件的松耦合。

          MediatR

          这是在开发中被广泛采用的实现以上两种设计模式的类库,更准确的说法是,它通过应用中介者模式,实现了进程内CQRS。基本思想是所有来自API接口和数据存储之间的逻辑,都需要通过MediatR来组织(即所谓的“中介者”)。

          从实现上看,MediatR提供了几组用于不同场景的接口,我们在本文中处理的比较多的是IRequest<T>/IRequestHandler<T>以及INotification<T>/INotificationHander<T>两组接口,更多的请参考官方文档和例子。

          实现

          所有需要使用MediatR的地方都集中在Application项目中。

          引入MediatR

          $ dotnet add src/TodoList.Application/TodoList.Application.csproj package MediatR.Extensions.Microsoft.DependencyInjection

          为了适配CQRS的模式,我们在Application项目中的TodoLists和TodoItems下相同地创建几个文件夹:

          Commands:用于组织CUD相关的业务逻辑;

          Queries:用于组织R相关的业务逻辑;

          EventHandlers:用于组织领域事件处理的相关业务逻辑。

          Application根目录下同样创建DependencyInjection.cs用于该项目的依赖注入:

          DependencyInjection.cs

          using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace TodoList.Application; public static class DependencyInjection { public static IServiceCollection AddApplication(this IServiceCollection services) { services.AddMediatR(Assembly.GetExecutingAssembly()); return services; } }

          并在Api项目中使用:

          // 省略其他... // 添加应用层配置 builder.Services.AddApplication(); // 添加基础设施配置 builder.Services.AddInfrastructure(builder.Configuration);

          实现Post请求

          在本章中我们只实现TodoListTodoItem的Create接口(POST),剩下的接口后面的文章中逐步涉及。

          POST TodoList

          Application/TodoLists/Commands/下新建一个目录CreateTodoList用于存放创建一个TodoList相关的所有逻辑:

          CreateTodoListCommand.cs

          using MediatR; using TodoList.Application.Common.Interfaces; namespace TodoList.Application.TodoLists.Commands.CreateTodoList; public class CreateTodoListCommand : IRequest<Guid> { public string? Title { get; set; } } public class CreateTodoListCommandHandler : IRequestHandler<CreateTodoListCommand, Guid> { private readonly IRepository<Domain.Entities.TodoList> _repository; public CreateTodoListCommandHandler(IRepository<Domain.Entities.TodoList> repository) { _repository = repository; } public async Task<Guid> Handle(CreateTodoListCommand request, CancellationToken cancellationToken) { var entity = new Domain.Entities.TodoList { Title = request.Title }; await _repository.AddAsync(entity, cancellationToken); return entity.Id; } }

          有一些实践是将RequestRequestHandler分开两个文件,我更倾向于像这样将他俩放在一起,一是保持简洁,二是当你需要顺着一个Command去寻找它对应的Handler时,不需要更多的跳转。

          接下来在TodoListController里实现对应的POST方法,

          using MediatR; using Microsoft.AspNetCore.Mvc; using TodoList.Application.TodoLists.Commands.CreateTodoList; namespace TodoList.Api.Controllers; [ApiController] [Route("/todo-list")] public class TodoListController : ControllerBase { private readonly IMediator _mediator; // 注入MediatR public TodoListController(IMediator mediator) => _mediator = mediator; [HttpPost] public async Task<Guid> Create([FromBody] CreateTodoListCommand command) { var createdTodoList = await _mediator.Send(command); // 出于演示的目的,这里只返回创建出来的TodoList的Id, // 实际使用中可能会选择IActionResult作为返回的类型并返回CreatedAtRoute对象, // 因为我们还没有去写GET方法,返回CreatedAtRoute会报错(找不到对应的Route),等讲完GET后会在那里更新 return createdTodoList.Id; } }

          POST TodoItem

          类似TodoListController和CreateTodoListCommand的实现,这里我直接把代码贴出来了。

          CreateTodoItemCommand.cs

          using MediatR; using TodoList.Application.Common.Interfaces; using TodoList.Domain.Entities; using TodoList.Domain.Events; namespace TodoList.Application.TodoItems.Commands.CreateTodoItem; public class CreateTodoItemCommand : IRequest<Guid> { public Guid ListId { get; set; } public string? Title { get; set; } } public class CreateTodoItemCommandHandler : IRequestHandler<CreateTodoItemCommand, Guid> { private readonly IRepository<TodoItem> _repository; public CreateTodoItemCommandHandler(IRepository<TodoItem> repository) { _repository = repository; } public async Task<Guid> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken) { var entity = new TodoItem { // 这个ListId在前文中的代码里漏掉了,需要添加到Domain.Entities.TodoItem实体上 ListId = request.ListId, Title = request.Title, Done = false }; await _repository.AddAsync(entity, cancellationToken); return entity.Id; } }

          TodoItemController.cs

          using MediatR; using Microsoft.AspNetCore.Mvc; using TodoList.Application.TodoItems.Commands.CreateTodoItem; namespace TodoList.Api.Controllers; [ApiController] [Route("/todo-item")] public class TodoItemController : ControllerBase { private readonly IMediator _mediator; // 注入MediatR public TodoItemController(IMediator mediator) => _mediator = mediator; [HttpPost] public async Task<Guid> Create([FromBody] CreateTodoItemCommand command) { var createdTodoItem = await _mediator.Send(command); // 处于演示的目的,这里只返回创建出来的TodoItem的Id,理由同前 return createdTodoItem.Id; } }

          验证

          运行Api项目,通过Hoppscotch发送对应接口请求:

          创建TodoList验证

          请求

          返回

          如何用.NET 6和MediatR实现长尾词的POST请求改写TodoList应用?

          数据库

          第一条数据是种子数据,第二条是我们刚才创建的。

          创建TodoItem验证

          继续拿刚才创建的这个TodoList的Id来创建新的TodoItem:

          请求

          返回

          数据库

          最后一条是我们新创建的,其余是种子数据。

          总结

          我们已经通过演示在POST请求中实现MediatR库带来的CQRS模式,在这篇文章里我留了一个坑。就是领域事件的Handler并没有任何演示,只是创建了一个文件夹,结合在这篇文章中留下来的发布领域事件的坑,会在DELETE的文章中填完。

          看起来使用CQRS模式使得我们的代码结构变得更加复杂了,但是对于一些再复杂一些的实际项目中,正确使用CQRS模式有助于你分析和整理业务需求,并将相关的业务需求以及相关模型梳理到统一的位置进行管理,包括在后续的文章里我们会陆续向其中加入诸如入参校验、出参类型转换等逻辑。认真思考并运用习惯之后,大家可以自行体会这样做的“权衡”。

          参考资料

          MediatR

          Mediator 

          以上就是.NET 6开发TodoList应用之使用MediatR实现POST请求的详细内容,更多关于.NET 6 MediatR实现POST请求的资料请关注易盾网络其它相关文章!