项目

本文档有多个版本。请选择最适合您的选项。

UI
Database

使用分层解决方案构建待办事项应用程序教程

这是一个单部分快速入门教程,旨在使用 ABP 框架构建一个简单的待办事项应用程序。以下是最终应用程序的截图:

todo-list

你可以在 这里 找到已完成应用程序的源代码。

本教程还配有 YouTube 视频讲解!你可以在这里观看:

先决条件

安装 ABP CLI 工具

我们将使用 ABP CLI 来创建新的 ABP 解决方案。你可以在终端窗口中运行以下命令来安装此 dotnet 工具:

dotnet tool install -g Volo.Abp.Studio.Cli

创建你的 ABP 解决方案

创建一个空文件夹,打开命令行终端,并在终端中执行以下命令:

abp new TodoApp -u angular

这将创建一个名为 TodoApp 的新解决方案,其中包含 angularaspnet-core 文件夹。解决方案准备就绪后,在你喜欢的 IDE 中打开 ASP.NET Core 解决方案。

创建数据库

如果你使用 Visual Studio,右键单击 TodoApp.DbMigrator 项目,选择 设为启动项目,然后按 Ctrl+F5 运行(不调试)。它将创建初始数据库并填充初始数据。

某些 IDE(例如 Rider)可能在首次运行时遇到问题,因为 DbMigrator 会添加初始迁移并重新编译项目。在这种情况下,请在 .DbMigrator 项目文件夹中打开命令行终端,并执行 dotnet run 命令。

运行应用程序之前

安装客户端包

ABP CLI 在创建应用程序时,会在后台运行 abp install-libs 命令,以为你的解决方案安装所需的 NPM 包。

但是,有时可能需要手动运行此命令。例如,如果你克隆了应用程序,或者 node_modules 文件夹中的资源未复制到 wwwroot/libs 文件夹,或者你向解决方案添加了新的客户端包依赖,则需要运行此命令。

对于这些情况,请在解决方案的根目录下运行 abp install-libs 命令以安装所有必需的 NPM 包:

abp install-libs

运行应用程序

最好在开始开发之前运行应用程序。解决方案有两个主要应用程序:

  • TodoApp.HttpApi.Host(在 .NET 解决方案中)托管服务器端 HTTP API。
  • angular 文件夹包含 Angular 应用程序。

确保 TodoApp.HttpApi.Host 项目是启动项目,然后运行应用程序(在 Visual Studio 中按 Ctrl+F5),在 Swagger UI 上查看服务器端 HTTP API:

todo-swagger-ui-initial

你可以通过此界面探索和测试你的 HTTP API。如果运行正常,我们就可以运行 Angular 客户端应用程序。

你可以使用以下命令运行应用程序:

npm start

此命令需要一些时间,但最终会运行并在你的默认浏览器中打开应用程序:

todo-ui-initial

你可以点击 登录 按钮,使用 admin 作为用户名,1q2w3E* 作为密码登录应用程序。

一切准备就绪。我们可以开始编码了!

领域层

此应用程序有一个单一的 实体,我们将从创建它开始。在 TodoApp.Domain 项目的 Entities 文件夹下创建一个新的 TodoItem 类,如下所示:

using System;
using Volo.Abp.Domain.Entities;

namespace TodoApp;

public class TodoItem : BasicAggregateRoot<Guid>
{
    public string Text { get; set; } = string.Empty;
}

BasicAggregateRoot 是创建根实体最简单的基类,Guid 是此处实体的主键(Id)。

数据库集成

下一步是配置 Entity Framework Core

映射配置

打开 TodoApp.EntityFrameworkCore 项目 EntityFrameworkCore 文件夹中的 TodoAppDbContext 类,并向此类添加一个新的 DbSet 属性:

public DbSet<TodoItem> TodoItems { get; set; }

然后,导航到 TodoAppDbContext 类中的 OnModelCreating 方法,并为 TodoItem 实体添加映射代码:

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

    /* 将模块包含到你的迁移数据库上下文中 */

    builder.ConfigurePermissionManagement();
    ...

    /* 在此处配置你自己的表/实体 */
    builder.Entity<TodoItem>(b =>
    {
        b.ToTable("TodoItems");
    });
}

我们已将 TodoItem 实体映射到数据库中的 TodoItems 表。

代码优先迁移

启动解决方案已配置为使用 Entity Framework Core 的 代码优先迁移。由于我们更改了数据库映射配置,应该创建新的迁移并将更改应用到数据库。

TodoApp.EntityFrameworkCore 项目目录中打开命令行终端,并输入以下命令:

dotnet ef migrations add Added_TodoItem

这将在项目中添加一个新的迁移类:

todo-efcore-migration

你可以在同一命令行终端中使用以下命令将更改应用到数据库:

dotnet ef database update

如果你使用 Visual Studio,可能希望在 Package Manager Console (PMC) 中使用 Add-Migration Added_TodoItemUpdate-Database 命令。在这种情况下,请确保 TodoApp.HttpApi.Host 是启动项目,并且 TodoApp.EntityFrameworkCore 是 PMC 中的 默认项目

现在,我们可以使用 ABP 的仓储来保存和检索待办事项,如下一节所示。

应用层

应用服务 用于执行应用程序的用例。我们需要执行以下用例:

  • 获取待办事项列表
  • 创建新的待办事项
  • 删除现有的待办事项

应用服务接口

我们可以从为应用服务定义接口开始。在 TodoApp.Application.Contracts 项目中创建一个新的 ITodoAppService 接口,如下所示:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace TodoApp;

public interface ITodoAppService : IApplicationService
{
    Task<List<TodoItemDto>> GetListAsync();
    Task<TodoItemDto> CreateAsync(string text);
    Task DeleteAsync(Guid id);
}

数据传输对象

GetListAsyncCreateAsync 方法返回 TodoItemDtoApplicationService 通常获取并返回 DTO(数据传输对象)而不是实体。因此,我们应该在此定义 DTO 类。在 TodoApp.Application.Contracts 项目中创建一个新的 TodoItemDto 类:

using System;

namespace TodoApp;

public class TodoItemDto
{
    public Guid Id { get; set; }
    public string Text { get; set; } = string.Empty;
}

这是一个非常简单的 DTO 类,与我们的 TodoItem 实体匹配。我们已准备好实现 ITodoAppService

应用服务实现

TodoApp.Application 项目中创建一个 TodoAppService 类,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace TodoApp;

public class TodoAppService : ApplicationService, ITodoAppService
{
    private readonly IRepository<TodoItem, Guid> _todoItemRepository;

    public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository)
    {
        _todoItemRepository = todoItemRepository;
    }
    
    // TODO: 在此处实现方法...
}

此类继承自 ABP 的 ApplicationService 类,并实现之前定义的 ITodoAppService。ABP 为实体提供默认的通用 仓储。我们可以使用它们来执行基本的数据库操作。此类 注入IRepository<TodoItem, Guid>,这是 TodoItem 实体的默认仓储。我们将使用它来实现前面描述的用例。

获取待办事项

让我们从实现 GetListAsync 方法开始:

public async Task<List<TodoItemDto>> GetListAsync()
{
    var items = await _todoItemRepository.GetListAsync();
    return items
        .Select(item => new TodoItemDto
        {
            Id = item.Id,
            Text = item.Text
        }).ToList();
}

我们只是简单地从数据库中获取完整的 TodoItem 列表,将它们映射到 TodoItemDto 对象,并作为结果返回。

创建新的待办事项

下一个方法是 CreateAsync,我们可以如下所示实现它:

public async Task<TodoItemDto> CreateAsync(string text)
{
    var todoItem = await _todoItemRepository.InsertAsync(
        new TodoItem {Text = text}
    );

    return new TodoItemDto
    {
        Id = todoItem.Id,
        Text = todoItem.Text
    };
}

仓储的 InsertAsync 方法将给定的 TodoItem 插入数据库并返回同一个 TodoItem 对象。它还会设置 Id,因此我们可以在返回对象上使用它。我们只需通过从新的 TodoItem 实体创建来返回一个 TodoItemDto

删除待办事项

最后,我们可以如下代码块所示实现 DeleteAsync

public async Task DeleteAsync(Guid id)
{
    await _todoItemRepository.DeleteAsync(id);
}

应用服务已准备就绪,可供 UI 层使用。

用户界面层

是时候在用户界面上显示待办事项了!在开始编写代码之前,最好回顾一下我们要构建的内容。以下是最终用户界面的示例截图:

todo-list

为了使本教程简单且重点突出,我们将保持用户界面部分尽量简洁。请参阅 Web 应用程序开发教程 以了解如何构建包含所有方面的真实页面。

服务代理生成

ABP 提供了一个便捷的特性,可以自动创建客户端服务,以便轻松使用服务器提供的 HTTP API。

你首先需要运行 TodoApp.HttpApi.Host 项目,因为代理生成器从服务器应用程序读取 API 定义。

警告:IIS Express 有一个问题:它不允许从另一个进程连接到应用程序。如果你使用的是 Visual Studio,请在运行按钮下拉列表中选择 TodoApp.HttpApi.Host 而不是 IIS Express,如下图所示:

run-without-iisexpress

一旦你运行了 TodoApp.HttpApi.Host 项目,在 angular 文件夹中打开命令行终端,并输入以下命令:

abp generate-proxy -t ng

如果一切顺利,它应该会生成如下所示的输出:

CREATE src/app/proxy/generate-proxy.json (170978 bytes)
CREATE src/app/proxy/README.md (1000 bytes)
CREATE src/app/proxy/todo.service.ts (794 bytes)
CREATE src/app/proxy/models.ts (66 bytes)
CREATE src/app/proxy/index.ts (58 bytes)

然后,我们可以使用 todoService 来使用服务器端 HTTP API,如下一节所示。

home.component.ts

打开 /angular/src/app/home/home.component.ts 文件,并将其内容替换为以下代码块:

import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit, inject } from '@angular/core';
import { TodoItemDto, TodoService } from '@proxy';

@Component({
  selector: 'app-home',
  standalone: false,
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  todoItems: TodoItemDto[];
  newTodoText: string;

  private readonly todoService = inject(TodoService);
  private readonly toasterService = inject(ToasterService);

  ngOnInit(): void {
    this.todoService.getList().subscribe(response => {
      this.todoItems = response;
    });
  }
  
  create(): void {
    this.todoService.create(this.newTodoText).subscribe((result) => {
      this.todoItems = this.todoItems.concat(result);
      this.newTodoText = null;
    });
  }

  delete(id: string): void {
    this.todoService.delete(id).subscribe(() => {
      this.todoItems = this.todoItems.filter(item => item.id !== id);
      this.toasterService.info('已删除待办事项。');
    });
  }  
}

我们使用了 todoService 来获取待办事项列表,并将返回值赋值给 todoItems 数组。我们还添加了 createdelete 方法。这些方法将在视图端使用。

home.component.html

打开 /angular/src/app/home/home.component.html 文件,并将其内容替换为以下代码块:

<div class="container">
  <div class="card">
    <div class="card-header">
      <div class="card-title">待办事项列表</div>
    </div>
    <div class="card-body">
      <!-- 新建待办事项的表单 -->
      <form class="row row-cols-lg-auto g-3 align-items-center" (ngSubmit)="create()">
        <div class="col-12">
          <div class="input-group">
            <input name="NewTodoText" type="text" [(ngModel)]="newTodoText" class="form-control" placeholder="输入内容..." />
          </div>
        </div>
        <div class="col-12">
          <button type="submit" class="btn btn-primary">提交</button>
        </div>
      </form>
      <!-- 待办事项列表 -->
      <ul id="TodoList">
        <li *ngFor="let todoItem of todoItems">
          <i class="fa fa-trash-o" (click)="delete(todoItem.id)"></i> {{ todoItem.text }}
        </li>
      </ul>
    </div>
  </div>
</div>

home.component.scss

作为最后的润色,打开 /angular/src/app/home/home.component.scss 文件,并添加以下代码块:

#TodoList{
    list-style: none;
    margin: 0;
    padding: 0;
}

#TodoList li {
    padding: 5px;
    margin: 5px 0px;
    border: 1px solid #cccccc;
    background-color: #f5f5f5;
}

#TodoList li i
{
    opacity: 0.5;
}

#TodoList li i:hover
{
    opacity: 1;
    color: #ff0000;
    cursor: pointer;
}

这是待办事项页面的简单样式。我们相信你可以做得更好 :)

现在,你可以再次运行应用程序查看结果。

结论

在本教程中,我们构建了一个非常简单的应用程序来熟悉 ABP 框架。如果你想构建一个正式的项目,请查阅 Web 应用程序开发教程,它涵盖了现实 Web 应用程序开发的所有方面。

源代码

你可以在 这里 找到已完成应用程序的源代码。

另请参阅

在本文档中