ABP Framework-从零搭建
目录
Version
7.4.1
创建空Web项目
dotnet new sln
dotnew new web --name AspNetCoreAbp
dotnet sln add .\AspNetCoreAbp
增加Abp的基础包
Volo.Abp.AspNetCore.Mvc
增加启动模块
考虑是使用WebApi模式,如下代码中就尽可能的精简,去除Mvc部分,Abp没有纯WebApi依赖模块,只有AbpAspNetCoreMvcModule模块。
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
namespace AspNetCoreAbp;
[DependsOn(typeof(AbpAspNetCoreMvcModule))]
public class AspNetCoreAbpModule : AbpModule
{
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();
        app.UseRouting();
        app.UseConfiguredEndpoints();
    }
}
更改Program.cs
在7.0以上,ABP不用再单独弄一个Startup.cs文件,直接在Program.cs中启动模块就行。
默认代码
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
变更后代码
var builder = WebApplication.CreateBuilder(args);
builder.AddApplicationAsync<AppModule>(); 
var app = builder.Build();
app.InitializeApplicationAsync();
app.Run();
就第2行和第5行代码有所改动,也正对应着原Startup中的两行代码。
// adds all services defined in all modules starting from the AppModule.
builder.AddApplicationAsync<AppModule>(); 
//initializes and starts the application.
app.InitializeApplicationAsync();
集成Autofac
AspNetCore自身的DI系统已经足够使用了,但Autofac提供了更多功能,像属性注入,方法注入等,ABP需要依靠这些完成更丰富的功能。
增加Autofac的VoloAbp的包
Volo.Abp.Autofac
启动模块依赖AutofacModule
[DependsOn(
    typeof(AbpAspNetCoreMvcModule),
    typeof(AbpAutofacModule)
    )]
public class AspNetCoreAbpModule : AbpModule
{
  ...
}
Program.cs中替换DI容器,只需要使用UseAutofac注册到builder中,及其简单。
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseAutofac();
await builder.AddApplicationAsync<AspNetCoreAbpModule>();
集成Swagger
当前项目中不具备Swagger的展示,启动项目看不到接口文档,集成Swagger进入到项目中。
https://docs.abp.io/en/abp/latest/API/Swagger-Integration
增加Swagger的VoloAbp的包
Volo.Abp.Swashbuckle
启动模块依赖SwaggerModule
[DependsOn(
    typeof(AbpAspNetCoreMvcModule),
    typeof(AbpAutofacModule),
    typeof(AbpSwashbuckleModule)
    )]
public class AspNetCoreAbpModule : AbpModule
{
  ...
}
Swagger服务注册,在启动模块中增加ConfigurationServices,其中注册SwaggerGen
public override void ConfigureServices(ServiceConfigurationContext context)
{
    var services = context.Services;
    services.AddAbpSwaggerGen(options =>
    {
        options.SwaggerDoc("v1", new OpenApiInfo { Title = "AspNetCoreAbp API", Version = "v1" });
        options.DocInclusionPredicate((docName, description) => true);
        options.CustomSchemaIds(type => type.FullName);
    });
}
在中间件中增加SwaggerUI的中间件,同时需要UseStaticFiles中间件及UseSwagger中间件。
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    var app = context.GetApplicationBuilder();
    var env = context.GetEnvironment();
    app.UseStaticFiles();
    app.UseSwagger();
    app.UseAbpSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "AspNetCoreAbp API");
    });
    app.UseRouting();
    app.UseConfiguredEndpoints();
}
启动后是没有接口的,需要先增加一个Controller测试下。此处父类使用ControllerBase或是AbpControllerBase都行,无影响。
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreAbp.Controllers;
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
    [HttpGet]
    public int Get()
    {
        return 0;
    }
}
启动后可以看到接口信息
 如需要屏蔽Abp自身接口,可以关闭即可
如需要屏蔽Abp自身接口,可以关闭即可
https://docs.abp.io/en/abp/latest/API/Swagger-Integration#hide-abp-endpoints-on-swagger-ui
如需要集成OAuth到Swagger中,以跳转到Auth拿Token可以按照如下设置
https://docs.abp.io/en/abp/latest/API/Swagger-Integration#using-swagger-with-oidc
https://docs.abp.io/en/abp/latest/API/Swagger-Integration#using-swagger-with-oidc
如需要带着Token请求接口,可以做些改动在options上增加AddSecurityDefinition和AddSecurityRequirement.
https://dev.to/eduardstefanescu/aspnet-core-swagger-documentation-with-bearer-authentication-40l6
services.AddAbpSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "AspNetCoreAbp API", Version = "v1" });
    options.DocInclusionPredicate((docName, description) => true);
    options.CustomSchemaIds(type => type.FullName);
    // 使用Bearer token,如下默认格式就行,无需改动参数
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
    {
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 1safsfsdfdfd\"",
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme 
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] {}
        }
    });
});
再次启动便可以输入Token,请求接口了。

注意
- 在SwaggerGen中的配置,是对于展示部分的配置

- 在中间件的配置,是下拉展示的配置
 右上角对应选择项,也就使用对应的swagger.json文件
右上角对应选择项,也就使用对应的swagger.json文件
 两者命名可以不同,但是也建议设置成相同。
两者命名可以不同,但是也建议设置成相同。
集成Authentication
鉴权部分考虑集成JwtBearer,在Abp中,鉴权部分并没有文档描述,因为使用的就是AspNetCore自身的鉴权体系,只是封装了几个包
 此处只关注JwtBearer的包,内部封装了(Microsoft.AspNetCore.Authentication.JwtBearer),来方便同步版本。
此处只关注JwtBearer的包,内部封装了(Microsoft.AspNetCore.Authentication.JwtBearer),来方便同步版本。
增加Authentication的VoloAbp包
Volo.Abp.Authentication
启动模块依赖JwtBearerModule
[DependsOn(
    typeof(AbpAspNetCoreMvcModule),
    typeof(AbpAutofacModule),
    typeof(AbpSwashbuckleModule),
    typeof(AbpAspNetCoreAuthenticationJwtBearerModule)
    )]
public class AspNetCoreAbpModule : AbpModule
{
  ...
}
其余配置和在AspNetCore下鉴权服务配置相同。可参照如下链接设置。 https://sandrino.dev/blog/aspnet-core-5-jwt-authorization
服务注册中增加Authentication和JwtBearer,此处只简单添加不做具体参数设置。
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateLifetime = false,
            ValidateIssuerSigningKey = false,
            SignatureValidator = (string token, TokenValidationParameters parameters) => new JwtSecurityToken(token)
        };
    });
在中间件中使用鉴权授权中间件,如下第14,15行,注意需要在UseRouting之后。
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    var app = context.GetApplicationBuilder();
    var env = context.GetEnvironment();
    app.UseStaticFiles();
    app.UseSwagger();
    app.UseAbpSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "AspNetCoreAbp API");
    });
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseConfiguredEndpoints();
}
集成Serilog
增加Serilog的VoloAbp包
Volo.Abp.AspNetCore.Serilog
这个包中依赖了Serilog,所以我们无需在额外去装Serilog包了。https://github.com/serilog/serilog/wiki/Getting-Started
启动模块依赖SerilogModule
[DependsOn(
    typeof(AbpAspNetCoreMvcModule),
    typeof(AbpAutofacModule),
    typeof(AbpSwashbuckleModule),
    typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
    typeof(AbpAspNetCoreSerilogModule)
    )]
public class AspNetCoreAbpModule : AbpModule
{
  ...
}
中间件注册中,可以设置AbpSerilogEnrichers,如下第14行代码,能够输出当前用户,租户,client等额外信息。此为可选项,不是必备的。
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    var app = context.GetApplicationBuilder();
    var env = context.GetEnvironment();
    app.UseStaticFiles();
    app.UseSwagger();
    app.UseAbpSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "AspNetCoreAbp API");
    });
    app.UseRouting();
    app.UseAbpSerilogEnrichers();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseConfiguredEndpoints();
}
更改Program.cs
需要在启动时候替换Logger,用上Serilog的。
额外安装Serilog两个包
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
原先的Program.cs内容如下
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseAutofac();
await builder.AddApplicationAsync<AspNetCoreAbpModule>();
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();
在此基础上封装成一个方法,并增加机密文件及Serilog的替换,其余保持不变。
private static async Task Init(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    builder.Host.AddAppSettingsSecretsJson();
    builder.Host.UseAutofac();
    builder.Host.UseSerilog();
    await builder.AddApplicationAsync<AspNetCoreAbpModule>();
    var app = builder.Build();
    await app.InitializeApplicationAsync();
    await app.RunAsync();
}
并在此基础上在封装一个类,用来承载启动日志,Main方法中为默认格式,如果要搭建新项目,直接使用默认格式即可,注意日志文件写入后,需要将日志文件加入到gitignore中。
public class Program
{
    public async static Task<int> Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
#if DEBUG
            .MinimumLevel.Debug()
#else
            .MinimumLevel.Information()
#endif
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
            .Enrich.FromLogContext()
            .WriteTo.Async(c => c.File("Logs/logs.txt"))
            .WriteTo.Async(c => c.Console())
            .CreateLogger();
        try
        {
            Log.Information("Starting AspNetCoreAbp.");
            await Init(args);
            return 0;
        }
        catch (Exception ex)
        {
            if (ex is HostAbortedException)
            {
                throw;
            }
            Log.Fatal(ex, "Host terminated unexpectedly!");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
    private static async Task Init(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        builder.Host.AddAppSettingsSecretsJson();
        builder.Host.UseAutofac();
        builder.Host.UseSerilog();
        await builder.AddApplicationAsync<AspNetCoreAbpModule>();
        var app = builder.Build();
        await app.InitializeApplicationAsync();
        await app.RunAsync();
    }
}
设置Serilog从配置文件中读取日志配置
可以设置builder.Host.UseSerilog(),增加参数让Serilog读取appsetings.json中的配置,当然默认的Logging配置可以删掉了,因为Serilog不会用到那段配置设置,而是走自己的配置设置。
https://github.com/serilog/serilog-settings-configuration
设置Serilog输出渠道
https://github.com/serilog/serilog/wiki/Configuration-Basics
serilog能够将日志信息输出到很多个渠道,Serilog.AspNetCore包中已经集成了几个渠道,因此选择渠道时候可以先看下已有包。

配置AspNetCoreMvc
全局AspNetCoreMvc设置,基本不用设置,也可以用来自动生成Controller,如下设置Application模块生成Controller,并设置路由前缀
Configure<AbpAspNetCoreMvcOptions>(options =>
{
    options.ConventionalControllers.Create(typeof(OrderingManagementApplicationModule).Assembly, opts =>
    {
        opts.RootPath = "OrderingManagement1";
    });
});
生成后,Swagger中会带上这节路由地址。

配置Mvc
Mvc中间件相关的设置,基本不用设置
Configure<MvcOptions>(options =>
{
    var filterMetadata = options.Filters.FirstOrDefault(x => x is ServiceFilterAttribute attribute && attribute.ServiceType.Equals(typeof(AbpExceptionFilter)));
    options.Filters.Remove(filterMetadata);
    options.Filters.Add(typeof(FixtExceptionFilter));
    options.Filters.Add(new Infrastructure.FixtAuthorizeFilter());
});
配置EFCore&DbContext
增加EFCore的VoloAbp包
选择底层的存储介质包
 此处选择Mysql的包,第一个包在各存储介质包中都有依赖,因此不用再引用第一个基础包。
此处选择Mysql的包,第一个包在各存储介质包中都有依赖,因此不用再引用第一个基础包。
Volo.Abp.EntityFrameworkCore.MySQL
增加Domain文件夹或类库
此处增加Domain文件夹,新增Order实体。
using Volo.Abp.Domain.Entities;
namespace AspNetCoreAbp.Domains.Models;
public class Order : BasicAggregateRoot<Guid>
{
    public string Name { get; set; } = default!;
}
增加EfCore文件夹或类库
此处增加EFCore文件夹,新增DbContext,配置Order的模型。
using AspNetCoreAbp.Domains.Models;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace AspNetCoreAbp.EntityFrameworkCores;
public class AppDbContext : AbpDbContext<AppDbContext>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }
    public DbSet<Order> Orders { get; set; }
    protected override void OnModelCreating(ModelBuilder builder)
    {
        //Always call the base method
        base.OnModelCreating(builder);
        builder.Entity<Order>(o =>
        {
            o.ToTable("Orders");
            o.ConfigureByConvention(); //auto configure for the base class props
            o.Property(x => x.Name).IsRequired().HasMaxLength(128);
        });
    }
}
启动模块依赖Module
[DependsOn(
    typeof(AbpAspNetCoreMvcModule),
    typeof(AbpAutofacModule),
    typeof(AbpSwashbuckleModule),
    typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
    typeof(AbpAspNetCoreSerilogModule),
    typeof(AbpEntityFrameworkCoreMySQLModule)
    )]
public class AspNetCoreAbpModule : AbpModule
{
 ...
}
服务注册中增加DbContext的注册,同时使用默认仓储服务。
public override void ConfigureServices(ServiceConfigurationContext context)
{
  ...
  services.AddAbpDbContext<AppDbContext>(options =>
  {
      options.AddDefaultRepositories();
  });
  ...
}
如果是使用的类库,那么一般是在类库中有一个module,注册写在module中,Host层依赖类库中的module。
[DependsOn(typeof(AbpEntityFrameworkCoreModule))]
public class AspNetCoreEntityFrameworkCoreModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext<AppDbContext>(options =>
        {
            options.AddDefaultRepositories();
        });
    }
}
服务注册中指明存储介质,默认使用的是ConnectionStrings:Default,此处不再做额外配置,一切从简,从默认。
public override void ConfigureServices(ServiceConfigurationContext context)
{
  ...
  Configure<AbpDbContextOptions>(options =>
  {
      options.UseMySQL();
  });
  ...
}
配置文件中增加连接字符串
{
  "ConnectionStrings": {
    "Default": "Server=ip;Database=dbname;Port=3306;charset=utf8;uid=root;pwd=xxx;"
  }
}
安装efcore tool和design包
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.14">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.14">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
ef命令生成脚本
add-migration xxx
update-database
新建OrderController测试存储
using AspNetCoreAbp.Domains.Models;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Domain.Repositories;
namespace AspNetCoreAbp.Controllers;
[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly IRepository<Order,Guid> _orderRepository;
    public OrderController(IRepository<Order, Guid> orderRepository)
    {
        _orderRepository = orderRepository;
    }
    [HttpGet]
    public async Task<List<Order>> GetListAsync()
    {
        return await _orderRepository.GetListAsync();
    }
    [HttpPost]
    public async Task<Order> CreateAsync(string name)
    {
        return await _orderRepository.InsertAsync(new Order()
        {
            Name = name
        });
    }
}
2024-01-15,望技术有成后能回来看见自己的脚步。
