使用 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. [使用目前最新版]HybridCLR6.9.0+YooAsset2.2.4实现纯C# Unity热更新方案 (一)

    1.前言 什么是热更新 游戏或者软件更新时,无需重新下载客户端进行安装,而是在应用程序启动的情况下,在内部进行资源或者代码更新 Unity目前常用热更新解决方案 HybridCLR,Xlua,ILRu ...

  2. Js运算符(操作符)

    算数运算符 a = 1 + 1 // 2 a = 10 - 5 // 5 a = 10 / 5 // 2 a = 10 / 0 // js中除以0不会报错,结果是Infinity a = 2*2 // ...

  3. arm64 下内核 crash—— 非法地址

    下面是在实际工作中遇到的一次内核(5.4.110)访问非法内存地址(空指针)导致出错的现场,在这里记录一下简单的分析流程为以后遇到类似的问题作为参考. [ 220.619861] Unable to ...

  4. 技术分享 | 徐轶韬:从MySQL5.7升级到MySQL 8.0

    在6月20日举办的[墨天轮数据库沙龙-MySQL 5.7 停服影响与应对方案]中,甲骨文MySQL解决方案首席工程师徐轶韬分享了<从MySQL5.7升级到MySQL 8.0>主题演讲,本文 ...

  5. 激活windows教程

    新建bat文件 [批处理文件:后缀是 bat ] 输入代码: slmgr/skms kms.03k.org slmgr/ato 然后以管理员运行 :

  6. Android复习(四)权限—>仅在默认处理程序中使用的权限

    仅在默认处理程序中使用的权限 注意:本指南主要面向准备在 Google Play 商店发布应用的 Android 应用开发者.不过,无论您在哪里发布 Android 应用,为了保护用户隐私,最好都完成 ...

  7. Kubernetes 对接 GlusterFS 磁盘扩容实战

    前言 知识点 定级:入门级 使用 Heketi Topology 扩容磁盘 使用 Heketi CLI 扩容磁盘 实战服务器配置 (架构 1:1 复刻小规模生产环境,配置略有不同) 主机名 IP CP ...

  8. [图像处理] 基于CleanVision库清洗图像数据集

    CleanVision是一个开源的Python库,旨在帮助用户自动检测图像数据集中可能影响机器学习项目的常见问题.该库被设计为计算机视觉项目的初步工具,以便在应用机器学习之前发现并解决数据集中的问题. ...

  9. 饿了么element-ui的图标设置大小

    给element-ui的图标设置大小,其实就是给此组件或其父组件设置字体大小 方法一 需要给父盒子设置字体大小 效果如下 父组件scss样式: 子组件样式: 方法二 直接给当前组件设置字体大小!省事儿 ...

  10. bresenham算法(贝汉明算法)