使用 MediatR 和 FluentValidator

1. 创建示例文件夹 Sample

首先,创建示例文件夹 Sample。

2. 创建表示层项目 Web

在示例文件夹 Sample 中,使用标准的 dotnet 命令,基于 .NET 6 下 minimal WebAPI 项目创建示例项目。

dotnet new webapi -n Web

新项目将默认包含一个 WeatherForecastController 控制器。

3. 为 Web 项目增加 Autofac 支持

为 Web 项目增加 Autofac 依赖注入容器的支持。

我们将不使用微软默认的依赖注入容器,而使用 Autofac 依赖注入容器,它可以支持更多的特性,例如,可以以程序集为单位直接注入其中定义的服务,极大简化服务的注册工作。

Autofac 是 Autofac 的核心库,而 Autofac.Extensions.DependencyInjection 则提供了各种便于使用的扩展方法。

添加 NuGet 包

进入 Web 项目的目录,使用下面的命令添加 Autofac 库。

dotnet add package Autofac
dotnet add package Autofac.Extensions.DependencyInjection

添加针对 Autofac 依赖注入容器的支持。

以后,既可以继续使用基于微软的服务注册形式来注册服务,这样,原有的服务注册还可以继续使用。又可以使用 Autofac 提供的方式注册服务。修改 Program.cs 中的代码,下面代码中的 builder 即来自 Autofac 的类型为 Autofac.ContainerBuilder。

using Autofac;
using Autofac.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // 配置使用 Autofac
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// 配置 Autofac 容器
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{ });

以后,我们可以在这个 ConfigureContainer() 方法中,使用 Autofac 的方式来注册服务了。

4. 创建业务表示层项目 Application

回到 Sample 文件夹,在其中创建类库项目 Application,我们用来表示业务处理领域逻辑。

dotnet new classlib -n Application

这将会在 Sample 目录下创建名为 Application 的第二个文件夹,其中包含新创建的项目。

4. Add MediatR

在本例中,我们将使用基于 MediatR 的中介者模式来实现业务逻辑与表示层的解耦。

进入新创建的 Application 文件夹,为项目增加 MediatR 的支持。

dotnet add package MediatR

而 MediatR.Contracts 包中仅仅包含如下类型:

  • IRequest (including generic variants and Unit)
  • INotification
  • IStreamRequest

5. 在 Application 中定义业务处理

首先,删除默认生成的 Class1.cs 文件。

然后,创建 Models 文件夹,保存我们使用的数据模型。在我们的示例中,这个模型就是 Web 项目中的 WeatherForecast ,我们将它从 Web 项目的根目录,移动到 Models 文件夹中,另外,将它的命名空间也修改为 Application。修改之后如下所示:

namespace Application;

public class WeatherForecast
{
public DateTime Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; set; }
}

然后,在 Application 项目中创建名为 Commands 的文件夹来保存我们定义的业务处理。我们定义的所有命令和对应的处理器都将保存在这个文件夹中。

我们定义查询天气的命令对象 QueryWeatherForecastCommand,它需要实现 MediatR 的接口 IRequest<T>,所以,记得使用它的命名空间 MediatR。这个命令处理之后的返回结果的类型是 WeatherForecast[] 数组类型,我们再增加一个 Name 属性,来表示天气预报所对应的地区名称,全部代码如下所示:

namespace Application;

using MediatR;

public class QueryWeatherForecastCommand: IRequest<WeatherForecast[]>
{
public string Name { get; set; }
}

在这个文件夹中,继续定义这个操作所对应的处理器。

处理器需要实现的接口是 IRequestHandler<in TRequest, TResponse>

namespace Application;

using MediatR;

public class QueryWeatherForecastCommandHandler
: IRequestHandler<QueryWeatherForecastCommand, WeatherForecast[]>
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
}; public Task<WeatherForecast[]> Handle(QueryWeatherForecastCommand command, CancellationToken cancellationToken)
{
var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray(); return Task.FromResult(result);
}
}

6. 在 Web 项目中使用 Application 定义的处理

回到 Web 文件夹中,为 Web 项目添加 MediatR 的支持,同时还需要添加 MediatR 对 Autofac 的支持库。

dotnet add package MediatR
dotnet add package MediatR.Extensions.Autofac.DependencyInjection;

为 Web 项目添加对 Application 项目的引用。

dotnet add reference ../Application/Application.csproj

在 WeatherForecastController.cs 中间顶部,添加对 Application 项目的引用,以支持 WeatherForecast 类型,它现在已经被转移到了 Application 项目中。

为了便于使用 Autofac 的模块注册功能,在 Web 项目中创建文件夹 AutofacModules,在其中创建 MediatorModule.cs 代码文件。文件内容可以先为空。

namespace Web;

using Autofac;
using Autofac.Extensions.DependencyInjection; public class MediatorModule : Autofac.Module {
protected override void Load (ContainerBuilder builder) { }
}

而在控制器 WeatherForecastController 中,由于原来的处理逻辑已经被转移到命令处理器中,所以,可以删除这里的处理逻辑代码,现在它应该如下所示:

using Application;
using Microsoft.AspNetCore.Mvc; namespace Web.Controllers; [ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
} [HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return null;
}
}

然后,修改 Program.cs 中的代码,使用 Autofac 注册 MediatR。

需要注意的是,我们的命令和处理器定义在 Application 程序集中。

// 配置使用 Autofac
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// 配置 Autofac 容器
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
// 注册 MediatR
// 来自 NuGet package: MediatR.Extensions.Autofac.DependencyInjection
builder.RegisterMediatR(typeof(Application.QueryWeatherForecastCommand).Assembly); // 注册模块
builder.RegisterModule<Web.MediatorModule>();
});

重新回到 WeatherForecastController 文件,使用新定义的 MediatR 方式。

using Application;
using Microsoft.AspNetCore.Mvc;
using MediatR; namespace Web.Controllers; [ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly IMediator _mediator; public WeatherForecastController(
ILogger<WeatherForecastController> logger,
IMediator mediator)
{
_logger = logger;
_mediator = mediator;
} [HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
var result = await _mediator.Send(
new QueryWeatherForecastCommand { Name="Hello" }
);
return result;
}
}

重新编译并运行程序,现在它应该和以前一样可以访问,并获得天气预报数据。

7 . 为 QueryWeatherForecastCommand 增加验证支持

ASP.NET Core 是原生支持模型验证的,现在我们使用 FluentValidation 来重新实现。

在 Application 项目中,添加 FluentValidation 包。同时,为了能够使用日志,我们还需要添加 Microsoft.Extensions.Logging.Abstractions 包。

dotnet add package FluentValidation
dotnet add package Microsoft.Extensions.Logging.Abstractions

在 Application 项目中,增加文件夹 Validatiors。并添加针对 QueryWeatherForecastCommand 的验证器。

代码实现如下:

using Application;

using FluentValidation;
using Microsoft.Extensions.Logging; public class QueryWeatherForecastCommandValidator : AbstractValidator<QueryWeatherForecastCommand>
{
public QueryWeatherForecastCommandValidator(
ILogger<QueryWeatherForecastCommandValidator> logger
)
{
RuleFor(c => c.Name).NotEmpty(); logger.LogInformation("----- INSTANCE CREATED - {ClassName}", GetType().Name);
}
}

重新编译项目,通过编译。

下面,我们使用 MediatR 的命令处理管道来支持验证。

在 Application 项目中,增加文件夹 Behaviors,在其中创建验证处理。

namespace  Application;

using MediatR;
using Microsoft.Extensions.Logging;
using FluentValidation; public class ValidatorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly ILogger<ValidatorBehavior<TRequest, TResponse>> _logger;
private readonly IEnumerable<IValidator<TRequest>> _validators; public ValidatorBehavior(IEnumerable<IValidator<TRequest>> validators, ILogger<ValidatorBehavior<TRequest, TResponse>> logger)
{
_validators = validators;
_logger = logger;
} public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var typeName = request.GetGenericTypeName(); _logger.LogInformation("----- Validating command {CommandType}", typeName); var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(error => error != null)
.ToList(); if (failures.Any())
{
_logger.LogWarning("Validation errors - {CommandType} - Command: {@Command} - Errors: {@ValidationErrors}", typeName, request, failures); throw new Exception(
$"Command Validation Errors for type {typeof(TRequest).Name}", new ValidationException("Validation exception", failures));
} return await next();
}
}

其中的 GetGenericTypeName() 是一个扩展方法 ,定义在 Extensions 文件夹中的 GenericTypeExtensions 类中。

该扩展方法定义如下:

namespace Application;
/*
* 扩展方法,用于获取对象实例或者类型的字符串名称
*/
public static class GenericTypeExtensions
{
public static string GetGenericTypeName(this Type type)
{
string typeName; if (type.IsGenericType)
{
var genericTypes = string.Join(",", type.GetGenericArguments().Select(t => t.Name).ToArray());
typeName = $"{type.Name.Remove(type.Name.IndexOf('`'))}<{genericTypes}>";
}
else
{
typeName = type.Name;
} return typeName;
} public static string GetGenericTypeName(this object @object)
{
return @object.GetType().GetGenericTypeName();
}
}

回到 Web 项目中,我们注册定义的验证器,并定义 MediatR 的处理管道。将 MediatorModule 代码修改为如下所示:

namespace Web;

using System.Reflection;
using Application;
using Autofac;
using FluentValidation;
using MediatR; public class MediatorModule : Autofac.Module {
protected override void Load (ContainerBuilder builder) {
// Register the Command's Validators (Validators based on FluentValidation library)
builder
.RegisterAssemblyTypes(
typeof(QueryWeatherForecastCommandValidator).GetTypeInfo().Assembly)
.Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
.AsImplementedInterfaces(); builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); }
}

重新编译并运行,可以在控制台,看到如下输出:

info: QueryWeatherForecastCommandValidator[0]
----- INSTANCE CREATED - QueryWeatherForecastCommandValidator
info: Application.ValidatorBehavior[0]
----- Validating command QueryWeatherForecastCommand

8. 增加 MediatR 处理管道日志支持

在 Application 的 Behaviors 文件夹下,增加 LoggingBehavior.cs 文件。它将会在调用实际的处理之前和之后记录日志。

namespace  Application;

using MediatR;
using Microsoft.Extensions.Logging; public class LoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger) => _logger = logger; public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_logger.LogInformation("----- Handling command {CommandName} ({@Command})", request.GetGenericTypeName(), request);
var response = await next();
_logger.LogInformation("----- Command {CommandName} handled - response: {@Response}", request.GetGenericTypeName(), response); return response;
}
}

回到 Web 项目中的 MediatorModule 文件,在验证处理之前增加日志支持,特别需要注意注册的顺序。

namespace Web;

using System.Reflection;
using Application;
using Autofac;
using FluentValidation;
using MediatR; public class MediatorModule : Autofac.Module {
protected override void Load (ContainerBuilder builder) {
// Register the Command's Validators (Validators based on FluentValidation library)
builder
.RegisterAssemblyTypes(
typeof(QueryWeatherForecastCommandValidator).GetTypeInfo().Assembly)
.Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
.AsImplementedInterfaces(); builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>)); }
}

重新编译运行,访问 WeatherForecase API,可以在控制台输出中看到:

info: QueryWeatherForecastCommandValidator[0]
----- INSTANCE CREATED - QueryWeatherForecastCommandValidator
info: Application.LoggingBehavior[0]
----- Handling command QueryWeatherForecastCommand (Application.QueryWeatherForecastCommand)
info: Application.ValidatorBehavior[0]
----- Validating command QueryWeatherForecastCommand
info: Application.LoggingBehavior[0]
----- Command QueryWeatherForecastCommand handled - response: Application.WeatherForecast, Application.WeatherForecast, Application.WeatherForecast, Application.WeatherForecast, Application.WeatherForecast

9. 参考资料

使用 Autofac, MediatR 和 FluentValidator 构建松耦合 ASP.NET Core API 应用的更多相关文章

  1. 从零开始构建一个的asp.net Core 项目(二)

    接着上一篇博客继续进行.上一篇博客只是显示了简单的MVC视图页,这篇博客接着进行,连接上数据库,进行简单的CRUD. 首先我在Controllers文件夹点击右键,添加->控制器 弹出的对话框中 ...

  2. 自动构建自己的ASP.NET Core基础镜像

    在开发过程中,我们可以根据自身情况来定制自己的基础镜像,以便加快CI\CD构建速度以及提高开发体验.这里我们就以ASP.NET Core的基础镜像为例来进行讲解. 本次教程代码见开源库:https:/ ...

  3. 从零开始构建一个的asp.net Core 项目

    最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到"He ...

  4. 从零开始构建一个的asp.net Core 项目(一)

    最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到“Hello W ...

  5. [译]ASP.NET Core中使用MediatR实现命令和中介者模式

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中,我将解释命令模式,以及如何利用基于命令模式的第三方库来实现它们,以及如何 ...

  6. ASP.NET Core 入门教程 1、使用ASP.NET Core 构建第一个Web应用

    一.前言 1.本文主要内容 Visual Studio Code 开发环境配置 使用 ASP.NET Core 构建Web应用 ASP.NET Core Web 应用启动类说明 ASP.NET Cor ...

  7. 使用 autofac 实现 asp .net core 的属性注入

    使用 autofac 代替 asp .net core 默认的 IOC 容器,可实现属性注入. 之前的使用方式不受影响. 源码已开源: dotnet-campus/Autofac.Annotation ...

  8. List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac

    List多个字段标识过滤 class Program{  public static void Main(string[] args) { List<T> list = new List& ...

  9. Prism 4 文档 ---第9章 松耦合组件之间通信

    当构建一个大而负责的应用程序时,通用的做法时将功能拆分到离散的模块程序集中.将模块之间的静态引用最小化.这使得模块可以被独立的开发,测试,部署和升级,以及它迫使松散耦合的沟通. 当在模块之间通信时,你 ...

  10. Microsoft Prism安装使用教程 搭建WPF松耦合架构框架

    Microsoft Prism安装使用教程 搭建WPF松耦合架构框架 Prism是由微软Patterns & Practices团队开发的项目,目的在于帮助开发人员构建松散耦合的.更灵活.更易 ...

随机推荐

  1. go中能比较和不能比较的数据类型

    在 Go 语言中,比较操作符(== 和 !=)可以用于许多数据类型,但也有一些数据类型不支持直接比较.下面详细解释哪些数据类型可以比较,哪些不能比较,以及相关的规则和原因. 可以比较的数据类型 布尔型 ...

  2. USB协议详解第10讲(USB描述符-报告描述符)

    1.报告描述符的概念和作用 开门见山,报告描述符就是描述报告(HID接口上传输事务中的数据)的一组数据结构. 首先大家可能会问,报告又是什么?我们前面讲过,USB主机一般是以中断的方式向HID设备发送 ...

  3. 架构与思维:漫谈高并发业务的CAS及ABA

    1 高并发场景下的难题 1.1 典型支付场景 这是最经典的场景.支付过程,要先查询买家的账户余额,然后计算商品价格,最后对买家进行进行扣款,像这类的分布式操作, 如果是并发量低的情况下完全没有问题的, ...

  4. kotlin协程——>基础、取消与超时

    Kotlin使用挂起函数为异步操作,使用kotlinx.coroutines中的launch.async 1. 第⼀个协程程序 import kotlinx.coroutines.* fun main ...

  5. URL是什么

    URL是什么 URL(Uniform Resource Locator,统一资源定位器) URL的组成: 协议://{域名|主机名|IP}:端口/路径/文件名?参数#锚点 协议 Scheme/Prot ...

  6. vue 父子组件之间传值

    在vue中父子组件传值是必不可少的,大家必须要学会! 首先父组件向子组件传值: 父组件:比如我们传teacher为index,如果我们传值变量可以使用 : 绑定我们在data中定义的变量. 子组件:为 ...

  7. SSIS连接Excel2007版本之后的数据源

    今天我发现了新大陆,兴奋得不得了,由于原文写得太过详细与专业,我就偷偷懒直接Copy过来了,怕自己以后没地儿找,哈哈哈 原文链接:https://www.cnblogs.com/biwork/p/34 ...

  8. PHP Excel Word 文件转 HTML输出

    ob_end_clean(); $filePath = './123.xls'; //$filePath = './123.docx'; $savePath = './123.html'; //这里记 ...

  9. Mellanox hp 544+FLR QSFP 40G 网卡升级固件及性能测试

    烧录命令参考视频: 相关视频: https://www.youtube.com/watch?v=_2-qPV1giEc flint -allow_psid_change -d /dev/mst/mt4 ...

  10. dotnet core微服务框架Jimu ~ 会员注册微服务

    提供会员注册服务,用户必须注册成会员才能享受应用提供的服务,如浏览和发布新闻, 但有些服务又需要指定角色的会员才能操作,如所有会员都可以浏览新闻,只有管理员(admin)角色的会员才可以发布新闻. 有 ...