项目

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

UI
Database

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

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

待办事项列表

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

本文档在 YouTube 上配有视频教程!!你可以在这里观看:

前置要求

创建新解决方案

在本教程中,我们将使用 ABP CLI 创建一个基于 ABP 的示例应用程序。如果你尚未安装 ABP CLI,可以在命令行终端中运行以下命令来安装:

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

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

abp new TodoApp -t app-nolayers -d mongodb

这将创建一个包含单个项目的新解决方案,项目名为 TodoApp。解决方案准备就绪后,请在你喜欢的 IDE 中打开它。

运行应用程序前的准备工作

如果你使用 ABP CLI 创建了解决方案,可以跳过此部分直接阅读运行应用程序章节。因为 CLI 会自动执行以下步骤。但是,有时可能需要手动执行这些步骤。例如,如果你从源代码控制系统克隆了应用程序代码,则需要手动执行。

创建数据库

你可以在 项目的根目录下(与 .csproj 文件同级的文件夹) 运行以下命令来创建数据库并填充初始数据:

dotnet run --migrate-database

此命令将为你创建数据库并填充初始数据。你也可以执行解决方案根文件夹中包含的 migrate-database.ps1 脚本。

安装客户端包

在解决方案的根目录下运行 abp install-libs 命令以安装所有必需的 NPM 包:

abp install-libs

运行应用程序

在开始开发之前,最好先运行一下应用程序。运行应用程序非常简单,你可以使用任何支持 .NET 的 IDE 来运行,或者在项目目录中运行 dotnet run CLI 命令:

todo-ui-initial

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

好的,我们可以开始编码了!

定义实体

这个应用程序将有一个单一的实体,我们可以从创建它开始。在 项目 的 Entities 文件夹下创建一个新的 TodoItem 类:

using Volo.Abp.Domain.Entities;

namespace TodoApp.Entities;

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

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

数据库集成

下一步是配置 MongoDB。打开项目中 Data 文件夹下的 TodoAppDbContext 类,并进行以下更改:

  1. 向类中添加一个新属性:
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>();
  1. CreateModel 方法内添加以下代码:
modelBuilder.Entity<TodoItem>(b =>
{
    b.CollectionName = "TodoItems";
});

完成数据库集成后,现在我们可以开始创建应用服务方法并实现我们的用例了。

创建应用服务

应用服务用于执行应用程序的用例。在这个应用程序中,我们需要执行以下用例:

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

在开始实现这些用例之前,我们首先需要创建一个将在应用服务中使用的 DTO 类。

创建数据传输对象 (DTO)

应用服务通常获取和返回 DTO(数据传输对象),而不是实体。因此,在 Services/Dtos 文件夹下创建一个新的 TodoItemDto 类:

namespace TodoApp.Services.Dtos;

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

这是一个非常简单的 DTO 类,其属性与 TodoItem 实体相同。现在,我们准备好实现我们的用例了。

应用服务实现

在 你的项目 的 Services 文件夹下创建一个 TodoAppService 类,如下所示:

using TodoApp.Services.Dtos;
using TodoApp.Entities;
using Volo.Abp.Domain.Repositories;

namespace TodoApp.Services;

public class TodoAppService : TodoAppAppService
{
    private readonly IRepository<TodoItem, Guid> _todoItemRepository;
    
    public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository)
    {
        _todoItemRepository = todoItemRepository;
    }
    
    // TODO: 在这里实现方法...
}

该类继承自 TodoAppAppService,而 TodoAppAppService 继承自 ABP 的 ApplicationService 类,并实现我们的用例。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 层调用了。那么,让我们来实现它。

用户界面

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

待办事项列表

Index.cshtml.cs

打开 Pages 文件夹中的 Index.cshtml.cs 文件,并将其内容替换为以下代码块:

using TodoApp.Services;
using TodoApp.Services.Dtos;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;

namespace TodoApp.Pages;

public class IndexModel : AbpPageModel
{
    public List<TodoItemDto> TodoItems { get; set; } = new();

    private readonly TodoAppService _todoAppService;

    public IndexModel(TodoAppService todoAppService)
    {
        _todoAppService = todoAppService;
    }
    
    public async Task OnGetAsync()
    {
        TodoItems = await _todoAppService.GetListAsync();
    }
}

该类使用 TodoAppService 来获取待办事项列表,并将其赋值给 TodoItems 属性。我们将使用它在 Razor 页面上渲染待办事项。

Index.cshtml

打开 Pages 文件夹中的 Index.cshtml 文件,并将其内容替换为以下内容:

@page
@model TodoApp.Pages.IndexModel

@section styles {
    <abp-style src="/Pages/Index.cshtml.css" />
}
@section scripts {
    <abp-script src="/Pages/Index.cshtml.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.cshtml.js

打开 Pages 文件夹中的 Index.cshtml.js 文件,并将其内容替换为以下内容:

$(function () {
    
    // 删除项目 /////////////////////////////////////////
    $('#TodoList').on('click', 'li i', function(){
        var $li = $(this).parent();
        var id = $li.attr('data-id');
        
        todoApp.services.todo.delete(id).then(function(){
            $li.remove();
            abp.notify.info('已删除待办事项。');
        });
    });
    
    // 创建新项目 /////////////////////////////////////
    $('#NewItemForm').submit(function(e){
        e.preventDefault();
        
        var todoText = $('#NewItemText').val();        
        todoApp.services.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('');
        });
    });
});

在第一部分中,我们订阅了待办事项旁边垃圾桶图标的点击事件,在服务器上删除了相关项目,并在 UI 上显示了一条通知。同时,我们从 DOM 中移除了被删除的项目,这样我们就不需要刷新页面。

在第二部分中,我们在服务器上创建了一个新的待办事项。如果成功,我们将操作 DOM,向待办事项列表中插入一个新的 <li> 元素。这样,在创建新的待办事项后,我们就不需要刷新整个页面。

这里有趣的部分是我们如何与服务器通信。请参阅动态 JavaScript 代理和自动 API 控制器部分以了解其工作原理。但现在,让我们继续完成应用程序。

Index.cshtml.css

作为最后的润色,打开 Pages 文件夹中的 Index.cshtml.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.cshtml.js 文件中,我们使用了 todoApp.services.todo.delete(...)todoApp.services.todo.create(...) 函数来与服务器通信。这些函数是由 ABP 通过动态 JavaScript 客户端代理系统动态创建的。它们向服务器执行 HTTP API 调用并返回一个 Promise,因此你可以像上面那样向 then 函数注册一个回调。

services 关键字来自命名空间 (namespace TodoApp.Services;)。这是一种命名约定。

但是,你可能注意到我们并没有创建任何 API 控制器,那么服务器是如何处理这些请求的呢?这个问题引出了 ABP 的自动 API 控制器功能。它根据约定自动将应用服务转换为 API 控制器

如果你在应用程序中输入 /swagger 网址打开 Swagger UI,你可以看到 Todo API:

todo-api

结论

在本教程中,我们构建了一个非常简单的应用程序来熟悉 ABP。请查看Web 应用程序开发教程,以了解使用 分层应用程序启动模板 在分层架构中进行的真实 Web 应用程序开发。

源代码

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

另请参阅

在本文档中