Asp.Net Core6.0中MediatR的应用CQRS
1、前言
对于简单的系统而言模型与数据可以进行直接的映射,比如说三层模型就足够支撑项目的需求了。对于这种简单的系统我们过度设计说白了无异于增加成本,因为对于一般的CRUD来说我们不用特别区分查询和增删改的程序结构。高射炮打蚊子那就有点大材小用了。但是我们的系统具有一定复杂性的时候,可能源于访问频次、数据量或者数据模型这个时候我们的查询跟增删改的需求差距就逐渐变大。所以CQRS(Command Query Responsibility Segregation)命令查询的责任分离就出现了。CQRS本质上是一种读写分离设计思想,这种框架设计模式将命令型业务和查询型业务分开单独处理。我们运用MediatR就可以轻松的实现CQRS。
2、中介者模式
中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系,中介者使各个对象之间不需要显式地相互引用,从而降低耦合性。也符合符合迪米特原则。MediatR本质就是中介者模式,实现命令的构造和命令的处理分开来。
3、MediatR简介
MediatR是一个跨平台通过一种进程内消息传递机制,进行请求/响应、命令、查询、通知和事件的消息传递,并通过C#泛型来支持消息的智能调度,其目的是消息发送和消息处理的解耦。它支持以单播和多播形式使用同步或异步的模式来发布消息,创建和侦听事件。
4、主要的几个对象
a.IMediator:主要提供Send与Publish方法,需要执行的命令都是通过这两个方法实现
b.IRequest、IRequest<T>:命令查询 | 处理类所继承的接口,一个有返回类型,一个无返回类型,一个查询对应一个处理类,程序集只认第一个扫描到的类。
c.IRequestHandler<in TRequest,TResponse>(实现Handle方法) :命令处理接口。命令查询 | 处理类继承它,也可以继承AsyncRequestHandler(实现抽象Handle方法)、RequestHandler(实现抽象Handle方法)接口
d.INotification:命令查询 | 处理类所继承的接口这个没有返回,与IRequest不通的是可以对于多个处理类。
e.INotificationHandler<in TNotification>:与IRequestHandler一样的只不过这是INotification的处理接口
5、IRequest栗子
a.IRequest<T>:有返回值的类
说了那么多干巴巴的直接上代码看。我这里是Core6.0控制台应用程序,安装nuget包 MediatR与扩展包MediatR.Extensions.Microsoft.DependencyInjection。也可以通过命令行添加dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
先看命令的查询处理
这里我习惯性的将两个类放在一个文件里面方便查看,命名这里做查询就写XXXQuery 处理类的命名也是XXXQueryHandler
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Mrdiator.Query
{
/// <summary>
/// 查询信息命令类
/// </summary>
internal class GetInfoQuery:IRequest<Result>
{
/// <summary>
/// 构造函数--就是查询的条件说白了
/// </summary>
/// <param name="age"></param>
/// <param name="name"></param>
/// <param name="nowTime"></param>
internal GetInfoQuery(int age, string name, DateTime nowTime)
{
Age = age;
Name = name;
NowTime = nowTime;
} public int Age { get; set; }
public string Name { get; set; }
public DateTime NowTime { get; set; }
}
/// <summary>
/// 查询命令的处理类
/// </summary>
internal class GetInfoQueryHandller : IRequestHandler<GetInfoQuery, Result>
{
public Task<Result> Handle(GetInfoQuery request, CancellationToken cancellationToken)
{
Console.WriteLine("GetObjCommandHandller");
object ret = new
{
request.Name,
request.NowTime,
request.Age,
};
var result = new Result()
{
Code = 200,
Message="Success",
Data = ret
};
return Task.FromResult(result);
}
}
}
来看一下调用
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Mrdiator.Query;
using Newtonsoft.Json;
using System.Net.Http;
using System.Reflection; //实例化一个ServiceCollection
IServiceCollection services = new ServiceCollection();
//添加当前的程序集MediatR会扫描当前的程序集
//services.AddMediatR(typeof(Program).Assembly);
services.AddMediatR(Assembly.GetExecutingAssembly());
//构建一个serviceProvider
var serviceProvider = services.BuildServiceProvider();
//从容器中获取mediator
var mediator = serviceProvider.GetService<IMediator>();
//执行命令
var result =await mediator.Send(new GetInfoQuery(18,"wyy",DateTime.Now));
同样我们启动程序也打印了我们的输出。
b.IRequest:无返回值的栗子
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Mrdiator.Query
{
/// <summary>
/// 命令查询类--无返回值
/// </summary>
internal class GetInfoQuery2 : IRequest
{
public GetInfoQuery2(int age, string name, DateTime nowTime)
{
Age = age;
Name = name;
NowTime = nowTime;
} public int Age { get; set; }
public string Name { get; set; }
public DateTime NowTime { get; set; }
}
/// <summary>
/// 命令处理类1-----继承AsyncRequestHandler 实现抽象方法 Handle
/// </summary>
internal class GetInfoQuery2_2Handller : AsyncRequestHandler<GetInfoQuery2>
{
protected override Task Handle(GetInfoQuery2 request, CancellationToken cancellationToken)
{
Console.WriteLine("GetInfoQuery2_2Handller");
return Task.CompletedTask;
}
}
/// <summary>
/// 命令处理类2-----IRequestHandler 实现接口方法 Handle
/// </summary>
internal class GetInfoQuery2Handller : IRequestHandler<GetInfoQuery2>
{
public Task<Unit> Handle(GetInfoQuery2 request, CancellationToken cancellationToken)
{
Console.WriteLine("GetInfoQuery2Handller"); return Task.FromResult(new Unit());
}
} }
var result2 =await mediator.Send(new GetInfoQuery2(18,"wyy",DateTime.Now));
我们写了一个GetInfoQuery2,下面有两个类都在泛型里实现了,可以看到程序是只执行了GetInfoQuery2_2Handller就可以看出IRequest命令类跟处理类失忆对一的关系。我们只是通过Mediator的send将GetInfoQuery2 作为参数传进去程序就能执行到GetInfoQuery2_2Handller里面的Handle方法这就是MediatR的好处。
/// <summary>
/// 命令处理类-----继承RequestHandler 实现抽象方法 Handle
/// </summary>
internal class GetInfoQuery3Handller : RequestHandler<GetInfoQuery3, Result>
{
protected override Result Handle(GetInfoQuery3 request)
{
Console.WriteLine("GetInfoQuery3Handller");
return new Result();
}
}
这样写也可以调用到 ,这就是上面写的 继承不同的类或者接口,一般大多数我都是继承IRequestHandler。
6、INotification栗子
这里我新建了一个Core6.0的WebAPI的工程来演示INotification的运用。同样的nuget安装MediatR与扩展包MediatR.Extensions.Microsoft.DependencyInjection。在Program.cs里添加。这里如果你的命令处理类跟项目在同一个程序集里面就用第二个也可以,如果你是分开的另外建了一个类库写命令查询的直接引用里面随便一个类获取程序集就可以了
//获取该类下的程序集
builder.Services.AddMediatR(typeof(Program).Assembly);
//获取当前程序集
//builder.Services.AddMediatR(Assembly.GetExecutingAssembly());
这里我们注册了处理多个事件、每个都执行到了。
using MediatApi.Helper;
using MediatApi.Model;
using MediatR;
using Newtonsoft.Json; namespace MediatApi.Application.Command
{
/// <summary>
/// 创建订单
/// </summary>
public class OrderCreateCommand:INotification
{
/// <summary>
/// Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 订单号
/// </summary>
public string? OrderNum { get; set; }
/// <summary>
/// 订单类型
/// </summary>
public string? OrderType { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreatTime { get; set; } }
/// <summary>
/// 创建订单处理1
/// </summary>
public class OrderCreateCommandHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Order model = new(notification.Id, notification.OrderNum, notification.OrderType, notification.CreatTime);
//数据库操作省略
Result ret = new()
{
Code=200,
Message="",
Data=model
};
string retJson=JsonConvert.SerializeObject(ret);
Console.WriteLine("11111——————————————订单创建啦!");
return Task.FromResult(retJson);
}
}
/// <summary>
/// 创建订单后处理步骤2
/// </summary>
public class OrderCreateTwoHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("22222——————————————扣钱成功了");
return Task.CompletedTask;
}
}
/// <summary>
/// 创建订单后处理步骤3
/// </summary>
public class OrderCreateThreeHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("333333333——————————————订单入库啦!");
return Task.CompletedTask;
}
}
/// <summary>
/// 创建订单后处理步骤4
/// </summary>
public class OrderCreateFoureHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("4444444——————————————第四个操作呢!");
return Task.CompletedTask;
}
}
/// <summary>
/// 创建订单后处理步骤5
/// </summary>
public class OrderCreateFiveHandler : INotificationHandler<OrderCreateCommand>
{
public Task Handle(OrderCreateCommand notification, CancellationToken cancellationToken)
{
Console.WriteLine("55555——————————————接着奏乐接着舞!");
return Task.CompletedTask;
}
} }
注意:这里是用mediator的publish方法的实现的,命令查询类继承INotification就要用publish方法,继承IRequest就要用Send方法,项目目录也在左侧这样别人看着也清晰点
7、IPipelineBehavior
这个接口的作用就是在我们命令处理之前或者之后插入逻辑,类似我们的中间件,我新建一个TransactionBehavior来处理保存数据库之前之后的操作,这里的代码只判断了是否为空之前写的是判断事务是否为空,代码多就随便写了意思意思。
然后新建一个DBTransactionBehavior这里对操作数据库新增演示一下
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace MediatApi.Helper
{
public class TransactionBehavior<TDBContext, TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TDBContext : WyyDbContext
{
ILogger _logger;
WyyDbContext _dbContext; public TransactionBehavior(ILogger logger, WyyDbContext dbContext)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
} public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var response = default(TResponse);
try
{
Console.WriteLine("执行前逻辑++++++++++++++++++++++++++++++++++");
Console.WriteLine();
if (request != null)
return await next(); Console.WriteLine("逻辑不对处理++++++++++++++++++++++++++++++++");
Console.WriteLine();
var strategy = _dbContext.Database.CreateExecutionStrategy(); await strategy.ExecuteAsync(async () =>
{
Guid transactionId;
using (var transaction = await _dbContext.Database.BeginTransactionAsync())
using (_logger.BeginScope("TransactionContext:{TransactionId}", transaction.TransactionId))
{
_logger.LogInformation("----- 开始事务 {TransactionId} 请求{request}", transaction.TransactionId, request); response = await next(); _logger.LogInformation("----- 提交事务 {TransactionId}}", transaction.TransactionId); await _dbContext.CommitTransactionAsync(transaction); transactionId = transaction.TransactionId;
}
}); return response; }
catch(Exception ex)
{
_logger.LogError(ex, "处理事务出错{@Command}", request); throw;
}
}
}
}
using MediatApi.Entity;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage; namespace MediatApi.Helper
{
public class WyyDbContext: testContext
{
private IDbContextTransaction _currentTransaction; public IDbContextTransaction GetCurrentTransaction() => _currentTransaction;
public bool HasActiveTransaction => _currentTransaction != null; public async Task CommitTransactionAsync(IDbContextTransaction transaction)
{
if (transaction == null) throw new ArgumentNullException(nameof(transaction));
if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current");
try
{
await SaveChangesAsync();
transaction.Commit();
}
catch
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
}
}
在Program里面注册服务
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DBTransactionBehavior<,>));
这里链接数据库我们做一个新增Command里面的testContext就是数据库上下文我这里是从数据库直接生成的 WyyDbContext继承testContext
using MediatApi.Entity;
using MediatR; namespace MediatApi.Application.Command
{
public class CusCreateCommand:IRequest<int>
{
public string? Name { get; set; }
public int? Age { get; set; }
}
public class CusCreateCommandHandler : IRequestHandler<CusCreateCommand, int>
{
private readonly testContext _db; public CusCreateCommandHandler(testContext db)
{
_db = db;
} public async Task<int> Handle(CusCreateCommand request, CancellationToken cancellationToken)
{
Cu c = new()
{
Name = request.Name,
Age = request.Age, };
_db.Cus.Add(c);
Console.WriteLine("执行处理++++++++++++++++++++++++++++++++++");
return await _db.SaveChangesAsync();
}
}
}
为了增加对比性 我也新建了一个传统的services来新增Cus
using MediatApi.Entity; namespace MediatApi.services
{ public interface ICusService
{
Task<int> AddAsync();
}
public class CusService : ICusService
{
private readonly testContext _db; public CusService(testContext db)
{
_db = db;
} public async Task<int> AddAsync()
{
Cu c = new()
{
Name = "wyy",
Age = 18 };
_db.Cus.Add(c);
return await _db.SaveChangesAsync();
}
}
}
控制器里面两个新增 一个走MediatRy个走传统的Service
/// <summary>
/// 创建用户_mediator
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
[HttpPost]
public async Task<int> CusCreateMediator([FromBody] CusCreateCommand cmd)=> await _mediator.Send(cmd,HttpContext.RequestAborted);
/// <summary>
/// 创建用户 Service
/// </summary>
/// <returns></returns> [HttpPost]
public async Task<int> CusCreateService() => await _cusService.AddAsync();
运行可以发现 传统的Service就不会执行。MediatR
中具有与此类似的管线机制,可通过泛型接口 IPipelineBehavior<,>注册,使得我们在 Handle 真正执行前或后可以额外做一些事情:记录日志、对消息做校验、对数据做预处理数据库事务、记录性能较差的Handler 等等。
8、总结
MediatR的用法
a.IRequest、IRequest<T> 只有一个单独的Handler执行
b.Notification
,用于多个Handler。
对于每个 request 类型,都有相应的 handler 接口:
IRequestHandler<T, U>
实现该接口并返回Task<U>
RequestHandler<T, U>
继承该类并返回U
IRequestHandler<T>
实现该接口并返回Task<Unit>
AsyncRequestHandler<T>
继承该类并返回Task
RequestHandler<T>
继承该类不返回
Notification
Notification
就是通知,调用者发出一次,然后可以有多个处理者参与处理。
Notification
消息的定义很简单,只需要让你的类继承一个空接口 INotification
即可。而处理程序则实现 INotificationHandler<T>
接口的 Handle
方法
PASS:如果你想成为一个成功的人,那么请为自己加油,让积极打败消极,让高尚打败鄙陋,让真诚打败虚伪,让宽容打败褊狭,让快乐打败忧郁,让勤奋打败懒惰,让坚强打败脆弱,只要你愿意,你完全可以做最好的自己。
Asp.Net Core6.0中MediatR的应用CQRS的更多相关文章
- asp.net 2.0中新增的web.config的默认namespace功能 (转)
看上去这个题目比较长,但实际上,我在看资料时发现,这就是说,在asp.net 2.0中,只需要在web.config里定义你要用的那些namespace,则在aspx页面中就不需要再象1.1那样,用 ...
- 【转】如何在ASP.NET 2.0中定制Expression Builders
expressions是asp.net 2.0中的新特色,它可以使你在asp.net的页面里很方便的使用自定义的属性. 在ASPX页里只要使用$符号就可以访问到,你定制的属性了. 例如我们看个例子: ...
- asp.net MVC3.0 中@Html.Partial,@Html.Action,@Html.RenderPartial,@Html.RenderAction
asp.net MVC3.0 中@Html.Partial,@Html.Action,@Html.RenderPartial,@Html.RenderAction 1.带有Render的方法返回值是v ...
- 【翻译】asp.net core2.0中的token认证
原文地址:https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide token ...
- 使用asp.net 2.0中的SqlBulkCopy类批量复制数据
介绍:在软件开发中,把数据从一个地方复制到另一个地方是一个普遍的应用. 在很多不同的场合都会执行这个操作,包括旧系统到新系统的移植,从不同的数据库备份数据和收集数据. ASP.NET 2.0有一个Sq ...
- 最新版FreeTextBox(版本3.1.6)在ASP.Net 2.0中使用简介
http://www.cnblogs.com/kflwz/articles/1337310.html 1.下载最新版FreeTextBox(版本3.1.6),解压 FreeTextBox 3. ...
- 在ASP.NET Core2.0中使用百度在线编辑器UEditor(转)
一.起因 UEditor是百度旗下的富文本编辑器,对于后端上传处理仅提供了Asp.Net 版的支持. 如果想在.Net Core项目中使用,那么后台上传接口需要重构. UEditorNetCore:百 ...
- [转]ASP.NET 2.0中GridView无限层复杂表头的实现
本文转自:http://blog.csdn.net/net_lover/article/details/1306211 实现方法就是给单元格填充我们想要的格式代码. C# <%@ Page La ...
- (转)ASP.NET 2.0中的partial
1. 什么是局部类型? C# 2.0 引入了局部类型的概念.局部类型允许我们将一个类.结构或接口分成几个部分,分别实现在几个 不同的.cs文件中. 局部类型适用于以下情况: (1) 类型特别大,不宜放 ...
随机推荐
- 牛客CSP-S模拟题——十二桥问题
题面 n <= 50000,m <= 200000,k <= 12 题解 可以从K条边的两端和1结点出发各进行一次O(nlogn)的Dijk,然后就浓缩成了一个最多只有25个点的小完 ...
- Seatunnel超高性能分布式数据集成平台使用体会
@ 目录 概述 定义 使用场景 特点 工作流程 连接器 转换 为何选择SeaTunnel 安装 下载 配置文件 部署模式 入门示例 启动脚本 配置文件使用参数示例 Kafka进Kafka出的ETL示例 ...
- shellcode 注入执行技术学习
shellcode 注入执行技术学习 注入执行方式 CreateThread CreateRemoteThread QueueUserAPC CreateThread是一种用于执行Shellcode的 ...
- Postfix别名邮件与SASL验证
Postfix别名邮件与SASL验证 环境简介 系统: CentOS 8.3.2011 软件包: postfix-2:3.3.1-12.el8.x86_64 cyrus-sasl-2.1.27-5.e ...
- SpringMvc(二)- 请求处理参数 和 响应数据处理
1.请求处理参数 1.1 请求参数 @RequestParam 1.1.1 不使用 @RequestParam 注解 请求参数处理,不使用参数注解: 1.如果请求参数名和请求处理的形参名一致,spri ...
- 手撸Router,还要啥Router框架?react-router/vue-router躺一边去
有没有发现,在大家使用React/Vue的时候,总离不开一个小尾巴,到哪都得带着他,那就是react-router/vue-router,而基于它们的第三方框架又出现很多个性化约定和扩展,比如nuxt ...
- CVE-2022-39197(CobaltStrike XSS <=4.7)漏洞复现
最新文章更新见个人博客 漏洞说明 根据9.20日CobaltStrike官方发布的最新4.7.1版本的更新日志中介绍,<=4.7的teamserver版本存在XSS漏洞,从而可以造成RCE远程代 ...
- 使用 Loki 收集 Traefik 日志
转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247492264&idx=1&sn=f443c92664 ...
- SonarQube支持Gitlab授权登录
部署好SonarQube之后,由于我们内部使用的是自建的Gitlab仓库,即每个开发同学都有Gitlab账号,SonarQube我们就可以使用上Gitlab登录,这样就不需要再维护一套用户体系了. S ...
- NOIP2011 提高组 聪明的质监员(二分+前缀和)
看到这道题,应该都能想到用二分,那问题是怎么去判定呢? 我们考虑用前缀和(a1统计w,a2统计v),枚举每个矿石,,当前判定的值是x,如果该矿石的w>=x,a1[i]=a1[i-1]+1,a2[ ...