马上国庆了,dotNative 预祝大家假期愉快,Happy National Day!
本文将介绍在 .net6
平台的 asp.net core webapi
框架中,如何使用 abp vnext
框架进行模块化开发,重在思想理解。
ABP vNext
本身是一个包含许多 NuGet
包的 模块化(Module) 框架。它还提供了一个完整的基础架构来开发你自己的具有实体、服务、数据库集成、API、UI 组件等等功能的应用程序模块。
ABP vNext
框架是一个基于 ASP.NET Core
的完整基础设施,通过遵循软件开发最佳实践和最新技术来创建现代 web 应用程序和 API,不同于老的 ABP
框架。新的 ABP vNext
框架核心库更加精简,因为将原有许多的组件从其核心库抽离成独立的组件。这样开发人员可以更加灵活的选择自己需要的功能进行集成,使项目远离臃肿的库,比起原有的 ABP
框架 ABP vNext
完全基于 ASP.NET Core
丢掉了历史包袱,设计更加合理,更加细粒度的模块化设计。
ABP vNext
框架是一个集成多个第三方组件类库的一个应用程序集,遵循 模块化(Module) 思想实践的最佳方式。
通过 dotnet cli
命令新建 asp.net core webapi
项目,命名为 Demo.Abp.WebApplication1
,执行如下命令:
dotnet new webapi -n Demo.Abp.WebApplication1
或者通过 Visual Studio Community 2022 (64 位)
IDE 工具创建该项目。
ASP.NET Core Web API
项目模板,点击【下一步】。ASP.NET Core Web API
,继续点击【下一步】。经过上面步骤, Demo.Abp.WebApplication1
项目就创建完毕了,项目结构如下:
然后在 Demo.Abp.WebApplication1
项目中添加 nuget
包:
NuGet\Install-Package Volo.Abp.AspNetCore -Version 6.0.0-rc.5
NuGet\Install-Package Volo.Abp.Swashbuckle -Version 6.0.0-rc.5
Program.cs
文件默认代码如下:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run();
接下来我们改造默认的 Program.cs
文件,让其遵循 ABP vNext
模块化(Module
)的编程风格,操作步骤如下:
Program.cs
文件中新建一个 class
类,名称为 DemoWebApiModule
,并继承 AbpModule
。DependsOn
特性来定义模块的依赖关系。Program.cs
文件中通过 AddApplication
注入自定义模块类。InitializeApplication
初始化 http
请求管道(request pipeline
)。Module
)改造Abp 模块化(Module
) 改造后,Program.cs
文件 同步 版本的代码如下:
using Volo.Abp;
using Volo.Abp.AspNetCore;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle;
var builder = WebApplication.CreateBuilder(args);
// 重新注册 Configuration
builder.Services.ReplaceConfiguration(builder.Configuration);
// 添加自定义模块 DemoWebApiModule
builder.Services.AddApplication<DemoWebApiModule>();
/* 同上等效代码
* 添加自定义模块 DemoWebApiModule
builder.Services.AddApplication(options => {
options.Services.ReplaceConfiguration(builder.Configuration); // 重新注册 Configuration
});
*/
var app = builder.Build();
// 初始化 http 请求管道(request pipeline)
app.InitializeApplication();
app.Run();
// 声明 ABP 对应的模块(Module)依赖
[DependsOn(typeof(AbpAspNetCoreModule))]
// 自定义模块类 DemoWebApiModule,并继承自 AbpModule
public class DemoWebApiModule : AbpModule
{
// IoC 注册服务容器
public override void ConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
services.AddControllers();
}
// http 请求管道(request pipeline)初始化
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseConfiguredEndpoints(); // 替换原来的 app.MapControllers();
}
}
说明:请求管道(request pipeline) 由各种业务逻辑对应的 中间件(
middleware
) 组成。
到这里默认的 asp.net core webapi
项目就依据 ABP vNext
模块化(Module
)的风格改造完毕。
我们继续上面的 Abp 模块化(Module
) 【同步版】修改,此处使用【异步】模式构建,并集成 Swagger
模块,改造后代码如下:
using Microsoft.OpenApi.Models;
using Volo.Abp;
using Volo.Abp.AspNetCore;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle;
var builder = WebApplication.CreateBuilder(args);
//builder.Services.ReplaceConfiguration(builder.Configuration);
await builder.Services.AddApplicationAsync<DemoWebApiModule>(options => {
options.Services.ReplaceConfiguration(builder.Configuration);
});
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();
[DependsOn(
typeof(AbpAspNetCoreModule),
typeof(AbpSwashbuckleModule)
)]
public class DemoWebApiModule : AbpModule
{
public override async Task ConfigureServicesAsync(ServiceConfigurationContext context)
{
var services = context.Services;
services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddAbpSwaggerGen(options => {
options.SwaggerDoc("TestAPI", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
});
await Task.CompletedTask;
}
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
//app.UseDeveloperExceptionPage();
app.UseSwaggerUI();
app.UseAbpSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json", "TestAPI");
});
}
else
{
app.UseExceptionHandler("/Error");
}
//app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseConfiguredEndpoints(); //代替原来的 app.MapControllers();
await Task.CompletedTask;
}
}
启动配置文件,你可以在项目中 “Properties”
文件夹中找到 launchSettings.json
文件。该文件是 ASP.NET Core
应用特有的配置标准,用于应用的启动准备工作,包括环境变量,开发端口等。
在 launchSettings.json
文件中进行配置和右键项目—属性中所提交的更改的效果是一样的,并且支持同步更新。此文件设置了 Visual Studio
可以启动的不同环境,以下是示例项目中 launchSettings.json
文件生成的默认配置:
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:48492",
"sslPort": 0
}
},
"profiles": {
"Demo.Abp.WebApplication1": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5220",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
我们先运行下 Demo.Abp.WebApplication1
项目,正常运行后请求默认的 WeatherForecastController
。
浏览器输入 weatherforecast API
地址:
http://localhost:5220/weatherforecast
API 接口访问成功,如下所示:
通过以上步骤我们就完成了对 asp.net core webapi 框架 MiniAPI 模式的 ABP vNext 模块化改造。
由于本人不太喜欢 MiniAPI
模式,因此再继续改造,还原为 .net core 3.1
时代的玩法 —— Program.cs + Startup.cs 模式(此处不知是否有同感的小伙伴呢?)。
此处为了和上面项目形成对照(原有项目保留不变),新建一个 asp.net core webapi
项目,命名为 Demo.Abp.WebApplication2
,项目结构的改造对比如下图所示:
说明:为了演示业务 Service 类中 DI 的注入方式,这里会引入一个 Abp 集成 Autofac 的模块 ——
Volo.Abp.Autofac
。
NuGet\Install-Package Volo.Abp.Autofac -Version 6.0.0-rc.5
这里安装 NuGet
包同上,不再叙述,下面的步骤假定安装了如下 Abp vNext
依赖模块:
Volo.Abp.AspNetCore
Volo.Abp.Swashbuckle
Volo.Abp.Autofac
通过这个改造过程,学会使用 Abp vNext
模块化、规范化的应用在实际项目开发中,并且会使用 Abp vNext
依赖模块( Module
模块化具有传染性)。
如下改造步骤,尽量详细的介绍一下项目结构中的每一个模块,相应的类使用文件夹划分,目录层次更佳清晰明了。
在 MiniAPI
模式中,为了避免新建文件,直接在 Program.cs
文件中新建自定义模块化类,此处为了更好的工程化对该文件单独提取,改造代码如下所示:
using Microsoft.OpenApi.Models;
using Volo.Abp;
using Volo.Abp.AspNetCore;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle;
using Demo.Abp.WebApplication2.Services;
namespace Demo.Abp.WebApplication2.Module;
// 使用 Abp 模块(Module)
[DependsOn(
typeof(AbpAspNetCoreModule),
typeof(AbpAutofacModule),
typeof(AbpSwashbuckleModule)
)]
// 自定义类 Module 化
public class DemoWebApiModule : AbpModule
{
// IoC 注册服务容器
public override async Task ConfigureServicesAsync(ServiceConfigurationContext context)
{
//1、IoC 注册服务类
var services = context.Services;
services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddAbpSwaggerGen(options => {
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
});
// 2、注册具体的业务服务(声明式)
context.Services.AddScoped<IWeatherForecastService, WeatherForecastService>();
await Task.CompletedTask;
}
// Pipeline 管道中间件初始化
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
//app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseAbpSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test DemoWebApiModule API");
});
}
else
{
app.UseExceptionHandler("/Error");
}
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseConfiguredEndpoints(); // 替代 app.MapControllers();
await Task.CompletedTask;
}
}
在 .net core 3.1.x
时代,默认 Program.cs + Startup.cs
模式,而在 Startup.cs
中存在两个方法:
ConfigureServices()
,在该方法中注册依赖关系/服务(无序)。Configure()
,在该方法中(有序)注册中间件(Middleware
),多个中间件组成 http 请求管道(Http request pipeline
)。这里为了上述两个方法命名更佳见名知意,特此修改如下:
ConfigureServices()
修改为 RegisterServicesAsync()
;Configure()
修改为 SetupMiddlewaresAsync()
;说明:这里是改造为异步模式,同步方式去除方法名称的
Async
后缀即可。
新建 Startup.cs
类,添加如下代码:
using Volo.Abp;
using Volo.Abp.Modularity.PlugIns;
using Demo.Abp.WebApplication2.Module;
namespace Demo.Abp.WebApplication2;
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// Add services to the container. 注册服务到 Ioc 容器
public async Task RegisterServicesAsync(IServiceCollection services, IHostBuilder host)
{
Console.WriteLine($"{DateTime.UtcNow},加载 DemoWebApiModule 模块...");
host.UseAutofac(); // 使用 Autofac 第三方 DI
//services.ReplaceConfiguration(Configuration);
// 注册自定义模块 DemoWebApiModule
await services.AddApplicationAsync<DemoWebApiModule>(options => {
// options.UseAutofac();
options.Services.ReplaceConfiguration(Configuration); // 等效同上代码
// 加载插件,固定模式,可热插拔
// options.PlugInSources.AddFolder(@"E:\CodeStuday\dotnet6\DemoAbpPulgins");
});
}
// Configure the HTTP request pipeline. 配置 HTTP 请求管道(中间件管道即中间件委托链)
public async Task SetupMiddlewaresAsync(IApplicationBuilder app, IWebHostEnvironment env)
{
Console.WriteLine($"{env.ApplicationName},{DateTime.UtcNow},启动中间件管道初始化 InitializeApplicationAsync...");
await app.InitializeApplicationAsync();
}
}
思考:当依赖模块变多,需要频繁的调整,这样就会违背开闭原则,那么该如规避该问题呢?
其实 ABP vNext
官网提供了解决方案(PlugIns,插件机制),遵循开闭原则,只需添加 NuGet
包 Volo.Abp.Modularity.PlugIns
,便可实现 Module
模块的插件式加载,只需如下两不操作即可:
DemoAbpPulgins
(文件夹命名尽量见名知意)。AddApplication
或 AddApplicationAsync
的 options
中添加如上代码(注释部分)。Xxx.dll
文件拷贝到新建的根文件夹里面,保存运行即可。以上就实现了 Abp 模块
的 动态加载,规避了 开闭原则 之冲突点。
开闭原则:在面向对象编程中声明 “软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭”;这样的实体可以允许在不修改源代码的情况下对其行为进行扩展。
由于 Startup.cs
文件里面的代码是异步的,所以 Program.cs
文件也需要调整为异步模式(异步具有传染性)。
namespace Demo.Abp.WebApplication2;
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// 此处代码,已经调整到 RegisterServicesAsync 方法,注意观察。
//builder.Services.ReplaceConfiguration(builder.Configuration);
//builder.Host.UseAutofac(); // 直接使用Autofacz
var startup = new Startup(builder.Configuration);
await startup.RegisterServicesAsync(builder.Services, builder.Host);
var app = builder.Build();
await startup.SetupMiddlewaresAsync(app, builder.Environment);
await app.RunAsync();
}
}
这里使用 WeatherForecastService.cs
文件来模拟具体的业务服务,继承自 IWeatherForecastService
接口,结构如上图中 Services
文件夹部分,代码定义如下:
IWeatherForecastService
接口,定义业务方法规范。using Demo.Abp.WebApplication2.Model;
namespace Demo.Abp.WebApplication2.Services;
public interface IWeatherForecastService
{
public IEnumerable<WeatherForecast> GetWeatherForecast();
}
WeatherForecastService.cs
类,继承自 IWeatherForecastService
接口。using Volo.Abp.DependencyInjection; //引入 DI
using Demo.Abp.WebApplication2.Model;
namespace Demo.Abp.WebApplication2.Services;
// Dependency 特性声明 Service 服务生命周期
[Dependency(ServiceLifetime.Scoped, ReplaceServices = true)]
public class WeatherForecastService : IWeatherForecastService
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
// 模拟业务操作,数据库取数据
public IEnumerable<WeatherForecast> GetWeatherForecast()
{
return 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();
}
}
这里使用默认的模型,代码如下:
namespace Demo.Abp.WebApplication2.Model;
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; }
}
此处稍作调整,为了演示业务操作中 DI 调用,把默认代码调整到 WeatherForecastService.cs
类。调整代码如下:
using Microsoft.AspNetCore.Mvc;
using Demo.Abp.WebApplication2.Model;
using Demo.Abp.WebApplication2.Services;
namespace Demo.Abp.WebApplication2.Controllers;
///
/// WebAPI 入口
///
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
// 1、属性 DI 注入
public IWeatherForecastService _IWeatherForecastService { get; set; }
// 2、构造函数 DI 注入
private readonly ILogger<WeatherForecastController> _logger;
///
/// 构造函数
///
///
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> GetWeatherForecast()
{
_logger.LogDebug($"{DateTime.UtcNow}, hello abpvnext ...");
return _IWeatherForecastService.GetWeatherForecast();
}
}
此处就用默认生成的(这里只是为了尽量说明项目文件结构),配置如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:36683",
"sslPort": 0
}
},
"profiles": {
"Demo.Abp.WebApplication2": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5165",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
到这里就基本完成了 Startup.cs
文件的改造,接下来我们启动运行测试看下,是否正常符合预期情况。
启动 Abp
集成 Swagger
模块 —— Volo.Abp.Swashbuckle
符合预期正常显示。
如果要隐藏 ABP vNext
的 默认端点,可以在 Swagger
配置中调用 HideAbpEndpoints
方法,
完整代码如下:
services.AddAbpSwaggerGen(options => {
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
options.HideAbpEndpoints(); // 隐藏 ABP vNext 的默认端点
});
运行项目,注意和上图对比,SwaggerUI
页面如下图所示:
点击【WeatherForecast
】执行业务 Service
的 DI
注册并调用,运行如下所示:
curl
命令访问:curl -X 'GET' \
'http://localhost:5165/WeatherForecast' \
-H 'accept: text/plain' \
-H 'RequestVerificationToken: CfDJ8ODjDXixt1tDuxO1cuT_L2z6DM3lUqtnRF0xKJPwuuLU0SKbwulLhV35ySmCqoC3CuQksJoqHnyzRQw5ZT-sE5Q20mC3vFONoLQ2Tx5Y1Y9qvQ67mtBCqNnSPtCePtukjn1Ocd6ocr-0E2fJVLroRYU' \
-H 'X-Requested-With: XMLHttpRequest'
url
访问:http://localhost:5165/WeatherForecast
在生产环境中,考虑安全性,通常需要 Swagger 接入 OAuth2 认证。
ABP vNext
集成了 Identity Server
来做权限认证,需要使用 AddAbpSwaggerGenWithOAuth
扩展,在 Module
的 ConfigureServices
方法中,使用 OAuth issuer
和 scopes
来配置 Swagger
。
// IoC 注册服务容器
public override async Task ConfigureServicesAsync(ServiceConfigurationContext context)
{
//1、IoC 注册服务类
var services = context.Services;
services.AddControllers();
services.AddEndpointsApiExplorer();
// Swagger 配置权限
services.AddAbpSwaggerGenWithOAuth(
authority: "https://localhost:44341", // authority issuer
scopes: new Dictionary<string, string> { // scopes
{"Test", "Test DemoWebApiModule API"}
},
options => {
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Test DemoWebApiModule API", Version = "v1", Description = "Module 模块化 DemoWebApiModule 自定义类。" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
options.HideAbpEndpoints(); // 隐藏 ABP vNext 的默认端点
});
// 2、注册具体的业务服务(声明式)
context.Services.AddScoped<IWeatherForecastService, WeatherForecastService>();
await Task.CompletedTask;
}
// Pipeline 管道中间件初始化
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
var env = context.GetEnvironment();
if (env.IsDevelopment())
{
//app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseAbpSwaggerUI(options => {
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test DemoWebApiModule API");
var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
options.OAuthClientId("TestSwagger"); // clientId
options.OAuthClientSecret("!QAZ2wsx"); // clientSecret
});
}
else
{
app.UseExceptionHandler("/Error");
}
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseConfiguredEndpoints(); // 替代 app.MapControllers();
await Task.CompletedTask;
}
说明:修改上面的代码配置后,需要提前搭建好
Identity Server
服务并启动运行,再该服务中注册clientId & clientSecret
,然后把authority: "https://localhost:44341"
修改为Identity Server
服务地址。
关于 Identity Server
服务的搭建,这里不是重点,想了解更多,请自行查看官网或相关资料:
修改配置如上代码后,运行项目 Swagger UI
如下图所示:
从 asp.net core webapi
的 MiniAPI
模式开始,遵循 ABP vNext Web
框架的 Module
模块化思想并学会使用,再到 Program.cs & Startup.cs
模式的改造,使得项目工程化体验更佳、目录层级结构更清晰,再逐步引入其他模块(Module
)依赖,并通过 DependsOn
特性显示声明。
遵循 ABP vNext
框架的规则,在代码层面更加直观的体现 Module 模块化开发思想
。当依赖模块多,需要频繁的调整代码模块的依赖, 此时 ABP vNext
框架提供了 PlugIns
插件化机制,实现 ABP vNext
模块 插件式 的动态加载,可热插拔,符合符合 开闭原则
。