myesn

myEsn2E9

hi
github

ABP More Detailed Quick Start - Creating Solutions Using Layered Architecture

Introduction#

This article aims to provide a CRUD RESTful API for a single entity using the ABP framework, which is a solution without a UI layer.

If you need web application development, please refer to this: https://docs.abp.io/en/abp/latest/Tutorials/Part-1

Prerequisites#

  • .NET 7+
  • Node.js v16+ (as it uses some default page implementations provided by Angular, such as login/register, tenant and user management)

Install ABP CLI Tool#

Since we will use the ABP CLI to create a new ABP solution, we need to globally install the following tool beforehand:

dotnet tool install -g Volo.Abp.Cli

Create Your ABP Solution#

Open the terminal in an empty directory and execute the following command to create a layered solution named TodoApp:

The command specifies using the app template, without any default front-end UI, using EF Core (default) for ORM, MySQL (default is MS SQL) for the database, and specifies the database connection string.

abp new TodoApp --template app --ui none --database-provider ef --database-management-system MySQL --connection-string "Server=localhost;Database=TodoApp;Uid=root;Pwd=123456;"

The layered architecture and dependencies of the app template are as follows. It adopts the design pattern of Domain Driven Design. For more explanations about project structure and responsibilities, please refer to:
image

The new command defaults to using --template app --ui mvc, which means using the Application Startup Template and selecting the ASP.NET Core MVC framework to provide the default UI implementation. ABP provides a total of 6 Startup Templates, which are: app, app-nolayers, module, console, WPF, MAUI.

After specifying --ui none, there is a default behavior of creating a aspnet-core subdirectory in the current directory, which can be handled as desired.

You can append the --output-folder or --create-solution-folder parameters after the new command to specify the output directory or whether to create a solution folder.

It is recommended to learn more about the parameters of the new command in ABP CLI, and the official documentation also provides some examples for reference.

If you encounter the prompt Find the following template in your cache directory during creation, it indicates that your local machine cannot retrieve the latest version of the ABP Startup Template through the network. The ABP CLI will then look for it in your local cache. Regardless of whether there is one or multiple caches, you need to specify the desired version number using --version x.x.x. The source code for this logic can be referenced, and the disk directory for the local cache of Startup Template can be referenced, which is the %USERPROFILE%\.abp\templates directory.

Create the Database#

Open the terminal in the TodoApp.DbMigrator project directory and execute the dotnet run command to create the database:
image

If you want to change the database after creating the project, refer to.

Run the Application#

ABP officially recommends running the TodoApp.Web project once before development to ensure that the project can compile and run normally. However, since we used the --ui none parameter when creating the solution, there will be no .Web project. So here, I open the terminal in the TodoApp.HttpApi.Host directory and execute the dotnet run command.

Finally, access https://localhost:44398 through the browser to verify whether it can run normally. ABP will generate a default user when creating the database, with the username admin and password 1q2w3E*.

For Swagger integration, such as hiding the default ABP interfaces or enabling OAUTH authentication for them, refer to. If the default ABP interfaces are hidden, you will find that the Default Endpoints in Swagger are indeed gone, but the Schemas below are still there. This is actually the expected effect, refer to.

Everything is ready, and you can start coding next.

Domain Layer#

This is the Domain Layer in the solution, which mainly contains entities, aggregate roots, domain services, value objects, repository interfaces, and other domain objects.

Create a TodoItem class in the TodoApp.Domain project, which is an entity (Entity) that maps to a relational database table:

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

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

[BasicAggregateRoot](https://docs.abp.io/en/abp/latest/Entities#basicaggregateroot-class) is the simplest base class for creating root entities (used when there is no need to handle concurrency conflicts and additional extension properties), with the generic parameter Guid representing the primary key (Id) of the entity.

Database Integration#

Next, configure Entity Framework Core.

Mapping Configuration#

Open the TodoAppDbContex class in the TodoApp.EntityFrameworkCore/EntityFrameworkCore directory and add a DbSet property:

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

Then, add the mapping (database table) code for the TodoItem entity in the OnModelCreating function of that class:

using Humanizer;
...

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>(builder =>
    {
        builder.ToTable($"{TodoAppConsts.DbTablePrefix}TodoItem".Pluralize());
    });
}

The [Pluralize](https://github.com/Humanizr/Humanizer#pluralize) function comes from the Humanizer.Core.zh-CN NuGet package. It is more appropriate to install it in the TodoApp.Domain project because it is the lowest-level project that other projects depend on. Its function is to "pluralize the provided input considering irregular words and uncountable nouns," thus avoiding writing the wrong plural form of words, which is a minor detail.

Code First Migrations#

The solution we created uses EF Core's Code First Migrations. Since we changed the database mapping configuration above, we need to create a new migration and apply the changes to the database.

Open the terminal in the TodoApp.EntityFrameworkCore project directory and execute the following command:

dotnet ef migrations add Added_TodoItem

This will add a new migration class to the project:
image

Then, in the same terminal, execute the following command to apply the changes to the database:

dotnet ef database update

Check the database, and you should see that this table has been successfully created:
image

Application Layer#

An Application Service is used to execute a series of related use cases, which expose domain logic to the presentation layer and use DTO as input and output parameters (optional). It can be understood as the Service layer in a three-tier architecture. We need to execute the following use cases:

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

Application Service Interface#

First, define an interface for the application service by creating an ITodoAppService interface in the TodoApp.Application.Contracts project:

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<TodoItemDto> UpdateAsync(Guid id, string text);
        Task DeleteAsync(Guid id);
    }
}

For simplicity, the input parameters are not encapsulated into DTOs.

Data Transfer Object#

The GetListAsync, CreateAsync, and UpdateAsync functions return TodoItemDto objects. The input and output parameters of the ApplicationService are usually DTO rather than Entity, and DTO classes are generally defined in the TodoApp.Application.Contracts project. Create the TodoItemDto used above:

using System;

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

Application Service Implementation#

Create a TodoAppService class in the TodoApp.Application project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;

namespace TodoApp
{
    public class TodoAppService : TodoAppAppService, ITodoAppService
    {
        private readonly IRepository<TodoItem, Guid> _todoItemRepository;

        public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository)
        {
            _todoItemRepository = todoItemRepository;
        }

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

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

        public async Task<TodoItemDto> UpdateAsync(Guid id, string text)
        {
            var item = await _todoItemRepository.SingleOrDefaultAsync(x => x.Id.Equals(id));
            if (item == null) throw new EntityNotFoundException();

            item.Text = text;
            var newItem = await _todoItemRepository.UpdateAsync(item);

            return new TodoItemDto
            {
                Id = id,
                Text = newItem.Text
            };
        }

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

ABP provides default generic repositories for Entities.

The custom Application Service implementation class should inherit from the ABP-provided ApplicationService class, so that ABP can automatically generate API Controllers for the Application Service based on conventions, reducing redundant code (since generally, Controllers simply call the domain logic exposed in the Application Service). For more information on automatically generating API Controllers, refer to.

If you only perform simple CRUD operations on entities, you can refer to the ABP-provided CRUD Application Service to further reduce redundant code.

Of course, for type conversion between Entity and DTO, you can refer to.

Finally, run the TodoApp.HttpApi.Host project, and you will see that the CRUD Application Service implemented above has automatically generated APIs on the Swagger page.
image

Conclusion#

In summary, ABP is indeed comprehensive, similar to Java's Spring Boot and Node.js's NestJS frameworks. It has transcended the framework itself and is a more complete and mature solution. It indeed improves development efficiency, unifies code standards, and reduces handover costs. However, I recommend beginners to refer to the ASP.NET Core documentation before using the advanced version of the ABP framework.

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.