项目

构建订单模块

上一部分,您创建了订单模块并将其安装到主应用程序中。然而,订单模块目前还没有任何功能。在本部分中,您将创建一个 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 中应类似于下图:

visual-studio-order-entity

配置数据库映射

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 -> 添加迁移 命令:

abp-studio-add-entity-framework-core-migration

添加迁移 命令会打开一个新对话框以获取迁移名称:

abp-studio-entity-framework-core-add-migration-order

点击 确定 按钮后,一个新的数据库迁移类将被添加到 ModularCrm 项目的 Migrations 文件夹中:

visual-studio-new-migration-class-2

现在,您可以返回 ABP Studio,右键单击 ModularCrm 项目并选择 EF Core CLI -> 更新数据库 命令:

abp-studio-entity-framework-core-update-database

操作完成后,您可以检查数据库,看到新的 OrderingOrders 表已创建:

sql-server-products-database-table

订单模块的所有表名前都添加了 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);
}

定义数据传输对象

GetListAsyncCreateAsync 方法将使用数据传输对象(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 项目下的新文件应类似于下图:

visual-studio-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:

abp-studio-ordering-swagger-ui-in-browser

如果您没有看到 Order API,可能需要重新构建整个解决方案。右键单击 ABP Studio 解决方案资源管理器面板中 main 文件夹下的 ModularCrm,然后选择 Dotnet CLI -> Graph Build 命令。这将确保所有模块和主应用程序都已完全构建。

展开 POST /api/ordering/order API,点击 试一试 按钮。然后,通过填写请求正文并点击 执行 按钮来创建几个订单:

abp-studio-swagger-ui-create-order-execute

如果您检查数据库,应该会在 Orders 表中看到创建的实体:

sql-server-orders-database-table-filled

创建用户界面

在本节中,您将创建一个非常简单的用户界面,以演示如何在目录模块中构建 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 命令:

abp-studio-solution-runner-graph-build

您之所以执行图形构建,是因为您修改了一个模块,仅构建主应用程序是不够的。如果需要,Graph Build 命令也会构建依赖的模块。或者,您可以先在 ABP Studio 或您的 IDE 中构建订单模块。如果您有很多模块并且只修改了其中一个模块,这种方法可能会更快。现在,您可以通过右键单击 ModularCrm 应用程序并选择 启动 命令来运行应用程序。

abp-studio-browser-orders-menu-item

很好!我们可以看到订单列表。但是,有一个问题:我们看到的是产品的 GUID ID,而不是其名称。这是因为订单模块与目录模块没有集成,无法访问产品模块的数据库来执行 JOIN 查询。我们将在 下一部分 解决这个问题。

总结

模块化 CRM 教程的这一部分,您已经在 上一部分 创建的订单模块内部构建了功能。在 下一部分 ,您将致力于建立订单模块和目录模块之间的通信。


在本文档中