使用 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. Android Perfetto 系列 1:Perfetto 工具简介

    2019 年开始写 Systrace 系列,陆陆续续写了 20 多篇,从基本使用到各个模块在 Systrace 上的呈现,再到启动速度.流畅性等实战,基本上可以满足初级系统开发者和 App 开发者对于 ...

  2. 墨天轮沙龙 | 亚马逊云科技李君:见微知著 - Serverless云原生数据库概览

    导读 以业务为导向的数据库需要满足现代化应用的需要,以 Serverless 数据库为代表,云数据库正在迅速发展成熟,并带来更好的可访问性和高可用性,还有高扩展性与可迁移性. [墨天轮数据库沙龙-Se ...

  3. 墨天轮国产数据库沙龙 | 胡津铭:时序数据库DolphinDB,从量化金融到万物互联

    分享嘉宾:胡津铭 DolphinDB研发副总监 整理:墨天轮社区 导读 DolphinDB是高性能分布式时序数据库,集成了功能强大的编程语言和高容量高速度的流数据分析系统,为海量结构化数据的快速存储. ...

  4. iOS之动画(transform和UIView动画)学习

    1.transform 形变 这个是UIView的属性,继承UIView的控件都具有这个属性 UIImageView *imageview=[[UIImageView alloc]init]; ima ...

  5. day12-包机制

    包机制 为了更好地组织类,Java提供了包机制,用于区别类名的命名空间. 包语句的语法格式为: 包的本质就是文件夹  package pkg1[.pkg2[.pkg3...]]; 一般公司域名倒置作为 ...

  6. Centos7 安装配置FTP服务

    1.FTP简介 ftp(File Transfer Protocol文件传输协议)是基于TCP/IP 协议的应用层协议,用于文件的传输,包括ftp服务器(或服务端)和ftp客户端 FTP协议会在客户端 ...

  7. JavaScript 语句后可以省略分号么?

    摘自知乎:https://www.zhihu.com/question/20298345 田乐:加与不加是风格问题,风格争议不需要有个定论.关键的问题在于如何"争论",处理好冲突, ...

  8. Linux新建用户无法登陆系统的解决方案

    前言 出现这个问题的原因,就是大家没有从基础开始学Linux,导致很多基础操作不会使用,遇到问题反而用搜索引擎搜索,得到一堆相似的答案,你就信了,因为重复就是权威.而你不清楚的是,这个答案是无数人复制 ...

  9. 【填算符】(log 值域的做法)

    比赛在这里呢 填算符 下发题解说的神马东西,赛时根本想不到 讲一个赛时想得到的 \(O(n\log 值域)\) 的思路,很好理解 我们处理出二进制下每一位上的 1 的最后一次出现的位置,将第 \(i\ ...

  10. 初识GO语言--并发