You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
abp/docs/zh-Hans/Tutorials/todo/Index.md

26 KiB

快速入门

//[doc-params]
{
    "UI": ["MVC", "Blazor", "BlazorServer", "NG"],
    "DB": ["EF", "Mongo"]
}

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

todo-list

你可以在这里找到已完成的项目源代码.

先决条件

{{if DB=="Mongo"}}

{{end}}

{{if UI=="NG"}}

{{end}}

创建新的解决方案

我们将使用ABP CLI 创建带有ABP框架的新解决方案. 你可以在命令行终端中运行以下命令来安装它:

dotnet tool install -g Volo.Abp.Cli

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

abp new TodoApp{{if UI=="Blazor"}} -u blazor{{else if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}}

{{if UI=="NG"}}

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

{{else}}

这将创建一个名为TodoApp的新解决方案.解决方案准备好后,在你的IDE中打开它.

{{end}}

创建数据库

如果你使用的是visual studio,请右键单击TodoApp.DbMigrator项目,选择设置为启动项目,然后按Ctrl+F5运行它而不进行调试.它将创建初始数据库并播种初始数据.

{{if DB=="EF"}}

由于DbMigrator添加初始迁移并重新编译项目,一些ide(例如Rider)在第一次运行时可能会出现问题.在这种情况下,请打开.DbMigrator项目文件夹中的命令行终端,然后执行dotnet run命令.

{{end}}

运行应用程序

{{if UI=="MVC" || UI=="BlazorServer"}}

最好在开始开发之前运行一下应用程序.确保 {{if UI=="BlazorServer"}}TodoApp.Blazor{{else}}TodoApp.Web{{end}} 是启动项目,然后运行应用程序(Visual Studio中是Ctrl+F5)以查看查看初始UI:

{{else if UI=="Blazor"}}

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

  • TodoApp.HttpApi.Host 托管服务器端的 HTTP API.
  • TodoApp.Blazor 客户端托管的 Blazor WebAssembly 应用.

确保 TodoApp.HttpApi.Host 是启动项目,然后运行应用程序(Visual Studio中的Ctrl+F5)以查看Swagger UI上server-side 的 HTTP API:

todo-swagger-ui-initial

你可以使用此UI查看和测试你的HTTP API.现在,我们可以将 TodoApp.Blazor 设置为启动项目,并运行它来打开实际的Blazor应用程序UI:

{{else if UI=="NG"}}

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

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

确保TodoApp.HttpApi.Hostproject是启动项目,然后运行应用程序(visual studio中是Ctrl+F5)以查看Swagger UI上server-side HTTP API:

todo-swagger-ui-initial

你可以使用这个UI查看和测试你的HTTP API.如果可以的话,我们可以运行Angular客户端应用程序.

首先,运行以下命令来还原NPM包:

npm install

安装所有软件包需要一些时间.然后可以使用以下命令运行应用程序:

npm start

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

{{end}}

todo-ui-initial

你可以单击 登录 按钮,以admin作为用户名,1q2w3E* 作为密码登录到应用程序.

一切就绪.我们可以开始编码了!

领域层

此应用程序只有一个 实体 我们开始创建它. 在TodoApp.Domain项目中创建一个新的 TodoItem 类:

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

namespace TodoApp
{
    public class TodoItem : BasicAggregateRoot<Guid>
    {
        public string Text { get; set; }
    }
}

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

Database Integration 数据库集成

{{if DB=="EF"}}

下一步是配置 Entity Framework Core.

Mapping Configuration 映射配置

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

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

然后在同一文件夹中打开 TodoAppDbContextModelCreatingExtensions 类,并为 TodoItem 类添加映射配置,如下所示:

public static void ConfigureTodoApp(this ModelBuilder builder)
{
    Check.NotNull(builder, nameof(builder));

    builder.Entity<TodoItem>(b =>
    {
        b.ToTable("TodoItems");
    });
}

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

代码优先迁移

解决方案启动模版已经配置为使用Entity Framework Core Code First 迁移.由于我们已经更改了数据库映射配置,因此我们应该创建一个新的迁移并将更改应用于数据库.

TodoApp.EntityFrameworkCore.DbMigrations 项目的目录中打开一个命令行终端,然后键入以下命令:

dotnet ef migrations add Added_TodoItem

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

todo-efcore-migration

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

dotnet ef database update

如果你使用的是Visual Studio,则可能希望在包管理器控制台 (PMC) 中使用 Add-Migration Added_TodoItemUpdate-Database 命令.在这种情况下,请确保 {{if UI=="MVC"}}TodoApp.Web{{else if UI=="BlazorServer"}}TodoApp.Blazor{{else if UI=="Blazor" || UI=="NG"}}TodoApp.HttpApi.Host{{end}} 是启动项目,并且 TodoApp.EntityFrameworkCore.DbMigrations 是PMC中的 默认项目. {{else if DB=="Mongo"}}

下一步是设置 MongoDB 配置.在 TodoApp.MongoDB 项目的 MongoDb 文件夹中打开 TodoAppMongoDbContext 类,并进行以下更改;

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

{{end}}

现在,我们可以使用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 方法返回 TodoItemDto. 通常应用服务获取或返回的是 DTO (数据传输对象) 而不是直接获取或返回实体对象. 所以,我们应该在此定义 DTO(数据传输对象) 类. 在 TodoApp.Application.Contracts 项目中创建一个名为 TodoItemDto 的类:

using System;

namespace TodoApp
{
    public class TodoItemDto
    {
        public Guid Id { get; set; }
        public string Text { get; set; }
    }
}

这是一个非常简单的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: Implement the methods here...
    }
}

该类继承自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层来使用.

用户界面层

是时候在UI上显示待办事项了!在开始编写代码之前,最好记住我们正在尝试构建的内容.这是最终UI的示例屏幕截图:

todo-list

我们将在本教程中保持最简洁轻量的UI端,以使本教程简单且重点突出.请参阅web应用程序开发教程 以构建包含各个方面的真实页面.

{{if UI=="MVC"}}

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>
                TODO LIST
            </abp-card-title>
        </abp-card-header>
        <abp-card-body>            
            <!-- FORM FOR NEW TODO ITEMS -->
            <form id="NewItemForm" class="form-inline">
                <input id="NewItemText" 
                       type="text" 
                       class="form-control mr-2" 
                       placeholder="enter text...">
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>
            
            <!-- TODO ITEMS LIST -->
            <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 () {
    
    // DELETING ITEMS /////////////////////////////////////////
    $('#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('Deleted the todo item.');
        });
    });
    
    // CREATING NEW ITEMS /////////////////////////////////////
    $('#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('');
        });
    });
});

在第一部分中,我们在待办事项目附近注册点击删除图标所要触发的事件,删除服务器上的相关待办事项并在UI上显示通知.另外,我们从DOM中删除已删除的项,因此我们不需要刷新页面.

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

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

Index.css

来到最后要做的工作,请打开 TodoApp.Web 项目的 Pages 文件夹中的 Index.css 文件,并替换为以下内容:

#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;
}

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

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

Dynamic JavaScript Proxies & Auto API Controllers 动态JavaScript代理和自动应用编程接口控制器

Index.js 文件中,我们使用了 todoApp.todo.delete(...)todoApp.todo.create(...) 函数与服务器通信.这些函数是由ABP框架动态创建的,这要归功于 动态JavaScript客户端代理 系统.他们对服务器执行HTTP API调用并返回承诺,因此你可以像上面所做的那样注册对 then 函数的回调.

但是,你可能会问我们还没有创建任何API控制器,那么服务器如何处理这些请求呢? 这个问题为我们引出了ABP框架的 自动API控制器 功能.它通过约定自动将应用程序服务转换为API Controllers(API控制器).

如果通过在应用程序中输入 /swagger URL来打开 [swagger UI](https://swagger.io/tools/ Swagger-ui/),则可以看到Todo API:

todo-api

{{else if UI=="Blazor" || UI=="BlazorServer"}}

Index.razor.cs

TodoApp.Blazor 项目的 Pages 文件夹中打开 Index.razor.cs 文件,并用以下代码块替换内容:

using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace TodoApp.Blazor.Pages
{
    public partial class Index
    {
        [Inject]
        private ITodoAppService TodoAppService { get; set; }

        private List<TodoItemDto> TodoItems { get; set; } = new List<TodoItemDto>();
        private string NewTodoText { get; set; }

        protected async override Task OnInitializedAsync()
        {
            TodoItems = await TodoAppService.GetListAsync();
        }
        
        private async Task Create()
        {
            var result = await TodoAppService.CreateAsync(NewTodoText);
            TodoItems.Add(result);
            NewTodoText = null;
        }

        private async Task Delete(TodoItemDto todoItem)
        {
            await TodoAppService.DeleteAsync(todoItem.Id);
            await Notify.Info("Deleted the todo item.");
            TodoItems.Remove(todoItem);
        }
    }
}

这个类使用 ITodoAppService 对待办事项执行操作.它在创建和删除操作后操纵 TodoItems 列表.这样,我们不需要从服务器刷新整个todo列表.

{{if UI=="Blazor"}}

请参阅下面的 动态C # 代理和自动API控制器 部分,了解如何从浏览器上运行的Blazor应用程序注入和使用应用程序服务接口!但是现在,让我们继续并完成这个应用.

{{end # Blazor}}

Index.razor

TodoApp.Blazor 项目的 Pages 文件夹中打开 Index.razor 文件,并用以下代码块替换内容:

@page "/"
@inherits TodoAppComponentBase
<div class="container">
    <Card>
        <CardHeader>
            <CardTitle>
                TODO LIST
            </CardTitle>
        </CardHeader>
        <CardBody>
            <!-- FORM FOR NEW TODO ITEMS -->
            <form id="NewItemForm" 
                  @onsubmit:preventDefault
                  @onsubmit="() => Create()"
                  class="form-inline">
                <input type="text" 
                       @bind-value="@NewTodoText"
                       class="form-control mr-2" 
                       placeholder="enter text...">
                <button type="submit" class="btn btn-primary">Submit</button>
            </form>

            <!-- TODO ITEMS LIST -->
            <ul id="TodoList">
                @foreach (var todoItem in TodoItems)
                {
                    <li data-id="@todoItem.Id">
                        <i class="far fa-trash-alt"
                           @onclick="() => Delete(todoItem)"
                           ></i> @todoItem.Text
                    </li>
                }
            </ul>
        </CardBody>
    </Card>
</div>

Index.razor.css

最后,请在 TodoApp.Web 项目的 Pages 文件夹中打开 Index.razor.css 文件,并替换为以下内容:

#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;
}

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

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

{{if UI=="Blazor"}}

Dynamic C# Proxies & Auto API Controllers 动态C#代理和自动应用编程接口控制器

Index.razor.cs 文件中,我们已经注入(使用 [Inject] 特性)并像使用本地服务一样使用 ITodoAppService.请记住,当此应用程序服务的实例在服务器上运行的同时,Blazor应用程序正在浏览器上运行.

这个神奇的过程是由ABP框架的 动态C#客户端代理 系统完成的.它使用标准的 HttpClient 并向远程服务器执行HTTP API请求.它还为我们处理所有标准任务,包括授权,JSON序列化和异常处理.

但是,你可能会问我们还没有创建任何API控制器,那么服务器如何处理这些请求呢? 这个问题为我们引出了ABP框架的 自动API控制器 功能.它通过约定自动将应用程序服务转换为API控制器.

如果你运行 TodoApp.HttpApi.Host 应用程序,你可以看到Todo API:

todo-api

{{end # Blazor}}

{{else if UI=="NG"}}

服务代理生成

ABP提供了一个方便的功能,可以自动创建客户端服务,轻松使用服务器提供的HTTP APIs.

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

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

运行 TodoApp.HttpApi.Host 项目后,在 angular 文件夹中打开命令行终端,然后键入以下命令:

abp generate-proxy

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

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 APIs,就像我们将在下一节中做的那样.

home.component.ts

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

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

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

  todoItems: TodoItemDto[];
  newTodoText: string;

  constructor(
      private todoService: TodoService,
      private toasterService: 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('Deleted the todo item.');
    });
  }  
}

我们已经实现了使用 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">TODO LIST</div>
    </div>
    <div class="card-body">
      <!-- FORM FOR NEW TODO ITEMS -->
      <form class="form-inline" (ngSubmit)="create()">
        <input
          name="NewTodoText"
          type="text"
          [(ngModel)]="newTodoText"
          class="form-control mr-2"
          placeholder="enter text..."
        />
        <button type="submit" class="btn btn-primary">Submit</button>
      </form>

      <!-- TODO ITEMS LIST -->
      <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;
}

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

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

{{end}}

总结

在本教程中,我们构建了一个非常简单的应用程序来抢先探究ABP框架的新特性.如果你想构建一个实际场景的应用程序,请查看 应用程序开发教程,其中涵盖了实际web应用程序开发的所有方面.

源代码

你可以在这里获取到完整的应用程序源代码.

另请参见