文章系列



目錄



安裝模組

目前模組重要的部分都已經開發完成,接下來要想辦法將模組安裝到正式的應用程式上,這個部分也不困難,因為 ABP 背後會自動判斷模組間的依賴關係, 所以並不需要擔心模組的加載順序,所以只需添加專案參考與要手動將所有模組加入到 DependsOn 清單就行了。



手動安裝模組

首先建立一個新應用程式 Acme.UniStore,注意這是個應用程式專案,雖然專案的命名方式都相同,但是跟專注於抽象的模組專案不同, 可以將應用程式專案想像成最終的組裝專案,它主要的工作就是決定各種實做,例如資料庫選擇,以及加載所有關鍵模組組裝成最終應用程式。

abp new Acme.UniStore -dbms PostgreSQL --skip-migrations

這個步驟會將模組加入 Solution Folder 在添加模組過程與後續開發會比較方便。

dotnet sln Acme.UniStore.sln add ../BookStoreScratch/src/BookStoreScratch.Application -s modules/BookStoreScratch/src
dotnet sln Acme.UniStore.sln add ../BookStoreScratch/src/BookStoreScratch.Application.Contracts -s modules/BookStoreScratch/src
dotnet sln Acme.UniStore.sln add ../BookStoreScratch/src/BookStoreScratch.Domain -s modules/BookStoreScratch/src
dotnet sln Acme.UniStore.sln add ../BookStoreScratch/src/BookStoreScratch.Domain.Shared -s modules/BookStoreScratch/src
dotnet sln Acme.UniStore.sln add ../BookStoreScratch/src/BookStoreScratch.EntityFrameworkCore -s modules/BookStoreScratch/src
dotnet sln Acme.UniStore.sln add ../BookStoreScratch/src/BookStoreScratch.HttpApi -s modules/BookStoreScratch/src
dotnet sln Acme.UniStore.sln add ../BookStoreScratch/src/BookStoreScratch.HttpApi.Client -s modules/BookStoreScratch/src

將模組的各個層級依賴添加到對應到應用程式的層級內

dotnet add src/Acme.UniStore.Application/Acme.UniStore.Application.csproj reference ../BookStoreScratch/src/BookStoreScratch.Application/BookStoreScratch.Application.csproj                                                        
dotnet add src/Acme.UniStore.Application.Contracts/Acme.UniStore.Application.Contracts.csproj reference ../BookStoreScratch/src/BookStoreScratch.Application.Contracts/BookStoreScratch.Application.Contracts.csproj                                                        
dotnet add src/Acme.UniStore.Domain/Acme.UniStore.Domain.csproj reference ../BookStoreScratch/src/BookStoreScratch.Domain/BookStoreScratch.Domain.csproj                                                        
dotnet add src/Acme.UniStore.Domain.Shared/Acme.UniStore.Domain.Shared.csproj reference ../BookStoreScratch/src/BookStoreScratch.Domain.Shared/BookStoreScratch.Domain.Shared.csproj                                                        
dotnet add src/Acme.UniStore.EntityFrameworkCore/Acme.UniStore.EntityFrameworkCore.csproj reference ../BookStoreScratch/src/BookStoreScratch.EntityFrameworkCore/BookStoreScratch.EntityFrameworkCore.csproj                                                        
dotnet add src/Acme.UniStore.HttpApi/Acme.UniStore.HttpApi.csproj reference ../BookStoreScratch/src/BookStoreScratch.HttpApi/BookStoreScratch.HttpApi.csproj                                                        
dotnet add src/Acme.UniStore.HttpApi.Client/Acme.UniStore.HttpApi.Client.csproj reference ../BookStoreScratch/src/BookStoreScratch.HttpApi.Client/BookStoreScratch.HttpApi.Client.csproj                                                        

UniStoreApplicationModule 添加模組依賴 typeof(BookStoreScratchApplicationModule)

using BookStoreScratch;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SettingManagement;
using Volo.Abp.Account;
using Volo.Abp.Identity;
using Volo.Abp.AutoMapper;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Modularity;
using Volo.Abp.TenantManagement;

namespace Acme.UniStore;

[DependsOn(
    typeof(UniStoreDomainModule),
    typeof(UniStoreApplicationContractsModule),
    typeof(AbpPermissionManagementApplicationModule),
    typeof(AbpFeatureManagementApplicationModule),
    typeof(AbpIdentityApplicationModule),
    typeof(AbpAccountApplicationModule),
    typeof(AbpTenantManagementApplicationModule),
    typeof(AbpSettingManagementApplicationModule),
    typeof(BookStoreScratchApplicationModule)
    )]
public class UniStoreApplicationModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpAutoMapperOptions>(options =>
        {
            options.AddMaps<UniStoreApplicationModule>();
        });
    }
}

UniStoreApplicationContractsModule 添加模組依賴 typeof(BookStoreScratchApplicationContractsModule)

using BookStoreScratch;
using Volo.Abp.Account;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SettingManagement;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Identity;
using Volo.Abp.TenantManagement;

namespace Acme.UniStore;

[DependsOn(
    typeof(UniStoreDomainSharedModule),
    typeof(AbpFeatureManagementApplicationContractsModule),
    typeof(AbpSettingManagementApplicationContractsModule),
    typeof(AbpIdentityApplicationContractsModule),
    typeof(AbpAccountApplicationContractsModule),
    typeof(AbpTenantManagementApplicationContractsModule),
    typeof(AbpPermissionManagementApplicationContractsModule),
    typeof(BookStoreScratchApplicationContractsModule)
)]
public class UniStoreApplicationContractsModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        UniStoreDtoExtensions.Configure();
    }
}

UniStoreDomainModule 添加模組依賴 typeof(BookStoreScratchDomainModule)

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Acme.UniStore.Localization;
using Acme.UniStore.MultiTenancy;
using BookStoreScratch;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement.Identity;
using Volo.Abp.SettingManagement;
using Volo.Abp.BlobStoring.Database;
using Volo.Abp.Caching;
using Volo.Abp.OpenIddict;
using Volo.Abp.PermissionManagement.OpenIddict;
using Volo.Abp.AuditLogging;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Emailing;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Identity;
using Volo.Abp.TenantManagement;

namespace Acme.UniStore;

[DependsOn(
    typeof(UniStoreDomainSharedModule),
    typeof(AbpAuditLoggingDomainModule),
    typeof(AbpCachingModule),
    typeof(AbpBackgroundJobsDomainModule),
    typeof(AbpFeatureManagementDomainModule),
    typeof(AbpPermissionManagementDomainIdentityModule),
    typeof(AbpPermissionManagementDomainOpenIddictModule),
    typeof(AbpSettingManagementDomainModule),
    typeof(AbpEmailingModule),
    typeof(AbpIdentityDomainModule),
    typeof(AbpOpenIddictDomainModule),
    typeof(AbpTenantManagementDomainModule),
    typeof(BlobStoringDatabaseDomainModule),
    typeof(BookStoreScratchDomainModule)
    )]
public class UniStoreDomainModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpMultiTenancyOptions>(options =>
        {
            options.IsEnabled = MultiTenancyConsts.IsEnabled;
        });


#if DEBUG
        context.Services.Replace(ServiceDescriptor.Singleton<IEmailSender, NullEmailSender>());
#endif
    }
}

UniStoreDomainSharedModule 添加模組依賴 typeof(BookStoreScratchDomainSharedModule)

using Acme.UniStore.Localization;
using BookStoreScratch;
using Volo.Abp.AuditLogging;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Identity;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Validation.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SettingManagement;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp.OpenIddict;
using Volo.Abp.BlobStoring.Database;
using Volo.Abp.TenantManagement;

namespace Acme.UniStore;

[DependsOn(
    typeof(AbpAuditLoggingDomainSharedModule),
    typeof(AbpBackgroundJobsDomainSharedModule),
    typeof(AbpFeatureManagementDomainSharedModule),
    typeof(AbpPermissionManagementDomainSharedModule),
    typeof(AbpSettingManagementDomainSharedModule),
    typeof(AbpIdentityDomainSharedModule),
    typeof(AbpOpenIddictDomainSharedModule),
    typeof(AbpTenantManagementDomainSharedModule),
    typeof(BlobStoringDatabaseDomainSharedModule),
    typeof(BookStoreScratchDomainSharedModule)
    )]
public class UniStoreDomainSharedModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        UniStoreGlobalFeatureConfigurator.Configure();
        UniStoreModuleExtensionConfigurator.Configure();
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            options.FileSets.AddEmbedded<UniStoreDomainSharedModule>();
        });

        Configure<AbpLocalizationOptions>(options =>
        {
            options.Resources
                .Add<UniStoreResource>("en")
                .AddBaseTypes(typeof(AbpValidationResource))
                .AddVirtualJson("/Localization/UniStore");

            options.DefaultResourceType = typeof(UniStoreResource);
            
            options.Languages.Add(new LanguageInfo("en", "en", "English")); 
            options.Languages.Add(new LanguageInfo("ar", "ar", "Arabic")); 
            options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "Chinese (Simplified)")); 
            options.Languages.Add(new LanguageInfo("zh-Hant", "zh-Hant", "Chinese (Traditional)")); 
            options.Languages.Add(new LanguageInfo("cs", "cs", "Czech")); 
            options.Languages.Add(new LanguageInfo("en-GB", "en-GB", "English (United Kingdom)")); 
            options.Languages.Add(new LanguageInfo("fi", "fi", "Finnish")); 
            options.Languages.Add(new LanguageInfo("fr", "fr", "French")); 
            options.Languages.Add(new LanguageInfo("de-DE", "de-DE", "German (Germany)")); 
            options.Languages.Add(new LanguageInfo("hi", "hi", "Hindi ")); 
            options.Languages.Add(new LanguageInfo("hu", "hu", "Hungarian")); 
            options.Languages.Add(new LanguageInfo("is", "is", "Icelandic")); 
            options.Languages.Add(new LanguageInfo("it", "it", "Italian")); 
            options.Languages.Add(new LanguageInfo("pt-BR", "pt-BR", "Portuguese (Brazil)")); 
            options.Languages.Add(new LanguageInfo("ro-RO", "ro-RO", "Romanian (Romania)")); 
            options.Languages.Add(new LanguageInfo("ru", "ru", "Russian")); 
            options.Languages.Add(new LanguageInfo("sk", "sk", "Slovak")); 
            options.Languages.Add(new LanguageInfo("es", "es", "Spanish")); 
            options.Languages.Add(new LanguageInfo("sv", "sv", "Swedish")); 
            options.Languages.Add(new LanguageInfo("tr", "tr", "Turkish")); 

        });
        
        Configure<AbpExceptionLocalizationOptions>(options =>
        {
            options.MapCodeNamespace("UniStore", typeof(UniStoreResource));
        });
    }
}

UniStoreHttpApiModule 添加模組依賴 typeof(BookStoreScratchHttpApiModule)

using Localization.Resources.AbpUi;
using Acme.UniStore.Localization;
using BookStoreScratch;
using Volo.Abp.Account;
using Volo.Abp.SettingManagement;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Identity;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement.HttpApi;
using Volo.Abp.Localization;
using Volo.Abp.TenantManagement;

namespace Acme.UniStore;

 [DependsOn(
    typeof(UniStoreApplicationContractsModule),
    typeof(AbpPermissionManagementHttpApiModule),
    typeof(AbpSettingManagementHttpApiModule),
    typeof(AbpAccountHttpApiModule),
    typeof(AbpIdentityHttpApiModule),
    typeof(AbpTenantManagementHttpApiModule),
    typeof(AbpFeatureManagementHttpApiModule),
    typeof(BookStoreScratchHttpApiModule)
    )]
public class UniStoreHttpApiModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        ConfigureLocalization();
    }

    private void ConfigureLocalization()
    {
        Configure<AbpLocalizationOptions>(options =>
        {
            options.Resources
                .Get<UniStoreResource>()
                .AddBaseTypes(
                    typeof(AbpUiResource)
                );
        });
    }
}

UniStoreHttpApiClientModule 添加模組依賴 typeof(BookStoreScratchHttpApiClientModule)

using BookStoreScratch.HttpApi.Client;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Account;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
using Volo.Abp.SettingManagement;
using Volo.Abp.VirtualFileSystem;
using Volo.Abp.FeatureManagement;
using Volo.Abp.Identity;
using Volo.Abp.TenantManagement;

namespace Acme.UniStore;

[DependsOn(
    typeof(UniStoreApplicationContractsModule),
    typeof(AbpPermissionManagementHttpApiClientModule),
    typeof(AbpFeatureManagementHttpApiClientModule),
    typeof(AbpAccountHttpApiClientModule),
    typeof(AbpIdentityHttpApiClientModule),
    typeof(AbpTenantManagementHttpApiClientModule),
    typeof(AbpSettingManagementHttpApiClientModule),
    typeof(BookStoreScratchHttpApiClientModule)
)]
public class UniStoreHttpApiClientModule : AbpModule
{
    public const string RemoteServiceName = "Default";

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddHttpClientProxies(
            typeof(UniStoreApplicationContractsModule).Assembly,
            RemoteServiceName
        );

        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            options.FileSets.AddEmbedded<UniStoreHttpApiClientModule>();
        });
    }
}

UniStoreEntityFrameworkCoreModule 添加模組依賴 typeof(BookStoreScratchEntityFrameworkCoreModule)

using System;
using BookStoreScratch.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Uow;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.BackgroundJobs.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.PostgreSql;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.OpenIddict.EntityFrameworkCore;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.BlobStoring.Database.EntityFrameworkCore;
using Volo.Abp.TenantManagement.EntityFrameworkCore;
using Volo.Abp.Studio;

namespace Acme.UniStore.EntityFrameworkCore;

[DependsOn(
    typeof(UniStoreDomainModule),
    typeof(AbpPermissionManagementEntityFrameworkCoreModule),
    typeof(AbpSettingManagementEntityFrameworkCoreModule),
    typeof(AbpEntityFrameworkCorePostgreSqlModule),
    typeof(AbpBackgroundJobsEntityFrameworkCoreModule),
    typeof(AbpAuditLoggingEntityFrameworkCoreModule),
    typeof(AbpFeatureManagementEntityFrameworkCoreModule),
    typeof(AbpIdentityEntityFrameworkCoreModule),
    typeof(AbpOpenIddictEntityFrameworkCoreModule),
    typeof(AbpTenantManagementEntityFrameworkCoreModule),
    typeof(BlobStoringDatabaseEntityFrameworkCoreModule),
    typeof(BookStoreScratchEntityFrameworkCoreModule)
    )]
public class UniStoreEntityFrameworkCoreModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        // https://www.npgsql.org/efcore/release-notes/6.0.html#opting-out-of-the-new-timestamp-mapping-logic
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

        UniStoreEfCoreEntityExtensionMappings.Configure();
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext<UniStoreDbContext>(options =>
        {
                /* Remove "includeAllEntities: true" to create
                 * default repositories only for aggregate roots */
            options.AddDefaultRepositories(includeAllEntities: true);
        });

        if (AbpStudioAnalyzeHelper.IsInAnalyzeMode)
        {
            return;
        }

        Configure<AbpDbContextOptions>(options =>
        {
            /* The main point to change your DBMS.
             * See also UniStoreDbContextFactory for EF Core tooling. */

            options.UseNpgsql();

        });
        
    }
}

這裡需要呼叫 ConfigureBookStoreScratch() 方法,將我們模組資料庫的架構整合到最終的 UniStoreDbContext

using BookStoreScratch.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.BackgroundJobs.EntityFrameworkCore;
using Volo.Abp.BlobStoring.Database.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.OpenIddict.EntityFrameworkCore;
using Volo.Abp.TenantManagement;
using Volo.Abp.TenantManagement.EntityFrameworkCore;

namespace Acme.UniStore.EntityFrameworkCore;

[ReplaceDbContext(typeof(IIdentityDbContext))]
[ReplaceDbContext(typeof(ITenantManagementDbContext))]
[ConnectionStringName("Default")]
public class UniStoreDbContext :
    AbpDbContext<UniStoreDbContext>,
    ITenantManagementDbContext,
    IIdentityDbContext
{
    /* Add DbSet properties for your Aggregate Roots / Entities here. */


    #region Entities from the modules

    /* Notice: We only implemented IIdentityProDbContext and ISaasDbContext
     * and replaced them for this DbContext. This allows you to perform JOIN
     * queries for the entities of these modules over the repositories easily. You
     * typically don't need that for other modules. But, if you need, you can
     * implement the DbContext interface of the needed module and use ReplaceDbContext
     * attribute just like IIdentityProDbContext and ISaasDbContext.
     *
     * More info: Replacing a DbContext of a module ensures that the related module
     * uses this DbContext on runtime. Otherwise, it will use its own DbContext class.
     */

    // Identity
    public DbSet<IdentityUser> Users { get; set; }
    public DbSet<IdentityRole> Roles { get; set; }
    public DbSet<IdentityClaimType> ClaimTypes { get; set; }
    public DbSet<OrganizationUnit> OrganizationUnits { get; set; }
    public DbSet<IdentitySecurityLog> SecurityLogs { get; set; }
    public DbSet<IdentityLinkUser> LinkUsers { get; set; }
    public DbSet<IdentityUserDelegation> UserDelegations { get; set; }
    public DbSet<IdentitySession> Sessions { get; set; }

    // Tenant Management
    public DbSet<Tenant> Tenants { get; set; }
    public DbSet<TenantConnectionString> TenantConnectionStrings { get; set; }

    #endregion

    public UniStoreDbContext(DbContextOptions<UniStoreDbContext> options)
        : base(options)
    {

    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        /* Include modules to your migration db context */

        builder.ConfigurePermissionManagement();
        builder.ConfigureSettingManagement();
        builder.ConfigureBackgroundJobs();
        builder.ConfigureAuditLogging();
        builder.ConfigureFeatureManagement();
        builder.ConfigureIdentity();
        builder.ConfigureOpenIddict();
        builder.ConfigureTenantManagement();
        builder.ConfigureBlobStoring();
        builder.ConfigureBookStoreScratch();

        /* Configure your own tables/entities inside here */

        //builder.Entity<YourEntity>(b =>
        //{
        //    b.ToTable(UniStoreConsts.DbTablePrefix + "YourEntities", UniStoreConsts.DbSchema);
        //    b.ConfigureByConvention(); //auto configure for the base class props
        //    //...
        //});
    }
}

接下來設定資料庫連線字串,分別需要修改 DbMigratorWeb 專案的 appsettings.json,這裡的 Web 專案預設是一體式架構, 最終完整的應用就是靠 Web 專案運行。

"ConnectionStrings": {
    "Default": "Host=localhost;Port=5432;Database=UniStore;User ID=postgres;Password=myPassw0rd;"
}

回到 Acme.UniStore.EntityFrameworkCore 專案添加遷移腳本,注意跟我們模組的 EntityFrameworkCore 的概念不完全一樣, 這裡代表的是最終應用的 EntityFrameworkCore 所以在這邊決定使用 PostgreSql 實做與預設安裝 EntityFrameworkCore.Tools

dotnet ef migrations add "Initial"

完成後運行 Acme.UniStore.DbMigrator 建立新的資料並運行 Seeder,此時我們的書店資料表也會加入,並且自動運行之前寫的 Seeder 插入一筆資料。

最後啟動 Acme.UniStore.Web 並查看 Swagger,會發現我們的 Book API 已經正式加入了,並且能讀取到一筆預設資料。

GET https://localhost:44320/api/book

Response body
{
  "totalCount": 1,
  "items": [
    {
      "name": "The Hitchhiker's Guide to the Galaxy",
      "bookType": 7,
      "publishDate": "1979-10-12T00:00:00",
      "price": 42,
      "id": "3a1e8043-d1e8-b17f-1382-9d6dc31b3832"
    }
  ]
}



自動安裝模組

實際手動安裝過一次模組後會發現幾乎都是複製貼上而已,因此有更快的方式就是透過命令安裝 abp install-local-module

要使用這個命令前要先改造一下我們的模組,因為 ABP 模組安裝命令原理是透過兩種類型的檔案 abppkgabpmdl

abppkg: 每個模組層級都要定義一個 abppkg,用來說明此函式庫是什麼層級。 abpmdl: 需要放在方案同一層級中,它是作為藍圖的角色,ABP 命令需要透過它才知道這個模組內部的 abppkg 清單。

例如在 BookStoreScratch.Application 的專案同一層級下定義一個 BookStoreScratch.Application.abppkg 內容為:

{
    "role": "lib.application"
}

這個 role 是由 ABP 自己定義的,當命令看到是這個角色時會將模組的 Application 專案參考添加到應用程式的 Application專案中, 並且在 DependsOn 清單中加上我們的 BookStoreScratchApplicationModule,等於是使用此命令能把我們之前手動操作都處理掉了。

接下來在所有層級都定義 abppkgBookStoreScratch.Application.Contracts 專案,新增 BookStoreScratch.Application.Contracts.abppkg

{
  "role": "lib.application-contracts"
}

BookStoreScratch.Domain 專案,新增 BookStoreScratch.Domain.abppkg

{
  "role": "lib.domain"
}

BookStoreScratch.Domain.Shared 專案,新增 BookStoreScratch.Domain.Shared.abppkg

{
  "role": "lib.domain-shared"
}

BookStoreScratch.EntityFrameworkCore 專案,新增 BookStoreScratch.EntityFrameworkCore.abppkg

{
  "role": "lib.ef"
}

BookStoreScratch.HttpApi 專案,新增 BookStoreScratch.HttpApi.abppkg

{
  "role": "lib.http-api"
}

BookStoreScratch.HttpApi.Client 專案,新增 BookStoreScratch.HttpApi.Client.abppkg

{
  "role": "lib.http-api-client"
}

完成後回到方案根目錄,並新增 BookStoreScratch.abpmdl

{
  "folders": {
    "items": {
      "src": {},
      "test": {}
    }
  },
  "packages": {
    "BookStoreScratch.HttpApi": {
      "path": "src/BookStoreScratch.HttpApi/BookStoreScratch.HttpApi.abppkg",
      "folder": "src"
    },
    "BookStoreScratch.HttpApi.Client": {
      "path": "src/BookStoreScratch.HttpApi.Client/BookStoreScratch.HttpApi.Client.abppkg",
      "folder": "src"
    },
    "BookStoreScratch.Application": {
      "path": "src/BookStoreScratch.Application/BookStoreScratch.Application.abppkg",
      "folder": "src"
    },
    "BookStoreScratch.Application.Contracts": {
      "path": "src/BookStoreScratch.Application.Contracts/BookStoreScratch.Application.Contracts.abppkg",
      "folder": "src"
    },
    "BookStoreScratch.Domain": {
      "path": "src/BookStoreScratch.Domain/BookStoreScratch.Domain.abppkg",
      "folder": "src"
    },
    "BookStoreScratch.Domain.Shared": {
      "path": "src/BookStoreScratch.Domain.Shared/BookStoreScratch.Domain.Shared.abppkg",
      "folder": "src"
    },
    "BookStoreScratch.EntityFrameworkCore": {
      "path": "src/BookStoreScratch.EntityFrameworkCore/BookStoreScratch.EntityFrameworkCore.abppkg",
      "folder": "src"
    }
  }
}

完成後在新增一個新的測試應用程式

abp new Acme.UniStore1 -dbms PostgreSQL --skip-migrations

最後透過 abp install-local-module 讀取 BookStoreScratch.abpmdl 模組安裝到 Acme.UniStore1 底下

abp install-local-module ../BookStoreScratch/BookStoreScratch.abpmdl -t Acme.UniStore1.abpmdl

執行完後會發現所有添加參考與添加 DependsOn 的工作全部都自動處理了,只剩下設定資料庫與連線字串需要手動設定,最後就能運行專案並且結果與第一個應用相同。

今天的進度 Github