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

UI
Database

TODO Application Tutorial with Single-Layer 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

Creating a New Solution

In this tutorial, we will use the ABP CLI to create the sample application with the ABP. You can run the following command in a command-line terminal to install the ABP CLI, if you haven't installed it yet:

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

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

abp new TodoApp -t app-nolayers -d mongodb

This will create a new solution with a single project, named TodoApp. Once the solution is ready, open it in your favorite IDE.

Before Running the Application

You can skip to the Run the Application section if you have created the solution using the ABP CLI. It automatically performs the following steps. However, sometimes these steps might need to be manually done. For example, you need to perform them if you have cloned the application code from a source control system.

Creating the Database

You can run the following command in the root directory of your project (in the same folder of the .csproj file) to create the database and seed the initial data:

dotnet run --migrate-database

This command will create the database and seed the initial data for you. You could also execute the migrate-database.ps1 script that is included in the root folder of the solution.

Installing the Client-Side Packages

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. Running the application is pretty straight-forward, you can run the application with any IDE that supports .NET or by running the dotnet run CLI command in the directory of your project:

todo-ui-initial

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

All right. We can start coding!

Defining the Entity

This application will have a single entity and we can start by creating it. So, create a new TodoItem class under the Entities folder of the project:

using Volo.Abp.Domain.Entities;

namespace TodoApp.Entities;

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

The next step is to setup the MongoDB configuration. Open the TodoAppDbContext class (under the Data folder) in your project and make the following changes:

  1. Add a new property to the class:
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>();
  1. Add the following code inside the CreateModel method:
modelBuilder.Entity<TodoItem>(b =>
{
    b.CollectionName = "TodoItems";
});

After the database integrations, now we can start to create application service methods and implement our use-cases.

Creating the Application Service

An application service is used to perform the use cases of the application. We need to perform the following use cases in this application:

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

Before starting to implement these use cases, first we need to create a DTO class that will be used in the application service.

Creating the Data Transfer Object (DTO)

Application services typically get and return DTOs (Data Transfer Objects) instead of entities. So, create a new TodoItemDto class under the Services/Dtos folder:

namespace TodoApp.Services.Dtos;

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

This is a very simple DTO class that has the same properties as the TodoItem entity. Now, we are ready to implement our use-cases.

The Application Service Implementation

Create a TodoAppService class under the Services folder of your project, as shown below:

using TodoApp.Services;
using TodoApp.Services.Dtos;
using TodoApp.Entities;
using Volo.Abp.Application.Services;
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: Implement the methods here...
}

This class inherits from the TodoAppAppService, which inherits from the ApplicationService class of the ABP and implements our use-cases. 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 our use cases.

Getting the Todo Items

Let's start by implementing the GetListAsync method, which is used to get a list of todo items:

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 TodoItem list from the repository, mapping them to the TodoItemDto objects and returning as the result.

Creating a New Todo Item

The 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. So, let's implement it.

User Interface

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

Index.cshtml.cs

Open the Index.cshtml.cs file in the Pages folder and replace the content with the following code block:

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

This class uses TodoAppService to get the list of todo items and assign the TodoItems property. We will use it to render the todo items on the razor page.

Index.cshtml

Open the Index.cshtml file in the Pages folder and replace it with the following content:

@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>
                TODO LIST
            </abp-card-title>
        </abp-card-header>
        <abp-card-body>            
            <!-- FORM FOR NEW TODO ITEMS -->
            <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="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">
                @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>

We are using ABP's card tag helper to create a simple card view. You could directly use the standard bootstrap HTML structure, however the ABP tag helpers make it much easier and type safe.

This page imports a CSS and a JavaScript file, so we should also create them.

Index.cshtml.js

Open the Index.cshtml.js file in the Pages folder and replace with the following content:

$(function () {
    
    // DELETING ITEMS /////////////////////////////////////////
    $('#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('Deleted the todo item.');
        });
    });
    
    // CREATING NEW ITEMS /////////////////////////////////////
    $('#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('');
        });
    });
});

In the first part, we subscribed to the click events of the trash icons near the todo items, deleted the related item on the server and showed a notification on the UI. Also, we removed the deleted item from the DOM, so we wouldn't need to refresh the page.

In the second part, we created a new todo item on the server. If it succeeded, we would then manipulate the DOM to insert a new <li> element to the todo list. This way, we wouldn't need to refresh the whole page after creating a new todo item.

The interesting part here is how we communicate with the server. See the Dynamic JavaScript Proxies & Auto API Controllers section to understand how it works. But now, let's continue and complete the application.

Index.cshtml.css

As for the final touch, open the Index.cshtml.css file in the Pages folder and replace with the following content:

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

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

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

Dynamic JavaScript Proxies & Auto API Controllers

In the Index.cshtml.js file, we've used the todoApp.services.todo.delete(...) and todoApp.services.todo.create(...) functions to communicate with the server. These functions are dynamically created by the ABP, thanks to the Dynamic JavaScript Client Proxy system. They perform HTTP API calls to the server and return a promise, so you can register a callback to the then function as we've done above.

services keyword comes from the namespace (namespace TodoApp.Services;). It's a naming convention.

However, you may notice that we haven't created any API Controllers, so how does the server handle these requests? This question brings us to the Auto API Controller feature of the ABP. It automatically converts the application services to API Controllers by convention.

If you open Swagger UI by entering the /swagger URL in your application, you can see the Todo API:

todo-api

Conclusion

In this tutorial, we've built a very simple application to warm up with the ABP.

Source Code

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

See Also

Contributors


Last updated: August 15, 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

Building Modular Monolith Applications Using .NET and ABP Framework

17 Oct, 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