使用分层解决方案构建待办事项应用程序教程
这是一个单部分快速入门教程,旨在使用 ABP 框架构建一个简单的待办事项应用程序。以下是最终应用程序的截图:
你可以在 这里 找到已完成应用程序的源代码。
本教程还配有 YouTube 视频讲解!你可以在这里观看:
先决条件
支持 .NET 9.0+ 开发的 IDE(例如 Visual Studio)。
安装 ABP CLI 工具
我们将使用 ABP CLI 来创建新的 ABP 解决方案。你可以在终端窗口中运行以下命令来安装此 dotnet 工具:
dotnet tool install -g Volo.Abp.Studio.Cli
创建你的 ABP 解决方案
创建一个空文件夹,打开命令行终端,并在终端中执行以下命令:
abp new TodoApp
这将创建一个名为 TodoApp 的新解决方案。解决方案准备就绪后,在你喜欢的 IDE 中打开它。
创建数据库
如果你使用 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.Web 项目是启动项目,然后运行应用程序(在 Visual Studio 中按 Ctrl+F5)以查看初始界面:
你可以点击 登录 按钮,使用 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
这将在项目中添加一个新的迁移类:
你可以在同一命令行终端中使用以下命令将更改应用到数据库:
dotnet ef database update
如果你使用 Visual Studio,可能希望在 Package Manager Console (PMC) 中使用
Add-Migration Added_TodoItem和Update-Database命令。在这种情况下,请确保TodoApp.Web是启动项目,并且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);
}
数据传输对象
GetListAsync 和 CreateAsync 方法返回 TodoItemDto。ApplicationService 通常获取并返回 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 层使用。
用户界面层
是时候在用户界面上显示待办事项了!在开始编写代码之前,最好回顾一下我们要构建的内容。以下是最终用户界面的示例截图:
为了使本教程简单且重点突出,我们将保持用户界面部分尽量简洁。请参阅 Web 应用程序开发教程 以了解如何构建包含所有方面的真实页面。
Index.cshtml.cs
打开 TodoApp.Web 项目 Pages 文件夹中的 Index.cshtml.cs 文件,并将其内容替换为以下代码块:
using System.Collections.Generic;
using System.Threading.Tasks;
namespace TodoApp.Web.Pages;
public class IndexModel : TodoAppPageModel
{
public List<TodoItemDto> TodoItems { get; set; }
private readonly ITodoAppService _todoAppService;
public IndexModel(ITodoAppService todoAppService)
{
_todoAppService = todoAppService;
}
public async Task OnGetAsync()
{
TodoItems = await _todoAppService.GetListAsync();
}
}
此类使用 ITodoAppService 获取待办事项列表,并将结果赋值给 TodoItems 属性。我们将使用它在 Razor 页面上呈现待办事项。
Index.cshtml
打开 TodoApp.Web 项目 Pages 文件夹中的 Index.cshtml 文件,并将其内容替换为以下内容:
@page
@model TodoApp.Web.Pages.IndexModel
@section styles {
<abp-style src="/Pages/Index.css" />
}
@section scripts {
<abp-script src="/Pages/Index.js" />
}
<div class="container">
<abp-card>
<abp-card-header>
<abp-card-title>
待办事项列表
</abp-card-title>
</abp-card-header>
<abp-card-body>
<!-- 新建待办事项的表单 -->
<form id="NewItemForm" class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12">
<div class="input-group">
<input id="NewItemText" type="text" class="form-control" placeholder="输入内容...">
</div>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
<!-- 待办事项列表 -->
<ul id="TodoList">
@foreach (var todoItem in Model.TodoItems)
{
<li data-id="@todoItem.Id">
<i class="fa fa-trash-o"></i> @todoItem.Text
</li>
}
</ul>
</abp-card-body>
</abp-card>
</div>
我们正在使用 ABP 的 卡片标签助手 来创建一个简单的卡片视图。你也可以直接使用标准的 Bootstrap HTML 结构,但 ABP 的 标签助手 使其更加简单和类型安全。
此页面导入了 CSS 和 JavaScript 文件,因此我们也应该创建它们。
Index.js
在 TodoApp.Web 项目的 Pages 文件夹中创建一个名为 Index.js 的文件,并将其内容替换为以下内容:
$(function () {
// 删除项目 /////////////////////////////////////////
$('#TodoList').on('click', 'li i', function(){
var $li = $(this).parent();
var id = $li.attr('data-id');
todoApp.todo.delete(id).then(function(){
$li.remove();
abp.notify.info('已删除待办事项。');
});
});
// 创建新项目 /////////////////////////////////////
$('#NewItemForm').submit(function(e){
e.preventDefault();
var todoText = $('#NewItemText').val();
todoApp.todo.create(todoText).then(function(result){
$('<li data-id="' + result.id + '">')
.html('<i class="fa fa-trash-o"></i> ' + result.text)
.appendTo($('#TodoList'));
$('#NewItemText').val('');
});
});
});
在第一部分中,我们订阅了待办事项旁边垃圾桶图标的点击事件,在服务器上删除相关项目,并在用户界面上显示通知。同时,我们从 DOM 中移除了已删除的项目,因此我们不需要刷新页面。
在第二部分中,我们在服务器上创建一个新的待办事项。如果成功,我们将操作 DOM 以向待办事项列表插入一个新的 <li> 元素。这样在创建新的待办事项后,我们就不需要刷新整个页面。
这里有趣的部分是我们如何与服务器通信。请参阅 动态 JavaScript 代理与自动 API 控制器 部分以了解其工作原理。但现在,让我们继续完成应用程序。
Index.css
作为最后的润色,在 TodoApp.Web 项目的 Pages 文件夹中创建一个名为 Index.css 的文件,并添加以下内容:
#TodoList{
list-style: none;
margin: 0;
padding: 0;
}
#TodoList li {
padding: 5px;
margin: 5px 0px;
}
#TodoList li i
{
opacity: 0.5;
}
#TodoList li i:hover
{
opacity: 1;
color: #ff0000;
cursor: pointer;
}
这是待办事项页面的简单样式。我们相信你可以做得更好 :)
现在,你可以再次运行应用程序查看结果。
动态 JavaScript 代理与自动 API 控制器
在 Index.js 文件中,我们使用了 todoApp.todo.delete(...) 和 todoApp.todo.create(...) 函数与服务器通信。这些函数是由 ABP 的 动态 JavaScript 客户端代理 系统动态创建的。它们向服务器执行 HTTP API 调用并返回一个 Promise,因此你可以像上面那样向 then 函数注册回调。
然而,你可能注意到我们还没有创建任何 API 控制器,那么服务器如何处理这些请求呢?这个问题将我们带到 ABP 的 自动 API 控制器 特性。它会按照约定自动将应用服务转换为 API 控制器。
如果你通过在应用程序中输入 /swagger URL 打开 Swagger UI,就可以看到 Todo API:
结论
在本教程中,我们构建了一个非常简单的应用程序来熟悉 ABP 框架。如果你想构建一个正式的项目,请查阅 Web 应用程序开发教程,它涵盖了现实 Web 应用程序开发的所有方面。
源代码
你可以在 这里 找到已完成应用程序的源代码。
抠丁客






