构建订单模块
在上一部分,您创建了订单模块并将其安装到主应用程序中。然而,订单模块目前还没有任何功能。在本部分中,您将创建一个 Order 实体,并添加创建和列出订单的功能。
创建 Order 实体
在您的 IDE 中打开 ModularCrm.Ordering .NET 解决方案。
提示:您可以通过在 ABP Studio 中右键单击相关模块并选择 打开方式 -> 资源管理器 命令来打开模块的 .NET 解决方案文件夹。
添加 Order 类
在 ModularCrm.Ordering 项目中创建一个 Order 类:
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace ModularCrm.Ordering;
public class Order : CreationAuditedAggregateRoot<Guid>
{
public Guid ProductId { get; set; }
public string CustomerName { get; set; } = null!;
public OrderState State { get; set; }
}
我们允许用户在一个订单中只放置单个产品。在真实的应用中,Order 实体会复杂得多。然而,Order 实体的复杂性并不影响模块化设计。因此,在本教程中我们保持简单,以专注于模块化。我们继承自 CreationAuditedAggregateRoot 类,因为我希望知道订单何时创建以及由谁创建。
添加 OrderState 枚举
我们使用了一个尚未定义的 OrderState 枚举。在 ModularCrm.Ordering.Contracts 项目内创建一个 OrderState.cs 文件,并定义以下枚举:
namespace ModularCrm.Ordering;
public enum OrderState : byte
{
Placed = 0,
Delivered = 1,
Canceled = 2
}
订单模块的最终结构在您的 IDE 中应类似于下图:
配置数据库映射
Order 实体已经创建。现在,您需要为该实体配置数据库映射。您将首先定义数据库表映射,创建数据库迁移并更新数据库。
定义数据库映射
Entity Framework Core 要求定义一个 DbContext 类作为数据库映射的主要对象。我们希望使用主应用程序的 DbContext 对象。这样,您可以在一个点控制数据库迁移,确保多模块操作上的数据库事务,并建立不同模块数据库表之间的关系。然而,订单模块不能使用主应用程序的 DbContext 对象,因为它不依赖于主应用程序,并且您也不想建立这样的依赖关系。
作为解决方案,您将在订单模块中使用 DbContext 接口,然后由主模块的 DbContext 实现。
在您的 IDE 中,打开 ModularCrm.Ordering 项目下的 Data 文件夹,并按如下所示编辑 IOrderingDbContext 接口:
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace ModularCrm.Ordering.Data;
[ConnectionStringName(OrderingDbProperties.ConnectionStringName)]
public interface IOrderingDbContext : IEfCoreDbContext
{
DbSet<Order> Orders { get; set; }
}
之后,在 ModularCrm.Ordering 项目下的 Data 文件夹中,为 OrderingDbContext 类创建 Orders DbSet:
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace ModularCrm.Ordering.Data;
[ConnectionStringName(OrderingDbProperties.ConnectionStringName)]
public class OrderingDbContext : AbpDbContext<OrderingDbContext>, IOrderingDbContext
{
public DbSet<Order> Orders { get; set; }
public OrderingDbContext(DbContextOptions<OrderingDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigureOrdering();
}
}
您可以在订单模块中注入并使用 IOrderingDbContext。然而,您通常不会直接使用该接口。相反,您将使用 ABP 的仓储,它在内部使用该接口。
最好在订单模块中配置 Order 实体的数据库表映射。您将在同一 Data 文件夹中使用 OrderingDbContextModelCreatingExtensions:
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
namespace ModularCrm.Ordering.Data;
public static class OrderingDbContextModelCreatingExtensions
{
public static void ConfigureOrdering(
this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<Order>(b =>
{
// 配置表名
b.ToTable(OrderingDbProperties.DbTablePrefix + "Orders",
OrderingDbProperties.DbSchema);
// 始终调用此方法来设置基实体属性
b.ConfigureByConvention();
// 实体属性
b.Property(q => q.CustomerName).IsRequired().HasMaxLength(120);
});
}
}
配置主应用程序
在您的 IDE 中打开主应用程序的解决方案,找到 ModularCrm 项目 Data 文件夹下的 ModularCrmDbContext 类,并按照以下 3 个步骤操作:
(1) 在 ModularCrmDbContext 类的顶部添加以下属性:
[ReplaceDbContext(typeof(IOrderingDbContext))]
ReplaceDbContext 属性允许在订单模块的服务中使用 ModularCrmDbContext 类。
(2) 让 ModularCrmDbContext 类实现 IOrderingDbContext 接口:
public class ModularCrmDbContext :
AbpDbContext<ModularCrmDbContext>,
ICatalogDbContext,
IOrderingDbContext // 新内容:实现接口
{
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; } // 新内容:添加 DBSET 属性
...
}
(3) 最后,确保在 OnModelCreating 方法中调用了 ConfigureOrdering() 扩展方法(ABP Studio 应该已经完成了此操作):
protected override void OnModelCreating(ModelBuilder builder)
{
...
builder.ConfigureOrdering();
}
通过这种方式,订单模块可以通过 IOrderingDbContext 接口使用 ModularCrmDbContext。这部分每个模块只需要配置一次。下次,您可以添加新的数据库迁移,如下节所述。
添加数据库迁移
现在,您可以添加一个新的数据库迁移。您可以使用 Entity Framework Core 的 Add-Migration(或 dotnet ef migrations add)终端命令,但在本教程中,您将使用 ABP Studio 的快捷 UI。
确保解决方案已构建。您可以在 ABP Studio 解决方案运行器 中右键单击 ModularCrm(位于 main 文件夹下)并选择 Dotnet CLI -> Graph Build 命令。
右键单击 ModularCrm 包并选择 EF Core CLI -> 添加迁移 命令:
添加迁移 命令会打开一个新对话框以获取迁移名称:
点击 确定 按钮后,一个新的数据库迁移类将被添加到 ModularCrm 项目的 Migrations 文件夹中:
现在,您可以返回 ABP Studio,右键单击 ModularCrm 项目并选择 EF Core CLI -> 更新数据库 命令:
操作完成后,您可以检查数据库,看到新的 OrderingOrders 表已创建:
订单模块的所有表名前都添加了 Ordering 前缀。如果您想更改或移除它,请查看订单模块 .NET 解决方案中的 OrderingDbProperties 类。
创建应用服务
您将创建一个应用服务来管理 Order 实体。
定义应用服务契约
您将在 ModularCrm.Ordering.Contracts 项目下创建 IOrderAppService 接口。回到您的 IDE,打开 ModularCrm.Ordering 模块的 .NET 解决方案,并在 ModularCrm.Ordering.Contracts 项目中创建一个 IOrderAppService 接口:
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace ModularCrm.Ordering;
public interface IOrderAppService : IApplicationService
{
Task<List<OrderDto>> GetListAsync();
Task CreateAsync(OrderCreationDto input);
}
定义数据传输对象
GetListAsync 和 CreateAsync 方法将使用数据传输对象(DTO)与客户端通信。为此,您将创建两个 DTO 类。
在 ModularCrm.Ordering.Contracts 项目下创建一个 OrderCreationDto 类:
using System;
using System.ComponentModel.DataAnnotations;
namespace ModularCrm.Ordering;
public class OrderCreationDto
{
[Required]
[StringLength(150)]
public string CustomerName { get; set; } = null!;
[Required]
public Guid ProductId { get; set; }
}
在 ModularCrm.Ordering.Contracts 项目下创建一个 OrderDto 类:
using System;
namespace ModularCrm.Ordering;
public class OrderDto
{
public Guid Id { get; set; }
public string CustomerName { get; set; } = null!;
public Guid ProductId { get; set; }
public OrderState State { get; set; }
}
ModularCrm.Ordering.Contracts 项目下的新文件应类似于下图:
实现应用服务
首先,在 ModularCrm.Ordering 项目下创建一个新的映射类,该类继承 MapperBase<Order, OrderDto> 并具有 [Mapper] 属性,用于将 Order 实体映射到 OrderDto 对象,如下所示,因为我们稍后会需要它:
[Mapper]
public partial class OrderToOrderDtoMapper : MapperBase<Order, OrderDto>
{
public override partial OrderDto Map(Order source);
public override partial void Map(Order source, OrderDto destination);
}
现在,您可以实现 IOrderAppService 接口。在 ModularCrm.Ordering 项目下创建一个 OrderAppService 类:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace ModularCrm.Ordering;
public class OrderAppService : OrderingAppService, IOrderAppService
{
private readonly IRepository<Order, Guid> _orderRepository;
public OrderAppService(IRepository<Order, Guid> orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<List<OrderDto>> GetListAsync()
{
var orders = await _orderRepository.GetListAsync();
return ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
}
public async Task CreateAsync(OrderCreationDto input)
{
var order = new Order
{
CustomerName = input.CustomerName,
ProductId = input.ProductId,
State = OrderState.Placed
};
await _orderRepository.InsertAsync(order);
}
}
将应用服务公开为 HTTP API 控制器
实现应用服务后,我们可以使用 ABP 的自动 API 控制器功能为订单模块创建 HTTP API 端点。为此,请在订单模块的 .NET 解决方案中(即 ModularCrm.Ordering 解决方案)打开 OrderingModule 类,找到 ConfigureServices 方法,并在该方法内添加以下行:
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(OrderingModule).Assembly, settings =>
{
settings.RootPath = "ordering";
});
});
这将告诉 ABP 框架为 ModularCrm.Ordering 程序集中的应用服务创建 API 控制器。
创建示例订单
本节将使用 Swagger UI 创建几个示例订单。这样,您就有一些示例订单可以在 UI 上显示。
打开解决方案运行器面板,点击解决方案根目录附近的 播放 按钮。一旦 ModularCrm 应用程序运行,您可以右键单击它并选择 浏览 命令以打开用户界面。
看到 Web 应用程序的用户界面后,在 URL 末尾输入 /swagger 以打开 Swagger UI。向下滚动,您应该会看到 Order API:
如果您没有看到 Order API,可能需要重新构建整个解决方案。右键单击 ABP Studio 解决方案资源管理器面板中
main文件夹下的ModularCrm,然后选择 Dotnet CLI -> Graph Build 命令。这将确保所有模块和主应用程序都已完全构建。
展开 POST /api/ordering/order API,点击 试一试 按钮。然后,通过填写请求正文并点击 执行 按钮来创建几个订单:
如果您检查数据库,应该会在 Orders 表中看到创建的实体:
创建用户界面
在本节中,您将创建一个非常简单的用户界面,以演示如何在目录模块中构建 UI 并使其在主应用程序中工作。
第一步,如果应用程序当前正在运行,您可以在 ABP Studio 的解决方案运行器中停止它。
创建订单页面
将 ModularCrm.Ordering.UI 项目的 Pages/Ordering 文件夹中的 Index.cshtml.cs 内容替换为以下代码块:
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ModularCrm.Ordering.UI.Pages.Ordering;
public class IndexModel : PageModel
{
public List<OrderDto> Orders { get; set; }
private readonly IOrderAppService _orderAppService;
public IndexModel(IOrderAppService orderAppService)
{
_orderAppService = orderAppService;
}
public async Task OnGetAsync()
{
Orders = await _orderAppService.GetListAsync();
}
}
在这里,您注入了 IOrderAppService 以从数据库查询 Order 实体并在页面上显示。打开 Index.cshtml 文件,并将其内容替换为以下代码块:
@page
@model ModularCrm.Ordering.UI.Pages.Ordering.IndexModel
<h1>Orders</h1>
<abp-card>
<abp-card-body>
<abp-list-group>
@foreach (var order in Model.Orders)
{
<abp-list-group-item>
<strong>Customer:</strong> @order.CustomerName <br />
<strong>Product:</strong> @order.ProductId <br />
<strong>State:</strong> @order.State
</abp-list-group-item>
}
</abp-list-group>
</abp-card-body>
</abp-card>
此页面在 UI 上显示订单列表。您尚未创建用于创建新订单的 UI,并且为了保持本教程的简洁性,我们不会创建它。如果您想学习如何使用 ABP 创建高级 UI,请参阅《书店教程》。
编辑菜单项
ABP 提供了一个模块化的导航菜单系统,每个模块都可以动态地为主菜单做出贡献。
编辑 ModularCrm.Ordering.UI 项目中的 OrderingMenuContributor 类:
using System.Threading.Tasks;
using Volo.Abp.UI.Navigation;
namespace ModularCrm.Ordering.UI.Menus;
public class OrderingMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
if (context.Menu.Name == StandardMenus.Main)
{
await ConfigureMainMenuAsync(context);
}
}
private Task ConfigureMainMenuAsync(MenuConfigurationContext context)
{
context.Menu.AddItem(
new ApplicationMenuItem(
OrderingMenus.Prefix, // 唯一菜单 ID
"Orders", // 菜单显示文本
"~/Ordering", // URL
"fa-solid fa-basket-shopping" // 图标 CSS 类
)
);
return Task.CompletedTask;
}
}
OrderingMenuContributor 实现了 IMenuContributor 接口,该接口强制我们实现 ConfigureMenuAsync 方法。在该方法中,您可以操作菜单项(添加新菜单项、移除现有菜单项或更改现有菜单项的属性)。每当在 UI 上呈现菜单时,都会执行 ConfigureMenuAsync 方法,因此您可以动态决定如何操作菜单项。
您可以查看菜单文档以了解更多关于操作菜单项的信息。
构建应用程序
现在,您将运行应用程序以查看结果。如果应用程序已经在运行,请先停止它。然后打开 解决方案运行器 面板,右键单击 ModularCrm 应用程序,然后选择 构建 -> Graph Build 命令:
您之所以执行图形构建,是因为您修改了一个模块,仅构建主应用程序是不够的。如果需要,Graph Build 命令也会构建依赖的模块。或者,您可以先在 ABP Studio 或您的 IDE 中构建订单模块。如果您有很多模块并且只修改了其中一个模块,这种方法可能会更快。现在,您可以通过右键单击 ModularCrm 应用程序并选择 启动 命令来运行应用程序。
很好!我们可以看到订单列表。但是,有一个问题:我们看到的是产品的 GUID ID,而不是其名称。这是因为订单模块与目录模块没有集成,无法访问产品模块的数据库来执行 JOIN 查询。我们将在 下一部分 解决这个问题。
总结
在 模块化 CRM 教程的这一部分,您已经在 上一部分 创建的订单模块内部构建了功能。在 下一部分 ,您将致力于建立订单模块和目录模块之间的通信。
抠丁客














