Starts in:
1 DAY
19 HRS
15 MIN
55 SEC
Starts in:
1 D
19 H
15 M
55 S

There are multiple versions of this document. Pick the options that suit you best.

UI
Database

TODO Application Tutorial with Layered Solution

This is a single-part quick-start tutorial to build a simple todo application with the ABP. Here's a screenshot from the final application:

todo-list

You can find the source code of the completed application here.

This documentation has a video tutorial on YouTube!! You can watch it here:

Pre-Requirements

Install ABP CLI Tool

We will use the ABP CLI to create new ABP solutions. You can run the following command on a terminal window to install this dotnet tool:

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

Create Your ABP Solution

Create an empty folder, open a command-line terminal and execute the following command in the terminal:

abp new TodoApp -u angular

This will create a new solution, named TodoApp with angular and aspnet-core folders. Once the solution is ready, open the ASP.NET Core solution in your favorite IDE.

Create the Database

If you are using Visual Studio, right click on the TodoApp.DbMigrator project, select Set as StartUp Project, then hit Ctrl+F5 to run it without debugging. It will create the initial database and seed the initial data.

Some IDEs (e.g. Rider) may have problems for the first run since DbMigrator adds the initial migration and re-compiles the project. In this case, open a command-line terminal in the folder of the .DbMigrator project and execute the dotnet run command.

Before Running the Application

Installing the Client-Side Packages

ABP CLI runs the abp install-libs command behind the scenes to install the required NPM packages for your solution while creating the application.

However, sometimes this command might need to be manually run. For example, you need to run this command, if you have cloned the application, or the resources from node_modules folder didn't copy to wwwroot/libs folder, or if you have added a new client-side package dependency to your solution.

For such cases, run the abp install-libs command on the root directory of your solution to install all required NPM packages:

abp install-libs

We suggest you install Yarn to prevent possible package inconsistencies, if you haven't installed it yet.

Run the Application

It is good to run the application before starting the development. The solution has two main applications:

  • TodoApp.HttpApi.Host (in the .NET solution) host the server-side HTTP API.
  • angular folder contains the Angular application.

Ensure that the TodoApp.HttpApi.Host project is the startup project, then run the application (Ctrl+F5 in Visual Studio) to see the server-side HTTP API on the Swagger UI:

todo-swagger-ui-initial

You can explore and test your HTTP API with this UI. If it works, we can run the Angular client application.

You can run the application using the following command:

npm start

This command takes time, but eventually runs and opens the application in your default browser:

todo-ui-initial

You can click on the Login button, use admin as the username and 1q2w3E* as the password to login to the application.

All ready. We can start coding!

Domain Layer

This application has a single entity and we'll start by creating it. Create a new TodoItem class inside the TodoApp.Domain project:

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

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

BasicAggregateRoot is the simplest base class to create root entities, and Guid is the primary key (Id) of the entity here.

Database Integration

Next step is to setup the Entity Framework Core configuration.

Mapping Configuration

Open the TodoAppDbContext class in the EntityFrameworkCore folder of the TodoApp.EntityFrameworkCore project and add a new DbSet property to this class:

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

Then navigate to the OnModelCreating method in the TodoAppDbContext class and add the mapping code for the TodoItem entity:

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

    /* Include modules to your migration db context */

    builder.ConfigurePermissionManagement();
    ...

    /* Configure your own tables/entities inside here */
    builder.Entity<TodoItem>(b =>
    {
        b.ToTable("TodoItems");
    });
}

We've mapped the TodoItem entity to the TodoItems table in the database.

Code First Migrations

The startup solution is configured to use Entity Framework Core Code First Migrations. Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database.

Open a command-line terminal in the directory of the TodoApp.EntityFrameworkCore project and type the following command:

dotnet ef migrations add Added_TodoItem

This will add a new migration class to the project:

todo-efcore-migration

You can apply changes to the database using the following command, in the same command-line terminal:

dotnet ef database update

If you are using Visual Studio, you may want to use the Add-Migration Added_TodoItem and Update-Database commands in the Package Manager Console (PMC). In this case, ensure that TodoApp.HttpApi.Host is the startup project and TodoApp.EntityFrameworkCore is the Default Project in PMC.

Now, we can use the ABP repositories to save and retrieve the todo items, as we'll do in the next section.

Application Layer

An Application Service is used to perform the use cases of the application. We need to perform the following use cases:

  • Get the list of the todo items
  • Create a new todo item
  • Delete an existing todo item

Application Service Interface

We can start by defining an interface for the application service. Create a new ITodoAppService interface in the TodoApp.Application.Contracts project, as shown below:

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

Data Transfer Object

GetListAsync and CreateAsync methods return TodoItemDto. ApplicationService typically gets and returns DTOs (Data Transfer Objects) instead of entities. So, we should define the DTO class here. Create a new TodoItemDto class inside the TodoApp.Application.Contracts project:

using System;

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

This is a very simple DTO class that matches our TodoItem entity. We are ready to implement the ITodoAppService.

Application Service Implementation

Create a TodoAppService class inside the TodoApp.Application project, as shown below:

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...
    }
}

This class inherits from the ApplicationService class of the ABP and implements the ITodoAppService that was defined before. ABP provides default generic repositories for the entities. We can use them to perform the fundamental database operations. This class injects IRepository<TodoItem, Guid>, which is the default repository for the TodoItem entity. We will use it to implement the use cases described before.

Getting Todo Items

Let's start by implementing the GetListAsync method:

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

We are simply getting the complete TodoItem list from the database, mapping them to TodoItemDto objects and returning as the result.

Creating a New Todo Item

Next method is CreateAsync and we can implement it as shown below:

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

The repository's InsertAsync method inserts the given TodoItem to the database and returns the same TodoItem object. It also sets the Id, so we can use it on the returning object. We are simply returning a TodoItemDto by creating from the new TodoItem entity.

Deleting a Todo Item

Finally, we can implement the DeleteAsync as the following code block:

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

The application service is ready to be used from the UI layer.

User Interface Layer

It is time to show the todo items on the UI! Before starting to write the code, it would be good to remember what we are trying to build. Here's a sample screenshot from the final UI:

todo-list

We will keep the UI side minimal for this tutorial to make the tutorial simple and focused. See the web application development tutorial to build real-life pages with all aspects.

Service Proxy Generation

ABP provides a handy feature to automatically create client-side services to easily consume HTTP APIs provided by the server.

You first need to run the TodoApp.HttpApi.Host project since the proxy generator reads API definitions from the server application.

Warning: There is an issue with IIS Express: it doesn't allow connecting to the application from another process. If you are using Visual Studio, select the TodoApp.HttpApi.Host instead of IIS Express in the run button drop-down list, as shown in the figure below:

run-without-iisexpress

Once you run the TodoApp.HttpApi.Host project, open a command-line terminal in the angular folder and type the following command:

abp generate-proxy -t ng

If everything goes well, it should generate an output as shown below:

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)

We can then use todoService to use the server-side HTTP APIs, as we'll do in the next section.

home.component.ts

Open the /angular/src/app/home/home.component.ts file and replace its content with the following code block:

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.');
    });
  }  
}

We've used todoService to get the list of todo items and assigned the returning value to the todoItems array. We've also added create and delete methods. These methods will be used on the view side.

home.component.html

Open the /angular/src/app/home/home.component.html file and replace its content with the following code block:

<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="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="enter text..." />
          </div>
        </div>
        <div class="col-12">
          <button type="submit" class="btn btn-primary">Submit</button>
        </div>
      </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

As the final touch, open the /angular/src/app/home/home.component.scss file and replace its content with the following code block:

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

This is a simple styling for the todo page. We believe that you can do much better :)

Now, you can run the application again to see the result.

Conclusion

In this tutorial, we've built a very simple application to warm up for the ABP. If you are looking to build a serious application, please check the web application development tutorial which covers all the aspects of real-life web application development.

Source Code

You can find source code of the completed application here.

See Also

Contributors


Last updated: August 29, 2024 Edit this page on GitHub

Was this page helpful?

Please make a selection.

To help us improve, please share your reason for the negative feedback in the field below.

Please enter a note.

Thank you for your valuable feedback!

Please note that although we cannot respond to feedback, our team will use your comments to improve the experience.

In this document
Community Talks

What’s New with .NET 9 & ABP 9?

21 Nov, 17:00
Online
Watch the Event
Mastering ABP Framework Book
Mastering ABP Framework

This book will help you gain a complete understanding of the framework and modern web application development techniques.

Learn More