使用 Autofac, MediatR 和 FluentValidator 构建松耦合 ASP.NET Core API 应用
使用 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 andUnit)INotificationIStreamRequest
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 应用的更多相关文章
- 从零开始构建一个的asp.net Core 项目(二)
接着上一篇博客继续进行.上一篇博客只是显示了简单的MVC视图页,这篇博客接着进行,连接上数据库,进行简单的CRUD. 首先我在Controllers文件夹点击右键,添加->控制器 弹出的对话框中 ...
- 自动构建自己的ASP.NET Core基础镜像
在开发过程中,我们可以根据自身情况来定制自己的基础镜像,以便加快CI\CD构建速度以及提高开发体验.这里我们就以ASP.NET Core的基础镜像为例来进行讲解. 本次教程代码见开源库:https:/ ...
- 从零开始构建一个的asp.net Core 项目
最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到"He ...
- 从零开始构建一个的asp.net Core 项目(一)
最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到“Hello W ...
- [译]ASP.NET Core中使用MediatR实现命令和中介者模式
作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中,我将解释命令模式,以及如何利用基于命令模式的第三方库来实现它们,以及如何 ...
- ASP.NET Core 入门教程 1、使用ASP.NET Core 构建第一个Web应用
一.前言 1.本文主要内容 Visual Studio Code 开发环境配置 使用 ASP.NET Core 构建Web应用 ASP.NET Core Web 应用启动类说明 ASP.NET Cor ...
- 使用 autofac 实现 asp .net core 的属性注入
使用 autofac 代替 asp .net core 默认的 IOC 容器,可实现属性注入. 之前的使用方式不受影响. 源码已开源: dotnet-campus/Autofac.Annotation ...
- 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& ...
- Prism 4 文档 ---第9章 松耦合组件之间通信
当构建一个大而负责的应用程序时,通用的做法时将功能拆分到离散的模块程序集中.将模块之间的静态引用最小化.这使得模块可以被独立的开发,测试,部署和升级,以及它迫使松散耦合的沟通. 当在模块之间通信时,你 ...
- Microsoft Prism安装使用教程 搭建WPF松耦合架构框架
Microsoft Prism安装使用教程 搭建WPF松耦合架构框架 Prism是由微软Patterns & Practices团队开发的项目,目的在于帮助开发人员构建松散耦合的.更灵活.更易 ...
随机推荐
- go中能比较和不能比较的数据类型
在 Go 语言中,比较操作符(== 和 !=)可以用于许多数据类型,但也有一些数据类型不支持直接比较.下面详细解释哪些数据类型可以比较,哪些不能比较,以及相关的规则和原因. 可以比较的数据类型 布尔型 ...
- USB协议详解第10讲(USB描述符-报告描述符)
1.报告描述符的概念和作用 开门见山,报告描述符就是描述报告(HID接口上传输事务中的数据)的一组数据结构. 首先大家可能会问,报告又是什么?我们前面讲过,USB主机一般是以中断的方式向HID设备发送 ...
- 架构与思维:漫谈高并发业务的CAS及ABA
1 高并发场景下的难题 1.1 典型支付场景 这是最经典的场景.支付过程,要先查询买家的账户余额,然后计算商品价格,最后对买家进行进行扣款,像这类的分布式操作, 如果是并发量低的情况下完全没有问题的, ...
- kotlin协程——>基础、取消与超时
Kotlin使用挂起函数为异步操作,使用kotlinx.coroutines中的launch.async 1. 第⼀个协程程序 import kotlinx.coroutines.* fun main ...
- URL是什么
URL是什么 URL(Uniform Resource Locator,统一资源定位器) URL的组成: 协议://{域名|主机名|IP}:端口/路径/文件名?参数#锚点 协议 Scheme/Prot ...
- vue 父子组件之间传值
在vue中父子组件传值是必不可少的,大家必须要学会! 首先父组件向子组件传值: 父组件:比如我们传teacher为index,如果我们传值变量可以使用 : 绑定我们在data中定义的变量. 子组件:为 ...
- SSIS连接Excel2007版本之后的数据源
今天我发现了新大陆,兴奋得不得了,由于原文写得太过详细与专业,我就偷偷懒直接Copy过来了,怕自己以后没地儿找,哈哈哈 原文链接:https://www.cnblogs.com/biwork/p/34 ...
- PHP Excel Word 文件转 HTML输出
ob_end_clean(); $filePath = './123.xls'; //$filePath = './123.docx'; $savePath = './123.html'; //这里记 ...
- Mellanox hp 544+FLR QSFP 40G 网卡升级固件及性能测试
烧录命令参考视频: 相关视频: https://www.youtube.com/watch?v=_2-qPV1giEc flint -allow_psid_change -d /dev/mst/mt4 ...
- dotnet core微服务框架Jimu ~ 会员注册微服务
提供会员注册服务,用户必须注册成会员才能享受应用提供的服务,如浏览和发布新闻, 但有些服务又需要指定角色的会员才能操作,如所有会员都可以浏览新闻,只有管理员(admin)角色的会员才可以发布新闻. 有 ...