.NET?6開發(fā)TodoList應(yīng)用之使用MediatR實現(xiàn)POST請求
需求
需求很簡單:如何創(chuàng)建新的TodoList和TodoItem并持久化。
初學(xué)者按照教程去實現(xiàn)的話,應(yīng)該分成以下幾步:創(chuàng)建Controller并實現(xiàn)POST方法;實用傳入的請求參數(shù)new一個數(shù)據(jù)庫實體對象;調(diào)用IRepository<T>完成數(shù)據(jù)庫的寫入,最多會在中間加一層Service。這個做法本身沒有問題,也是需要從初學(xué)階段開始扎實地掌握開發(fā)技能的必經(jīng)之路,有助于幫助理解邏輯調(diào)用的過程。
對于稍微正式一些的項目,.NET工程上習(xí)慣的實現(xiàn)是通過使用一些比較成熟的類庫框架,有效地對業(yè)務(wù)邏輯進(jìn)行分類管理、消除冗余代碼,以達(dá)到業(yè)務(wù)邏輯職責(zé)清晰簡潔的目的。在這個階段我們經(jīng)常使用的兩個類庫分別是AutoMapper和MediatR,本文結(jié)合POST請求,先介紹關(guān)于MediatR部分,下一篇關(guān)于GET請求,會涉及AutoMapper的部分。
目標(biāo)
合理組織并使用MediatR,完成POST請求。
原理與思路
首先來簡單地介紹一下這個類庫。
關(guān)于CQRS模式、中介者模式和MediatR
CQRS模式
CQRS模式全稱是“Command Query Responsibility Segregation”,正如字面意思,CQRS模式的目的在于將讀取操作和寫入操作的指責(zé)區(qū)分開,并使用不同的Model去表示。從CRUD的角度來說,就是把R和CUD區(qū)分開來對待。如下圖所示:

這個模式可以有效地應(yīng)用到具有主從分離的數(shù)據(jù)庫架構(gòu)中,當(dāng)需要獲取數(shù)據(jù)時,從只讀數(shù)據(jù)庫(一般是從庫)中讀取數(shù)據(jù),當(dāng)需要寫入或更新數(shù)據(jù)時,向主庫進(jìn)行操作。
CQRS模式旨在解決的問題是:為了屏蔽數(shù)據(jù)庫層面“寫優(yōu)先”還是“讀優(yōu)先”的優(yōu)化設(shè)計策略,在業(yè)務(wù)邏輯側(cè)進(jìn)行解耦。
任何設(shè)計模式都是對解決特定問題的一個Trade off,自然也帶來了一些缺點,首先就是服務(wù)內(nèi)部的組件復(fù)雜度上升了,因為需要創(chuàng)建額外的類來實現(xiàn)CQRS模式;其次如果數(shù)據(jù)層是分離的,那么可能會有數(shù)據(jù)的狀態(tài)不一致問題。
中介者M(jìn)ediator模式
這是23種基本設(shè)計模式中的一個,屬于行為型設(shè)計模式,它給出了組件之間交互的一種解耦的方式。簡單參考下圖,具體內(nèi)容就不過多解釋了,任何一篇介紹設(shè)計模式的文章都有介紹。

這種設(shè)計模式實際上是一種采用依賴倒置(Inversion of Control, IoC)的方式,實現(xiàn)了圖中藍(lán)色組件的松耦合。
MediatR
這是在開發(fā)中被廣泛采用的實現(xiàn)以上兩種設(shè)計模式的類庫,更準(zhǔn)確的說法是,它通過應(yīng)用中介者模式,實現(xiàn)了進(jìn)程內(nèi)CQRS?;舅枷胧撬衼碜訟PI接口和數(shù)據(jù)存儲之間的邏輯,都需要通過MediatR來組織(即所謂的“中介者”)。
從實現(xiàn)上看,MediatR提供了幾組用于不同場景的接口,我們在本文中處理的比較多的是IRequest<T>/IRequestHandler<T>以及INotification<T>/INotificationHander<T>兩組接口,更多的請參考官方文檔和例子。
實現(xiàn)
所有需要使用MediatR的地方都集中在Application項目中。
引入MediatR
$ dotnet add src/TodoList.Application/TodoList.Application.csproj package MediatR.Extensions.Microsoft.DependencyInjection
為了適配CQRS的模式,我們在Application項目中的TodoLists和TodoItems下相同地創(chuàng)建幾個文件夾:
Commands:用于組織CUD相關(guān)的業(yè)務(wù)邏輯;
Queries:用于組織R相關(guān)的業(yè)務(wù)邏輯;
EventHandlers:用于組織領(lǐng)域事件處理的相關(guān)業(yè)務(wù)邏輯。
在Application根目錄下同樣創(chuàng)建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項目中使用:
// 省略其他... // 添加應(yīng)用層配置 builder.Services.AddApplication(); // 添加基礎(chǔ)設(shè)施配置 builder.Services.AddInfrastructure(builder.Configuration);
實現(xiàn)Post請求
在本章中我們只實現(xiàn)TodoList和TodoItem的Create接口(POST),剩下的接口后面的文章中逐步涉及。
POST TodoList
在Application/TodoLists/Commands/下新建一個目錄CreateTodoList用于存放創(chuàng)建一個TodoList相關(guān)的所有邏輯:
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;
}
}
有一些實踐是將Request和RequestHandler分開兩個文件,我更傾向于像這樣將他倆放在一起,一是保持簡潔,二是當(dāng)你需要順著一個Command去尋找它對應(yīng)的Handler時,不需要更多的跳轉(zhuǎn)。
接下來在TodoListController里實現(xiàn)對應(yīng)的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);
// 出于演示的目的,這里只返回創(chuàng)建出來的TodoList的Id,
// 實際使用中可能會選擇IActionResult作為返回的類型并返回CreatedAtRoute對象,
// 因為我們還沒有去寫GET方法,返回CreatedAtRoute會報錯(找不到對應(yīng)的Route),等講完GET后會在那里更新
return createdTodoList.Id;
}
}
POST TodoItem
類似TodoListController和CreateTodoListCommand的實現(xiàn),這里我直接把代碼貼出來了。
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);
// 處于演示的目的,這里只返回創(chuàng)建出來的TodoItem的Id,理由同前
return createdTodoItem.Id;
}
}
驗證
運行Api項目,通過Hoppscotch發(fā)送對應(yīng)接口請求:
創(chuàng)建TodoList驗證
請求

返回

數(shù)據(jù)庫

第一條數(shù)據(jù)是種子數(shù)據(jù),第二條是我們剛才創(chuàng)建的。
創(chuàng)建TodoItem驗證
繼續(xù)拿剛才創(chuàng)建的這個TodoList的Id來創(chuàng)建新的TodoItem:
請求

返回

數(shù)據(jù)庫

最后一條是我們新創(chuàng)建的,其余是種子數(shù)據(jù)。
總結(jié)
我們已經(jīng)通過演示在POST請求中實現(xiàn)MediatR庫帶來的CQRS模式,在這篇文章里我留了一個坑。就是領(lǐng)域事件的Handler并沒有任何演示,只是創(chuàng)建了一個文件夾,結(jié)合在這篇文章中留下來的發(fā)布領(lǐng)域事件的坑,會在DELETE的文章中填完。
看起來使用CQRS模式使得我們的代碼結(jié)構(gòu)變得更加復(fù)雜了,但是對于一些再復(fù)雜一些的實際項目中,正確使用CQRS模式有助于你分析和整理業(yè)務(wù)需求,并將相關(guān)的業(yè)務(wù)需求以及相關(guān)模型梳理到統(tǒng)一的位置進(jìn)行管理,包括在后續(xù)的文章里我們會陸續(xù)向其中加入諸如入?yún)⑿r?、出參類型轉(zhuǎn)換等邏輯。認(rèn)真思考并運用習(xí)慣之后,大家可以自行體會這樣做的“權(quán)衡”。
參考資料
以上就是.NET 6開發(fā)TodoList應(yīng)用之使用MediatR實現(xiàn)POST請求的詳細(xì)內(nèi)容,更多關(guān)于.NET 6 MediatR實現(xiàn)POST請求的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MVC HtmlHelper擴(kuò)展類(PagingHelper)實現(xiàn)分頁功能
這篇文章主要為大家詳細(xì)介紹了MVC HtmlHelper擴(kuò)展,實現(xiàn)分頁功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
Asp.net實現(xiàn)無刷新調(diào)用后臺實體類數(shù)據(jù)并以Json格式返回
本文主要分享了Asp.net實現(xiàn)無刷新調(diào)用后臺實體類數(shù)據(jù)并以Json格式返回的具體實例方法,具有一定的參考價值,有需要的朋友可以看下2016-12-12
asp.net模板引擎Razor調(diào)用外部方法用法實例
這篇文章主要介紹了asp.net模板引擎Razor調(diào)用外部方法用法,實例分析了Razor調(diào)用外部方法的相關(guān)使用技巧,需要的朋友可以參考下2015-06-06
Asp.net利用JQuery彈出層加載數(shù)據(jù)代碼
最近看QQ空間里面的投票功能很使用。點擊一個鏈接就彈出一個層,然后再加載一些投票信息,旁邊的區(qū)域變成灰色不可用狀態(tài)。其實這不算什么高深的技術(shù),只要在ASP.NET中利用JQuery結(jié)合一般處理程序ASHX即可搞定了。2009-11-11
一文帶你了解.Net基于Threading.Mutex實現(xiàn)互斥鎖
互斥鎖是一個互斥的同步對象,意味著同一時間有且僅有一個線程可以獲取它。這篇文章主要介紹了一文帶你了解.Net基于Threading.Mutex實現(xiàn)互斥鎖,感興趣的可以了解一下2021-06-06
ASP.NET oledb連接Access數(shù)據(jù)庫的方法
這篇文章主要介紹了ASP.NET oledb連接Access數(shù)據(jù)庫的方法,需要的朋友可以參考下2015-01-01
.NET Core利用動態(tài)代理實現(xiàn)AOP(面向切面編程)
用動態(tài)代理可以做AOP(面向切面編程),進(jìn)行無入侵式實現(xiàn)自己的擴(kuò)展業(yè)務(wù),調(diào)用者和被調(diào)用者之間的解耦,提高代碼的靈活性和可擴(kuò)展性。本文將為大家詳細(xì)介紹實現(xiàn)的方法,感興趣的可以學(xué)習(xí)一下2022-01-01

