构建 Catalog 模块
在这一部分,您将学习如何为目录模块创建实体、服务和基础用户界面。
此模块的功能将保持最简,以便专注于模块化概念。 您可以跟随 书店教程 来学习使用 ABP 构建更贴近实际的应用。
如果 Web 应用程序仍在运行,请在继续教程前停止它。
创建 Product 实体
在您喜欢的 IDE 中打开 ModularCrm.Catalog 模块。您可以右键点击 ModularCrm.Catalog 模块并选择 打开方式 -> Visual Studio Code 命令,用 Visual Studio Code 打开该模块的 .NET 解决方案。如果在 打开方式 列表中找不到您的 IDE,请选择 资源管理器 打开,然后在您的 IDE 中打开 .sln 文件:
ModularCrm.Catalog .NET 解决方案应如下图所示:
在 ModularCrm.Catalog 项目下添加一个新的 Product 类:
using System;
using Volo.Abp.Domain.Entities;
namespace ModularCrm.Catalog;
public class Product : AggregateRoot<Guid>
{
public string Name { get; set; }
public int StockCount { get; set; }
}
请注意,在本教程中,为保持简单,我们直接在项目根文件夹中创建类。您可以根据需要创建子文件夹(命名空间)以实现更精细的代码组织,尤其是对于大型模块。
将实体映射到数据库
下一步是为新实体配置 Entity Framework Core 的 DbContext 类和数据库。
添加 DbSet 属性
在同一项目的 Data 文件夹下打开 CatalogDbContext,并为 Product 实体添加一个新的 DbSet 属性。最终的 CatalogDbContext.cs 文件内容应如下所示:
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace ModularCrm.Catalog.Data;
[ConnectionStringName(CatalogDbProperties.ConnectionStringName)]
public class CatalogDbContext : AbpDbContext<CatalogDbContext>, ICatalogDbContext
{
public DbSet<Product> Products { get; set; } //新增:用于 PRODUCT 实体的 DBSET
public CatalogDbContext(DbContextOptions<CatalogDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigureCatalog();
}
}
CatalogDbContext 类实现了 ICatalogDbContext 接口。向 ICatalogDbContext 接口添加以下属性:
DbSet<Product> Products { get; set; }
最终的 ICatalogDbContext 接口应如下所示:
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace ModularCrm.Catalog.Data;
[ConnectionStringName(CatalogDbProperties.ConnectionStringName)]
public interface ICatalogDbContext : IEfCoreDbContext
{
DbSet<Product> Products { get; set; }
}
拥有这样一个 ICatalogDbContext 接口,可以使我们的仓储(及其他类)与具体的 CatalogDbContext 类解耦。这为最终应用程序提供了灵活性,可以将多个 DbContext 合并为一个,以便更轻松地管理数据库迁移,并为多模块数据库操作提供数据库级别的事务支持。我们将在本教程稍后部分实现这一点。
配置表映射
标准模块 模板设计灵活,使得您的模块可以拥有独立的物理数据库,也可以将其表存储在另一个数据库内(通常是应用程序的主数据库)。为了实现这一点,它在上述 OnModelCreating 方法内调用的扩展方法(ConfigureCatalog())中配置数据库映射。找到该扩展方法(位于 CatalogDbContextModelCreatingExtensions 类中),并将其内容更改为以下代码块:
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace ModularCrm.Catalog.Data;
public static class CatalogDbContextModelCreatingExtensions
{
public static void ConfigureCatalog(
this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<Product>(b =>
{
//配置表名和架构名
b.ToTable(CatalogDbProperties.DbTablePrefix + "Products",
CatalogDbProperties.DbSchema);
//始终调用此方法以设置基础实体属性
b.ConfigureByConvention();
//实体的属性
b.Property(q => q.Name).IsRequired().HasMaxLength(100);
});
}
}
首先,使用 ToTable 方法设置数据库表名。CatalogDbProperties.DbTablePrefix 定义了一个常量,作为此前缀添加到该模块所有数据库表名中。如果您查看 CatalogDbProperties 类,DbTablePrefix 的值为 Catalog。这样,Product 实体的表名将是 CatalogProducts。如果您对默认的表前缀不满意,可以修改 CatalogDbProperties 类,或者为您的表设置架构。
此时,在您的 IDE 中(或在 ABP Studio UI 上)构建 ModularCrm.Catalog .NET 解决方案。然后,切换到主应用程序的 .NET 解决方案。
配置主应用程序数据库
您更改了 Entity Framework Core 的配置。下一步应该是添加一个新的 Code-First 数据库迁移并更新数据库,以便在数据库上创建新的 CatalogProducts 表。
您不是在模块中管理数据库迁移。相反,由主应用程序决定使用哪种 DBMS(数据库管理系统)以及如何在模块之间共享物理数据库。在本教程中,我们将把所有模块的数据存储在同一个物理数据库中。
在您的 IDE 中打开 ModularCrm 模块(即主应用程序):
打开 ModularCrm 项目 Data 文件夹下的 ModularCrmDbContext 类:
您将把模块的数据库配置合并到 ModularCrmDbContext 中。
替换 ICatalogDbContext 服务
按照以下三个步骤操作:
(1) 在 ModularCrmDbContext 类的顶部添加以下特性:
[ReplaceDbContext(typeof(ICatalogDbContext))]
ReplaceDbContext 特性使得可以在 Catalog 模块的服务中使用 ModularCrmDbContext 类。
(2) 让 ModularCrmDbContext 类实现 ICatalogDbContext 接口:
[ReplaceDbContext(typeof(ICatalogDbContext))]
public class ModularCrmDbContext :
AbpDbContext<ModularCrmDbContext>,
ICatalogDbContext //新增:实现接口
{
public DbSet<Product> Products { get; set; } //新增:添加 DBSET 属性
...
}
(3) 最后,确保在 OnModelCreating 方法内部调用了 ConfigureCatalog() 扩展方法(由于您在创建初始解决方案的 模块化 步骤中选择了 设置为模块化解决方案 选项,此项应该已经完成)。
通过这种方式,ModularCrmDbContext 可以通过 ICatalogDbContext 接口供目录模块使用。此部分对于每个模块只需要操作一次。下次,您可以直接添加新的数据库迁移,如下一部分所述。
添加数据库迁移
您可以使用 Entity Framework Core 的 Add-Migration(或 dotnet ef migrations add)终端命令,但在本教程中我们将使用 ABP Studio 的快捷 UI。
确保解决方案已构建完成。您可以在 ABP Studio 解决方案资源管理器 中右键点击 ModularCrm(位于 main 文件夹下)并选择 Dotnet CLI -> Graph Build 命令。
右键点击 ModularCrm 包并选择 EF Core CLI -> Add Migration 命令:
Add Migration 命令会打开一个新对话框来获取迁移名称:
点击 OK 按钮后,一个新的数据库迁移类将被添加到 ModularCrm 项目的 Migrations 文件夹中:
现在,您可以返回 ABP Studio,再次右键点击 ModularCrm 包并选择 EF Core CLI -> Update Database 命令:
操作完成后,您可以检查数据库,确认新的 CatalogProducts 表已经创建:
创建应用服务
现在,您可以创建一个 应用服务 来执行与产品相关的一些用例。
定义应用服务契约
返回您的 IDE(例如 Visual Studio),打开 ModularCrm.Catalog 模块的 .NET 解决方案,并在 ModularCrm.Catalog.Contracts 项目下创建一个 IProductAppService 接口:
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace ModularCrm.Catalog;
public interface IProductAppService : IApplicationService
{
Task<List<ProductDto>> GetListAsync();
Task CreateAsync(ProductCreationDto input);
}
我们在 ModularCrm.Catalog.Contracts 项目中定义应用服务接口和 数据传输对象。这样,您就可以与客户端共享这些契约,而无需共享实际的实现类。
定义数据传输对象
GetListAsync 和 CreateAsync 方法使用了尚未定义的 ProductDto 和 ProductCreationDto 类。因此,您需要定义它们。
在 ModularCrm.Catalog.Contracts 项目下创建一个 ProductCreationDto 类:
using System.ComponentModel.DataAnnotations;
namespace ModularCrm.Catalog;
public class ProductCreationDto
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Range(0, int.MaxValue)]
public int StockCount { get; set; }
}
并在 ModularCrm.Catalog.Contracts 项目下创建一个 ProductDto 类:
using System;
namespace ModularCrm.Catalog;
public class ProductDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public int StockCount { get; set; }
}
ModularCrm.Catalog.Contracts 项目下的新文件如下图所示:
实现应用服务
现在,您可以实现 IProductAppService 接口。在 ModularCrm.Catalog 项目下创建一个 ProductAppService 类:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace ModularCrm.Catalog;
public class ProductAppService : CatalogAppService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<List<ProductDto>> GetListAsync()
{
var products = await _productRepository.GetListAsync();
return ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
}
public async Task CreateAsync(ProductCreationDto input)
{
var product = new Product
{
Name = input.Name,
StockCount = input.StockCount
};
await _productRepository.InsertAsync(product);
}
}
请注意,ProductAppService 类实现了 IProductAppService 接口,并且也继承自 CatalogAppService 类。CatalogAppService 是一个基类,它为 本地化 和 对象映射 做了一些配置(您可以在同一个 ModularCrm.Catalog 项目中看到)。您可以让所有应用服务都继承自该基类。这样,您可以定义一些通用属性和方法在所有应用服务之间共享。如果您觉得以后可能会混淆,可以重命名这个基类。
对象映射
ProductAppService.GetListAsync 方法使用 ObjectMapper 服务将 Product 实体转换为 ProductDto 对象。需要配置此映射。因此,在 ModularCrm.Catalog 项目中创建一个新的映射类,该类使用 [Mapper] 特性并继承 MapperBase<Product, ProductDto> 类,如下所示:
[Mapper]
public partial class ProductToProductDtoMapper : MapperBase<Product, ProductDto>
{
public override partial ProductDto Map(Product source);
public override partial void Map(Product source, ProductDto destination);
}
将应用服务公开为 HTTP API 控制器
此应用程序不需要将任何功能公开为 HTTP API,因为所有模块集成和通信都将在同一进程中完成,这是单体模块化应用程序的自然特性。然而,在本节中,我们将创建 HTTP API,原因如下:
- 我们将在开发中使用这些 HTTP API 端点来创建一些示例数据。
- 让您了解在需要时如何操作。
因此,请遵循本节中的说明,将产品应用服务公开为 HTTP API 端点。
要为目录模块创建 HTTP API 端点,您有两个选择:
- 您可以在
ModularCrm.Catalog项目中创建一个常规的 ASP.NET Core 控制器类,注入IProductAppService,并为产品应用服务的每个公共方法创建包装方法。您将在稍后创建 Ordering 模块时进行此操作。(您也可以查看ModularCrm.Catalog项目 Samples 文件夹下的SampleController类作为示例) - 或者,您可以使用 ABP 的 自动 API 控制器 功能,通过约定将您的应用服务公开为 API 控制器。
我们将在这里使用第二种方法。在您的 IDE 中打开 Catalog 模块 .NET 解决方案(即 ModularCrm.Catalog .NET 解决方案中的 ModularCrm.Catalog .NET 项目)中的 CatalogModule 类,找到 ConfigureServices 方法,并将以下代码块添加到该方法中:
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(CatalogModule).Assembly, settings =>
{
settings.RootPath = "catalog";
});
});
这将告知 ABP 框架为 ModularCrm.Catalog 程序集中的应用服务创建 API 控制器。
现在,ABP 将自动把 ModularCrm.Catalog 项目中定义的应用服务公开为 API 控制器。下一节将使用这些 API 控制器来创建一些示例产品。
创建示例产品
本节将使用 Swagger UI 创建一些示例产品。这样,您将拥有一些示例产品在 UI 上显示。
打开 解决方案运行器 面板,点击解决方案根目录附近的 播放 按钮。一旦 ModularCrm 应用程序运行,您可以右键点击它并选择 浏览 命令以打开用户界面。
看到 Web 应用程序的用户界面后,在 URL 末尾输入 /swagger 以打开 Swagger UI。向下滚动,您应该能看到 Catalog API:
如果您看不到 Product API,可能需要重新构建整个解决方案。在 ABP Studio 解决方案资源管理器面板中右键点击
main文件夹下的ModularCrm并选择 Dotnet CLI -> Graph Build 命令。这将确保目录模块和主应用程序完全构建。
展开 POST /api/catalog/product API 并点击 Try it out 按钮,如下图所示:
然后,通过填写 Request body 并点击 Execute 按钮来创建一些产品:
如果您检查数据库,应该能在 Products 表中看到已创建的实体:
数据库中有了一些实体,现在您可以在用户界面上显示它们了。
创建用户界面
在本节中,您将创建一个非常简单的用户界面,以演示如何在目录模块中构建 UI 并使其在主应用程序中工作。
作为第一步,如果应用程序当前正在运行,您可以在 ABP Studio 的解决方案运行器中停止它。
创建产品页面
在您的 IDE 中打开 ModularCrm.Catalog .NET 解决方案,找到 ModularCrm.Catalog.UI 项目下的 Pages/Catalog/Index.cshtml 文件:
将 Index.cshtml.cs 文件内容替换为以下内容:
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ModularCrm.Catalog.UI.Pages.Catalog;
public class IndexModel : CatalogPageModel
{
public List<ProductDto> Products { get; set; }
private readonly IProductAppService _productAppService;
public IndexModel(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task OnGetAsync()
{
Products = await _productAppService.GetListAsync();
}
}
在这里,您简单地使用 IProductAppService 来获取所有产品的列表,并将结果赋值给 Products 属性。您可以在 Index.cshtml 文件中使用它,在 UI 上显示一个简单的产品列表:
@page
@using Microsoft.Extensions.Localization
@using ModularCrm.Catalog.Localization
@model ModularCrm.Catalog.UI.Pages.Catalog.IndexModel
@inject IStringLocalizer<CatalogResource> L
<h1>Products</h1>
<abp-card>
<abp-card-body>
<abp-list-group>
@foreach (var product in Model.Products)
{
<abp-list-group-item>
@product.Name <span class="text-muted">(库存: @product.StockCount)</span>
</abp-list-group-item>
}
</abp-list-group>
</abp-card-body>
</abp-card>
在 ABP Studio 的解决方案运行器中右键点击 ModularCrm 应用程序,并选择 启动 命令:
现在,您可以浏览 Catalog 页面以查看产品列表:
如您所见,在模块化的 ABP 应用程序中开发 UI 页面非常直观。我们保持了 UI 的极简以专注于模块化概念。要学习如何构建复杂的应用程序 UI,请查阅 书店教程。
总结
在本教程的这一部分,您在 上一部分 创建的 Catalog 模块内构建了功能。在下一部分,您将创建一个新的 Ordering 模块并将其安装到主应用程序中。
抠丁客



















