roslyn分析字符串代码_.NET 5 源代码生成器——MediatR——CQRS

在这篇文章中,我们将探索如何使用.NET 5中的新source generator特性,使用MediatR库和CQRS模式自动为系统生成API。

中介者模式

中介模式是在应用程序中解耦模块的一种方式。在基于web的应用程序中,它通常用于将前端与业务逻辑的解耦。

在.NET平台上,MediatR库是该模式最流行的实现之一。如下图所示,中介器充当所发送命令的发送方和接收方之间的中间人。发送者不知道也不关心谁在处理命令。

53003a29a1d37b26509c2837bdfb39f9.png

使用MediatR,我们定义了一个command,它实现IRequest接口,其中T表示返回类型。在这个例子中,我们有一个CreateUser类,它将返回一个字符串给调用者:

public class CreateUser : IRequest<string>{    public string id { get; set; }    public string Name { get; set; }}

从ASP.NET Core API发送命令到MediatR,我们可以使用以下代码:

[Route("api/[controller]")][ApiController]public class CommandController : ControllerBase{    private readonly IMediator _mediator;    public CommandController(IMediator mediator)    {        _mediator = mediator;    }    [HttpPost]    public async Task<string> Get(CreateUser command)    {        return await _mediator.Send(command);    }}

在接收端,实现也非常简单:创建一个实现IRequestHandler接口的类。在本例中,我们有一个处理程序,它处理CreateUser并向调用者返回一个字符串:

public class CommandHandlers : IRequestHandlerstring={    public Task<string> Handle(CreateUser request,                                CancellationToken cancellationToken)    {        return Task.FromResult("User Created");    }}

每个处理程序类可以处理多个命令。处理规则是对于一个特定的命令,应该总是只有一个处理程序。如果希望将消息发送给许多订阅者,则应该使用MediatR中的内置通知功能,但在本例中我们将不使用该功能。

CQRS

Command Query Responsibility Segregation(CQRS)是一个非常简单的模式。它要求我们应该将系统中的命令(写)的实现与查询(读)分离开来。

有了CQRS,我们会从这样做:

dec956f4b12255051cccce2bbcd78c25.png

改为这样做:

4b9035cb24094520a3ab571a2e534195.png

CQRS通常与event sourcing相关联,但是使用CQRS并不需要使用event sourcing,而仅仅使用CQRS本身就会给我们带来很多架构上的优势。这是为什么呢?因为读写的需求通常是不同的,所以它们需要单独的实现。

Mediator + CQRS

在示例应用程序中结合这两种模式,我们可以创建如下的架构:

b0464849675fd73b79cf0287f7478008.png

 Command和Query

使用MediatR,Command和Query之间没有明显的分离,因为两者都将实现IRequest接口。为了更好地分离它们,我们将引入以下接口: 

public interface ICommand<T> : IRequest<T>{}public interface IQuery<T> : IRequest<T>{}

下面是使用这两个接口的示例:

public record CreateOrder : ICommand<string>{    public int Id { get; set; }    public int CustomerId { get; set; }}public record GetOrder : IQuery{    public int OrderId { get; set; }}

为了进一步改进我们的代码,我们可以使用新的C# 9 record特性。在内部,它仍然是一个类,但是我们为我们生成了很多样板代码,包括equality, GetHashCode, ToString……

 前端Command和Query

要真正从外部接收Command和Query,我们需要创建一个ASP.NET Core API。这些action方法将接收传入的HTTP命令,并将它们传递给MediatR以进行进一步处理。控制器可能是这样的:

[Route("api/[controller]")][ApiController]public class CommandController : ControllerBase{    private readonly IMediator _mediator;    public CommandController(IMediator mediator)    {        _mediator = mediator;    }    [HttpPost]    public async Task<string> CreateOrder([FromBody] CreateOrder command)    {        return await _mediator.Send(command);    }    [HttpPost]    public async TaskGetOrder([FromBody] GetOrder command)    {        return await _mediator.Send(command);    }}

然后,MediatR将把Command和Query传递给各种处理程序,这些处理程序将处理它们并返回响应。应用CQRS模式,我们将为Command和Query处理程序使用单独的类。

public class CommandHandlers : IRequestHandlerstring={    public Task<string> Handle(CreateOrder request, CancellationToken ct)    {        return Task.FromResult("Order created");    }}public class QueryHandlers : IRequestHandler"">{    public Task Handle(GetOrder request, CancellationToken ct)    {        return Task.FromResult(new Order()        {            Id = 2201,            CustomerId = 1234,            OrderTotal = 9.95m,            OrderLines = new List()        });    }}

源代码生成器 

这是Roslyn编译器中的一个新特性,它允许我们hook到编译器,并在编译过程中生成额外的代码。

在一个非常高的层次上,你可以看到它如下:

  1. 首先,编译器编译你的C#源代码并生成语法树。
  2. 然后,源代码生成器可以检查这个语法树并生成新的C#源代码。
  3. 然后,这个新的源代码被编译并添加到最终的输出中。

重要的是要知道源代码生成器永远不能修改现有的代码,它只能向应用程序添加新代码。

2338d2c1ba3b4bed9a2f95463adef827.png

源代码生成器+MediatR+CQRS

对于我们实现的每个Command和Query,我们必须编写相应的ASP.NET Core action方法。

add1322308bc81a5dab3d9b7ef9eb9ac.png

这意味着如果我们的系统中有50个Command和Query,我们需要创建50个action方法。当然,这将是相当乏味的、重复的和容易出错的。

但是,如果仅仅基于Command/Query,我们就可以生成API代码作为编译的一部分,这不是很酷吗?就像这样:

d763546be0b07714c51a994638fd162b.png

意思是,如果我创建这个Command类:

/// /// Create a new order/// /// /// Send this command to create a new order in the system for a given customer/// public record CreateOrder : ICommand<string>{    ///     /// OrderId    ///     /// This is the customers internal ID of the order.          /// 123     [Required]    public int Id { get; set; }    ///     /// CustomerID    ///     /// 1234    [Required]    public int CustomerId { get; set; }}

然后,源生成器将生成以下类,作为编译的一部分:

/// /// This is the controller for all the commands in the system/// [Route("api/[controller]/[Action]")][ApiController]public class CommandController : ControllerBase{    private readonly IMediator _mediator;    public CommandController(IMediator mediator)    {        _mediator = mediator;    }    ///     /// Create a new order    ///     ///     /// Send this command to create a new order in the system for a given customer    ///     /// An instance of the CreateOrder    /// The status of the operation    /// Returns the newly created item    /// If the item is null       [HttpPost]    [Produces("application/json")]    [ProducesResponseType(typeof(string), StatusCodes.Status201Created)]    [ProducesResponseType(StatusCodes.Status400BadRequest)]    public async Task<string> CreateOrder([FromBody] CreateOrder command)    {        return await _mediator.Send(command);    }}

使用OpenAPI生成API文档

幸运的是是Swashbuckle包含在ASP.NET Core 5的API模板默认情况下,会看到这些类并为我们生成漂亮的OpenAPI (Swagger)文档!

2974636d245fff2aad67fa0778a9fe02.png

看看我的代码

他是这样组成的:

2d17dd2808c944d6f0282357671a38a7.png

  • SourceGenerator

     这个项目包含实际的源生成器,它将生成API控制器action方法。

  • SourceGenerator-MediatR-CQRS

  1. 这是一个使用源代码生成器的示例应用程序。查看项目文件,以了解该项目如何引用源生成器。

  2. Templates这个文件夹包含Command和Query类的模板。源代码生成器将把生成的代码插入到这些模板中。

  3. CommandAndQueries基于此文件夹中定义的Command和Query,生成器将生成相应的ASP.NET终结点。

查看生成的代码

我们如何看到生成的源代码?通过将这些行添加到API项目文件中,我们可以告诉编译器将生成的源代码写到我们选择的文件夹中:

<EmitCompilerGeneratedFiles>   TrueEmitCompilerGeneratedFiles><CompilerGeneratedFilesOutputPath>   $(BaseIntermediateOutputPath)\GeneratedFilesCompilerGeneratedFilesOutputPath>

 这意味着你可以在这个目录中找到生成的代码:

\obj\GeneratedFiles\SourceGenerator\SourceGenerator.MySourceGenerator

在这个文件夹里你会找到以下两个文件:

e55bdf2ef95a5b9550766a590b05b310.png

结论

通过引入源代码生成器的概念,我们可以删除大量必须编写和维护的样板代码。我不是编译器工程师,我在源代码生成器方面的方法可能不是100%最优的(甚至不是100%正确的),但它仍然表明任何人都可以创建自己的源代码生成器,而没有太多麻烦。

 欢迎关注我的公众号,如果你有喜欢的外文技术文章,可以通过公众号留言推荐给我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/529873.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql跟memcache的区别_MySQL-mysql Memory Storage Engine 和memcache到底有何不同?各自的优缺点是什么?...

Memory存储引擎将表的数据存放在内存中&#xff0c;默认使用哈希索引&#xff0c;memcache同样是使用哈希的方式将数据存在内存中&#xff0c;不过它们还是有比较大的区别的&#xff0c;我列几条&#xff1a;1.MEMORY表使用固定的记录长度格式&#xff0c;像VARCHAR这样的可变长…

vb用adodb链接mysql找不到_VB使用ADODB操作数据库的常用方法

ADO常用方法下面是我所掌握的使用ADO对数据库操作的一些常用方法&#xff0c;主要是提供给初学者作为参考&#xff0c;有不对的地方请指正。如有补充不胜荣幸准备工作DimconnAsNewADODB.Connection创建一个 Connection 实例&#xff0c;在这里使用New等于将Dim和Set合并为一段代…

mysql2008怎么重装_SQL Server2005、2008如何彻底删除卸载并重新安装?

很多小伙伴在删除SQL Server后重新安装总是遇到诸多问题导致安装失败&#xff0c;这是因为你没有将SQL Server彻底删除卸载完毕。本文就如何彻底卸载进行详细介绍&#xff0c;本文以SQL Server2008为例&#xff0c;2005的方法相似。方法/步骤1.按照传统方法将SQL Server 2005、…

micropython编译原理_C语言嵌入式Linux高级编程第9期:CPU和操作系统入门视频课程...

嵌入式开发是一门交叉学科。它要求我们的嵌入式工程师&#xff0c;不仅学习C语言、汇编、软件工程等软件层面的知识技能&#xff0c;还要求对CPU内部工作机制、计算机系统架构、操作系统原理、编译器等都有一个全局的认识和把握。而现实情况是&#xff0c;很多嵌入式的学员&…

mysql主从复制 火墙_MySQL高级知识(十五)——主从复制

前言&#xff1a;本章主要讲解MySQL主从复制的操作步骤。由于环境限制&#xff0c;主机使用Windows环境&#xff0c;从机使用用Linux环境。另外MySQL的版本最好一致&#xff0c;笔者采用的MySQL5.7.22版本&#xff0c;具体安装过程请查询相关资料。1.主从复制的基本原理slave会…

python在人工智能领域的应用论文_Python的应用领域主要在人工智能方面。-智慧树计算机专业导论章节答案...

计算机专业导论:Python的应用领域主要在人工智能方面。【?ж???】A:错B:对计算机专业导论章节测试答案&#xff1a;对更多相关问题2020年直流电弧特性虚拟仿真实验智慧树章节测试答案电力电子技术(山东联盟—中国石油大学(华东))_智慧树_答案中国大学_《说文解字》与上古社…

正则只能出现特定字符_python正则表达式的简单使用总结

原文作者&#xff1a;小哲&#xff0c;雷锋网在编程中&#xff0c;经常会涉及到字符串的操作&#xff0c;一个常用的策略就是利用split函数&#xff0c;然后对于特定的字符串进行匹配&#xff0c;但是这种方法格式复杂&#xff0c;可复用性较差。正则表达式是处理字符串匹配一个…

MySQL配置日志服务器_mysql配置数据库日志

mysql配置数据库日志云服务器(Elastic Compute Service&#xff0c;简称ECS)是阿里云提供的性能卓越、稳定可靠、弹性扩展的IaaS(Infrastructure as a Service)级别云计算服务。云服务器ECS免去了您采购IT硬件的前期准备&#xff0c;让您像使用水、电、天然气等公共资源一样便捷…

python外星人入侵不显示子弹_【Python】python外星人入侵,武装飞船,代码写好后,不显示子弹...

按照书上写的武装飞船&#xff0c;写到能够左右移动了&#xff0c;但到了射击(装子弹)时候&#xff0c;按照书上的代码照搬了&#xff0c;运行时没显示代码有问题&#xff0c;但就是按了空格键&#xff0c;不见有子弹&#xff0c;其他都正常。代码&#xff1a;alien_invasion.p…

python常见的异常类有哪些_Python常见异常类型

标签&#xff1a;常见 nic 警告 werror pytho 无效 本地变量 计算 pointBaseException     所有异常的基类SystemExit             解释器请求退出KeyboardInterrupt          用户中断执行(通常是输入^C)Exc…

mysql dos 下切换连接_如何在dos下连接进入mysql对数据库进行操作

一、&#xff1a;windowsR 进入命令命令提示符输入 net start mysql若出现系统错误&#xff0c;要将命令提示符以管理员身份运行若出现不是内部指令&#xff0c;进入下面环境变量设置界面&#xff0c;配置path路径1.进入系统查看界面&#xff0c;点击高级系统设置2&#xff0c…

mysql二级缓存redis_SpringBoot+Mybatis+redis(二级缓存)搭建

刚刚开始接触Spring Boot&#xff0c;因为极简单的配置开发&#xff0c;搭建一个通用的Spring BootMybaitisredis的开发框架。一、用maven构建项目&#xff0c;pom.xml文件如下&#xff1a;org.springframework.bootspring-boot-starter-parent1.5.1.RELEASEorg.springframewor…

mysql自定义两个条件排序_使用MySQL中的两个不同列进行自定义排序?

为此&#xff0c;将ORDER BY子句与CASE语句一起使用。让我们首先创建一个表-mysql> create table DemoTable1610-> (-> Marks int,-> Name varchar(20)-> ) ;使用插入命令在表中插入一些记录-mysql> insert into DemoTable1610 values(85,John);mysql> in…

java获取文件大小_Java中获取文件大小的详解及实例代码

Java 获取文件大小今天写代码时需要实现获取文件大小的功能&#xff0c;目前有两种实现方法&#xff0c;一种是使用File的length()方法&#xff1b;另外一种是使用FileInputStream的available()方法&#xff0c;当InputStream未进行read操作时&#xff0c;available()的大小应该…

java访问权限friendly_Java的访问权限

一&#xff0e;Java访问权限饰词(access specifiers)Java有public、protect、friendly、private四种访问权限&#xff0c;并且这四访问权限的访问范围越来越小。1&#xff0e; friendly1) 果一个class内的数据成员或方法没有任何权限饰词&#xff0c;那么它的缺省访问权限就是f…

java 0 255_java – 什么(float)(par4 16255)/ 255.0F;意思?

带alpha通道的RGB(通常称为RGBA或aRGB)是四个字节打包成一个整数.AAAAAAAARRRRRRRRBBBBBBBBGGGGGGGG // the original par4, each char represents one bit.// where ARBG stands for alpha, red, blue and green bit.shift和运算符用于检索每个字节.例如,par4>> 16&…

java ie下载文件名乱码问题_php中强制下载文件的代码(解决了IE下中文文件名乱码问题)...

中间遇到一个问题是提交的中文文件名直接放到header里在IE下会变成乱码&#xff0c;解决方法是将文件名先urlencode一下再放入header&#xff0c;如下。$file_name urlencode($_REQUEST[filename]);header("Pragma: public"); header("Expires: 0");heade…

java如何获得当前路径_在java中如何得到当前路径

归纳一些网上取java路径的方法&#xff1a;注明&#xff1a;如果从ANT启动程序&#xff0c;this.getClass().getResource("")取出来的比较怪&#xff0c;直接用JAVA命令行调试就可成功。得到classpath和当前类的绝对路径的一些方法获得CLASSPATH之外路径的方法&#…

java继承总结_JAVA笔记:Java中的继承总结

继承&#xff1a;在Java中使用extends关键字来实现类的继承 &#xff0c;extends意思就是派生&#xff0c;所以子类也叫派生类&#xff0c;继承的主要目的是扩展类的内容操作格式&#xff1a; class A{}; class B extends A{};子类B可以继承父类A中的公用方法&#xff0c;也可…

java正则表达式 类_java正则表达式相关类的使用

import java.util.regex.Matcher;import java.util.regex.Pattern;public class TestZhengZe {public static void main(String[] args) {//匹配数字Pattern mac Pattern.compile("-?(0|([1-9][0-9]*))(\\.[0-9])?");System.out.println(mac.matcher("101.00…