手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践

本文主要讲解了如何把ABP官方的在线生成解决方案运行起来,并说明了解决方案中项目间的依赖关系。然后手动实践了如何从0搭建了一个简化的解决方案。ABP官方的在线生成解决方案源码下载参考[3],手动搭建的简化的解决方案源码下载参考[4]。.

一.ABP官方在线生成解决方案

1.将在线生成解决方案跑起来

首先进入页面https://abp.io/get-started,然后创建项目:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践然后头脑中要有一个项目之间的依赖关系图,不清楚的可以参考《基于ABP实现DDD》:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践

截止目前为止,项目使用的.NET版本是6.0,ABP版本是5.3.3。 使用Rider打开项目Acme.BookStore后,会提示使用yarn安装package,安装包后:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践在整个解决方案中搜索ConnectionStrings,发现其在Acme.BookStore.HttpApi.Host、Acme.BookStore.DbMigrator和Acme.BookStore.IdentityServer这3个启动项目中出现:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践将ConnectionStrings中的内容替换为:

"ConnectionStrings": {
    "Default": "Server=127.0.0.1;Database=BookStore;Trusted_Connection=True;User ID=sa;Password=913292836;"
  },

然后开始运行Acme.BookStore.DbMigrator进行数据种子迁移:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践出现上面图片输出结果,基本上表示迁移成功完成了,接下来看看数据库:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践运行Acme.BookStore.Web项目如下:

手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践启动后发现报错了,发现主要是3个问题:一个是Redis没有启动,另一个问题是IDS4服务没有启动,最后一个问题是Acme.BookStore.HttpApi.Host没有启动手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践启动IDS4服务的时候发现报错Volo.Abp.AbpException: Could not find the bundle file '/libs/abp/core/abp.css' for the bundle 'Basic.Global'!

手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践在该项目下执行命令abp install-libs:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践发现消息提示说ABP%20CLI有个更新的5.3.3版本,通过命令dotnet%20tool%20update%20-g%20Volo.Abp.Cli进行升级。再次运行Acme.BookStore.IdentityServer项目,发现不报错误了。如果在启动其它项目(特指Acme.BookStore.Web项目)的时候报同样的错误,那么同样执行命令abp%20install-libs即可解决问题。同时启动这3个项目如下:启动成功后就可以见到熟悉的界面:下面是项目Swagger的界面:至此,已经把从官方下载下来的项目成功地运行起来了。

2.ABP运行流程

下面是在网上[1]找到的一张图,很清晰的说明了AspNet%20Core和ABP模块的运行流程,个人认为图上的Startup.ConfigureServices应该是Startup.Configure,已经在图中做了修改。(1)AspNet%20Core运行流程简单理解,基本上就是在Startup.ConfigureServices中进行依赖注入配置,然后在Startup.Configure中配置管道中间件,访问的时候就像洋葱模型。(2)ABP模块运行流程

  • 在ABP模块中对Startup.ConfigureServices做了扩展,增加了PreConfigureServices和PostConfigureServices。对Startup.Configure也做了扩展,当然名字也修改了,Startup.Configure相当于是OnApplicationInitialization,同时增加了OnPreApplicationInitialization和OnPostApplicationInitialization。
  • 在ABP解决方案中有多个项目,每个项目都会有一个类继承自AbpModule。并且通过DependsOn描述了该模块依赖的模块。这样在ABP解决方案中就会有很多模块之间的依赖关系,通过拓扑排序算法对模块进行排序,从最深层的模块依次加载,直到启动所有模块。

(3)AbpModule抽象类中的方法%20除了主要的PreConfigureServices()、ConfigureServices()、PostConfigureServices()、OnPreApplicationInitialization()、OnApplicationInitialization()、OnPostApplicationInitialization()方法外,还有一些其它的方法。abp\framework\src\Volo.Abp.Core\Volo\Abp\Modularity\AbpModule.cs

二.手动创建解决方案

0.创建解决方案

首先创建一个目录BookStoreHand用于存放解决方案:然后创建一个解决方案,执行命令dotnet%20new%20sln%20-n%20Acme.BookStore:用Rider打开后解决方案是空的,然后手动创建2个New%20Solution%20Folder,分别为src和test:

1.创建领域共享层和领域层

(1)Acme.BookStore.Domain.Shared[领域共享层]
通常定义的常量和枚举,都放在该项目中。通过命令dotnet new classlib -n Acme.BookStore.Domain.Shareddotnet sln ../Acme.BookStore.sln add Acme.BookStore.Domain.Shared创建领域共享层,并将其添加到解决方案当中:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践然后就是创建模块类BookStoreDomainSharedModule如下:

namespace Acme.BookStore.Domain.Shared
{
    public class BookStoreDomainSharedModule: AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            base.ConfigureServices(context);
        }

        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            base.OnApplicationInitialization(context);
        }
    }
}

说明:接下来创建项目、添加项目间的引用等都使用Rider,而不再使用CLI操作,觉得CLI并不方便操作。基本思路顺序都是:创建项目,设置引用关系,创建模块,其它操作。
(2)Acme.BookStore.Domain[领域层]
该项目包含实体、值对象、领域服务、规约、仓储接口等。通过Rider创建Class Library项目Acme.BookStore.Domain如下:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践Acme.BookStore.Domain项目依赖于Acme.BookStore.Domain.Shared项目:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践创建模块类BookStoreDomainSharedModule如下:

[DependsOn(
    typeof(BookStoreDomainSharedModule) //依赖领域共享模块
)]
public class BookStoreDomainModule: AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        base.ConfigureServices(context);
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        base.OnApplicationInitialization(context);
    }
}

创建领域实体Book:

public class Book: Entity<int>
{
    public string BookName { get; set; } //名字
    
    public string Author { get; set; } //作者
    
    public DateTime PublishDate { get; set; } //出版日期
    
    public double Price { get; set; } //价格
}

创建仓储IBookRepository接口:

public interface IBookRepository: IRepository<Book, int>
{
}

2.创建基础设施层

(1)创建项目
基础设施层Acme.BookStore.EntityFrameworkCore是EF Core核心基础依赖项目,包含数据上下文、数据库映射、EF Core仓储实现等。通过Rider创建Class Library项目Acme.BookStore.EntityFrameworkCore如下:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践Acme.BookStore.EntityFrameworkCore项目依赖于Acme.BookStore.Domain项目:(2)创建模块创建模块类BookStoreEntityFrameworkCoreModule如下:

[DependsOn(
%20%20%20%20typeof(BookStoreDomainModule),
%20%20%20%20typeof(AbpEntityFrameworkCoreSqlServerModule)
)]
public%20class%20BookStoreEntityFrameworkCoreModule:%20AbpModule
{
%20%20%20%20public%20override%20void%20ConfigureServices(ServiceConfigurationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20context.Services.AddAbpDbContext<BookStoreDbContext>(options%20=>
%20%20%20%20%20%20%20%20{
%20%20%20%20%20%20%20%20%20%20%20%20//%20给所有的实体都增加默认仓储
%20%20%20%20%20%20%20%20%20%20%20%20options.AddDefaultRepositories(includeAllEntities:%20true);
%20%20%20%20%20%20%20%20});
%20%20%20%20%20%20%20%20
%20%20%20%20%20%20%20%20Configure<AbpDbContextOptions>(options%20=>
%20%20%20%20%20%20%20%20{
%20%20%20%20%20%20%20%20%20%20%20%20options.UseSqlServer();
%20%20%20%20%20%20%20%20});
%20%20%20%20}

%20%20%20%20public%20override%20void%20OnApplicationInitialization(ApplicationInitializationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20base.OnApplicationInitialization(context);
%20%20%20%20}
}

(3)创建数据库上下文创建数据库上下文BookStoreDbContext:

public%20class%20BookStoreDbContext:%20AbpDbContext<BookStoreDbContext>
{
%20%20%20%20public%20BookStoreDbContext(DbContextOptions<BookStoreDbContext>%20options)%20:%20base(options)
%20%20%20%20{
%20%20%20%20}
%20%20%20%20
%20%20%20%20protected%20override%20void%20OnModelCreating(ModelBuilder%20modelBuilder)
%20%20%20%20{
%20%20%20%20%20%20%20%20base.OnModelCreating(modelBuilder);
%20%20%20%20%20%20%20%20
%20%20%20%20%20%20%20%20modelBuilder.ApplyConfigurationsFromAssembly(typeof(BookStoreDbContext).Assembly);
%20%20%20%20}
}

ApplyConfigurationsFromAssembly应用来自IEntityTypeConfiguration中的配置。定义实体映射BookDbMapping如下:

public%20class%20BookDbMapping:%20IEntityTypeConfiguration<Book>
{
%20%20%20%20public%20void%20Configure(EntityTypeBuilder<Book>%20builder)
%20%20%20%20{
%20%20%20%20%20%20%20%20//%20配置主键
%20%20%20%20%20%20%20%20builder.HasKey(b%20=>%20b.Id).HasName("Id");
%20%20%20%20%20%20%20%20
%20%20%20%20%20%20%20%20//%20配置表和字段
%20%20%20%20%20%20%20%20builder.ToTable("AbpBook");
%20%20%20%20%20%20%20%20builder.Property(t%20=>%20t.BookName).IsRequired().HasColumnName("BookName").HasComment("书名");
%20%20%20%20%20%20%20%20builder.Property(t%20=>%20t.Author).IsRequired().HasColumnName("Author").HasComment("作者");
%20%20%20%20%20%20%20%20builder.Property(t%20=>%20t.PublishDate).IsRequired().HasColumnName("PublishDate").HasComment("出版日期");
%20%20%20%20%20%20%20%20builder.Property(t%20=>%20t.Price).IsRequired().HasColumnName("Price").HasComment("价格");
%20%20%20%20%20%20%20%20
%20%20%20%20%20%20%20%20//%20配置关系
%20%20%20%20}
}

(4)创建仓储实现%20定义IBookRepository的实现BookRepository如下:

public%20class%20BookRepository:%20EfCoreRepository<BookStoreDbContext,%20Book,%20int>,%20IBookRepository
{
%20%20%20%20public%20BookRepository(IDbContextProvider<BookStoreDbContext>%20dbContextProvider)%20:%20base(dbContextProvider)
%20%20%20%20{
%20%20%20%20}
}

3.创建应用契约层和应用层

(1)Acme.BookStore.Application.Contracts[应用契约层]包含应用服务接口和数据传输对象。该项⽬被应⽤程序客户端引用,比如Web项目、API客户端项目。通过Rider创建Class%20Library项目Acme.BookStore.Application.Contracts:Acme.BookStore.Application.Contracts项目依赖于Acme.BookStore.Domain.Shared项目如下:创建模块类BookStoreApplicationContractsModule如下:

[DependsOn(
%20%20%20%20typeof(BookStoreDomainSharedModule),%20//依赖于BookStoreDomainSharedModule
%20%20%20%20typeof(AbpObjectExtendingModule)%20//依赖于AbpObjectExtendingModule
)]
public%20class%20BookStoreApplicationContractsModule:%20AbpModule
{
%20%20%20%20public%20override%20void%20ConfigureServices(ServiceConfigurationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20base.ConfigureServices(context);
%20%20%20%20}

%20%20%20%20public%20override%20void%20OnApplicationInitialization(ApplicationInitializationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20base.OnApplicationInitialization(context);
%20%20%20%20}
}

创建服务接口IBookAppService如下:

public%20interface%20IBookAppService:%20IApplicationService
{
%20%20%20%20///%20<summary>
%20%20%20%20///%20获取书籍
%20%20%20%20///%20</summary>
%20%20%20%20Task<BookDto>%20GetBookAsync(int%20id);
}

创建输出DTO为BookDto如下:

public%20class%20BookDto
{
%20%20%20%20public%20int%20Id%20{%20get;%20set;%20}%20//主键
%20%20%20%20
%20%20%20%20public%20string%20BookName%20{%20get;%20set;%20}%20//名字
%20%20%20%20
%20%20%20%20public%20string%20Author%20{%20get;%20set;%20}%20//作者
%20%20%20%20
%20%20%20%20public%20DateTime%20PublishDate%20{%20get;%20set;%20}%20//出版日期
%20%20%20%20
%20%20%20%20public%20double%20Price%20{%20get;%20set;%20}%20//价格
}

(2)Acme.BookStore.Application[应用层]实现在Contracts项目中定义的接⼝。通过Rider创建Class%20Library项目Acme.BookStore.Application如下:Acme.BookStore.Application项目依赖于%20Acme.BookStore.Application.Contracts和Acme.BookStore.Domain项目:创建模块类BookStoreApplicationModule如下:

[DependsOn(
%20%20%20%20typeof(AbpAutoMapperModule),%20//依赖于AutoMapper
%20%20%20%20typeof(BookStoreDomainModule),%20//依赖于BookStoreDomainModule
%20%20%20%20typeof(BookStoreApplicationContractsModule)%20//依赖于BookStoreApplicationContractsModule
)]
public%20class%20BookStoreApplicationModule:%20AbpModule
{
%20%20%20%20public%20override%20void%20ConfigureServices(ServiceConfigurationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20var%20services%20=%20context.Services;
%20%20%20%20%20%20%20%20//%20添加ObjectMapper注入
%20%20%20%20%20%20%20%20services.AddAutoMapperObjectMapper<BookStoreApplicationModule>();
%20%20%20%20%20%20%20%20
%20%20%20%20%20%20%20%20//%20Abp%20AutoMapper设置
%20%20%20%20%20%20%20%20Configure<AbpAutoMapperOptions>(config%20=>
%20%20%20%20%20%20%20%20{
%20%20%20%20%20%20%20%20%20%20%20%20config.AddMaps<BookStoreApplicationAutoMapperProfile>();
%20%20%20%20%20%20%20%20});
%20%20%20%20}

%20%20%20%20public%20override%20void%20OnApplicationInitialization(ApplicationInitializationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20base.OnApplicationInitialization(context);
%20%20%20%20}
}

创建自动映类BookStoreApplicationAutoMapperProfile如下:

public%20class%20BookStoreApplicationAutoMapperProfile:%20Profile
{
%20%20%20%20public%20BookStoreApplicationAutoMapperProfile()
%20%20%20%20{
%20%20%20%20%20%20%20%20CreateMap<BookDto,%20Book>();
%20%20%20%20%20%20%20%20CreateMap<Book,%20BookDto>();
%20%20%20%20}
}

创建IBookAppService类的实现类BookAppService如下:

public%20class%20BookAppService:%20ApplicationService,%20IBookAppService
{
%20%20%20%20private%20readonly%20IBookRepository%20_bookRepository;

%20%20%20%20public%20BookAppService(IBookRepository%20bookRepository)
%20%20%20%20{
%20%20%20%20%20%20%20%20_bookRepository%20=%20bookRepository;
%20%20%20%20}
%20%20%20%20
%20%20%20%20public%20async%20Task<BookDto>%20GetBookAsync(int%20id)
%20%20%20%20{
%20%20%20%20%20%20%20%20var%20queryable%20=%20await%20_bookRepository.GetQueryableAsync();
%20%20%20%20%20%20%20%20var%20book%20=%20queryable.FirstOrDefault(t%20=>%20t.Id%20==%20id);
%20%20%20%20%20%20%20%20if%20(book%20==%20null)
%20%20%20%20%20%20%20%20{
%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20ArgumentNullException(nameof(book));
%20%20%20%20%20%20%20%20}
%20%20%20%20%20%20%20%20return%20ObjectMapper.Map<Book,%20BookDto>(book);
%20%20%20%20}
}

4.创建种子迁移

Acme.BookStore.DbMigrator是控制台应用程序,主要是迁移数据库结构并初始化种子数据。通过Rider创建ASP.NET%20Core%20Web%20Application的Empty项目Acme.BookStore.DbMigrator如下:Acme.BookStore.DbMigrator项目依赖于Acme.BookStore.Application.Contracts和Acme.BookStore.EntityFrameworkCore项目如下:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践创建模块类BookStoreDbMigratorModule如下:

[DependsOn(
    typeof(AbpAutofacModule),
    typeof(BookStoreEntityFrameworkCoreModule),
    typeof(BookStoreApplicationContractsModule)
    )]
public class BookStoreDbMigratorModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        base.ConfigureServices(context);
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        base.OnApplicationInitialization(context);
    }
}

在Program.cs中添加services.AddHostedService()这个数据库迁移主机服务,在DbMigratorHostedService类中包含StartAsync()和StopAsync()这2个方法,在StartAsync()中获取BookStore数据库迁移服务BookStoreDbMigrationService,并执行数据库迁移方法MigrateAsync()。数据库迁移的思路基本上就是在Acme.BookStore.DbMigrator目录下执行命令:dotnet ef migrations add InitialCreate和dotnet ef database update,只不过使用的C#代码来实现的。自己通过Acme.BookStore.DbMigrator项目没有迁移成功,最后还是通过命令行实现迁移的。迁移结果如下:
手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践说明:Program.cs、DbMigratorHostedService.cs和BookStoreDbMigrationService.cs的源码等完整项目源码参考[4]。

5.创建远程服务层

Acme.BookStore.HttpApi[远程服务层],简单理解就是很薄的控制层,该项目主要用于定义HTTP API,即应用服务层的包装器,将它们公开给远程客户端调用。通过Rider创建Class Library项目Acme.BookStore.HttpApi如下:
手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践Acme.BookStore.HttpApi项目依赖于Acme.BookStore.Application.Contracts项目如下:创建模块类BookStoreHttpApiModule如下:

[DependsOn(
%20%20%20%20typeof(BookStoreApplicationContractsModule)%20//依赖于BookStoreApplicationContractsModule
)]
public%20class%20BookStoreHttpApiModule%20:%20AbpModule
{
%20%20%20%20public%20override%20void%20ConfigureServices(ServiceConfigurationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20base.ConfigureServices(context);
%20%20%20%20}

%20%20%20%20public%20override%20void%20OnApplicationInitialization(ApplicationInitializationContext%20context)
%20%20%20%20{
%20%20%20%20%20%20%20%20base.OnApplicationInitialization(context);
%20%20%20%20}
}%20%20

为了简要说明问题,创建一个简单的控制器类BookStoreController如下:

[RemoteService]
[Area("BookStore")]
[Route("api/app/book")]
public%20class%20BookStoreController:%20AbpControllerBase
{
%20%20%20%20private%20readonly%20IBookAppService%20_bookAppService;
%20%20%20%20
%20%20%20%20public%20BookStoreController(IBookAppService%20bookAppService)
%20%20%20%20{
%20%20%20%20%20%20%20%20_bookAppService%20=%20bookAppService;
%20%20%20%20}

%20%20%20%20[HttpGet]
%20%20%20%20[Route("get-book")]
%20%20%20%20public%20Task<BookDto>%20GetBookAsync(int%20id)
%20%20%20%20{
%20%20%20%20%20%20%20%20return%20_bookAppService.GetBookAsync(id);
%20%20%20%20}
}%20%20

6.创建展示层

Acme.BookStore.HttpApi.Host这个是前后端分离时的项目命名方式。通过Rider创建ASP.NET%20Core%20Web%20Application的Empty项目Acme.BookStore.HttpApi.Host如下:Acme.BookStore.HttpApi.Host项目依赖于Acme.BookStore.Application、Acme.BookStore.EntityFrameworkCore和Acme.BookStore.HttpApi项目如下:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践创建模块类BookStoreHttpApiHostModule如下:

[DependsOn(
    typeof(BookStoreHttpApiModule), //依赖于BookStoreHttpApiModule
    typeof(AbpAutofacModule), //依赖于AbpAutofacModule
    typeof(BookStoreApplicationModule), //依赖于BookStoreApplicationModule
    typeof(BookStoreEntityFrameworkCoreModule), //依赖于BookStoreEntityFrameworkCoreModule
    typeof(AbpAspNetCoreSerilogModule), //依赖于AbpAspNetCoreSerilogModule
    typeof(AbpSwashbuckleModule) //依赖于AbpSwashbuckleModule
)]
public class BookStoreHttpApiHostModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var services = context.Services;
        var configuration = services.GetConfiguration();

        ConfigureConventionalControllers();
        ConfigureCors(context, configuration);
        ConfigureSwaggerServices(context, configuration);
    }

    private void ConfigureConventionalControllers()
    {
        Configure<AbpAspNetCoreMvcOptions>(options =>
        {
            options.ConventionalControllers.Create(typeof(BookStoreApplicationModule).Assembly);
        });
    }
            
    private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddCors(options =>
        {
            options.AddPolicy("AllowAll",builder =>
            {
                builder
                    .WithOrigins(
                        configuration["App:CorsOrigins"]
                            .Split(",", StringSplitOptions.RemoveEmptyEntries)
                            .Select(o => o.RemovePostFix("/"))
                            .ToArray()
                    )
                    .WithAbpExposedHeaders()
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });
    }
            
    private static void ConfigureSwaggerServices(ServiceConfigurationContext context, IConfiguration configuration)
    {
        context.Services.AddSwaggerGen(options =>
        {
            options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" });
            options.DocInclusionPredicate((docName, description) => true);
            options.CustomSchemaIds(type => type.FullName);
        });
    }
             
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var env = context.GetEnvironment();
        var app = context.GetApplicationBuilder();
        var configuration = context.GetConfiguration();
        
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseCors("AllowAll");

        if (configuration["UseSwagger"] == "true")
        {
            app.UseSwagger();
            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", "Acme.BookStore API");
            });
        }
        
        app.UseRouting();
        app.UseConfiguredEndpoints();
    }
}

说明:HomeController.cs、Program.cs和配置文件等项目完整源码参考[4]。
将Acme.BookStore.HttpApi.Host项目启动起来后如下:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践通过Swagger界面测试https://localhost:7016/api/app/book/get-book?id=1接口如下:手动从0搭建ABP框架-ABP官方完整解决方案和手动搭建简化解决方案实践

  奇怪的是在线生成解决方案的时候,UI框架选择了MVC,但是还是出现了这个项目,并且在启动Acme.BookStore.Web项目的时候,如果不启动Acme.BookStore.HttpApi.Host项目,还会报错Volo.Abp.AbpException: Remote service 'AbpMvcClient' was not found and there is no default configuration,并且还没有找到Acme.BookStore.Web项目在哪里用到了Acme.BookStore.HttpApi.Host项目。因为自己主要关注前后端分离的项目,所以就不纠结这个细节了。
  在线生成解决方案中还包括:Acme.BookStore.IdentityServer(认证授权项目),Acme.BookStore.HttpApi.Client(远程服务代理层),Acme.BookStore.Web(前后端不分离的展示层),Acme.BookStore.TestBase(其它项目共享或使用的类),Acme.BookStore.Domain.Tests(测试领域层对象),Acme.BookStore.EntityFrameworkCore.Tests(测试自定义仓储实现或EF Core映射),Acme.BookStore.Application.Tests(测试应用层对象),Acme.BookStore.HttpApi.Client.ConsoleTestApp(从.NET控制台中调用HTTP API)等。一篇文章放不下,后面继续解说实践。