abp-application-layer

star 14.3k

ABP Application Services, DTOs, CRUD service, object mapping (Mapperly/AutoMapper), validation, error handling. Use when creating or reviewing application services, DTOs, or working in the Application or Application.Contracts projects.

abpframework By abpframework schedule Updated 3/9/2026

name: abp-application-layer description: ABP Application Services, DTOs, CRUD service, object mapping (Mapperly/AutoMapper), validation, error handling. Use when creating or reviewing application services, DTOs, or working in the Application or Application.Contracts projects.

ABP Application Layer Patterns

Docs: https://abp.io/docs/latest/framework/architecture/domain-driven-design/application-services

Anti-Patterns to Avoid

  • Entity name in method: use GetAsync not GetBookAsync
  • ID inside UpdateDto: pass id as a separate parameter, not inside the DTO
  • Calling other app services in the same module: use domain services or repositories directly
  • Using IFormFile/Stream in app service: accept byte[] from controllers instead
  • Business logic in app service: put it in domain entities or domain services

Application Service Structure

Interface (Application.Contracts)

public interface IBookAppService : IApplicationService
{
    Task<BookDto> GetAsync(Guid id);
    Task<PagedResultDto<BookListItemDto>> GetListAsync(GetBookListInput input);
    Task<BookDto> CreateAsync(CreateBookDto input);
    Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input);
    Task DeleteAsync(Guid id);
}

Implementation (Application)

public class BookAppService : ApplicationService, IBookAppService
{
    private readonly IBookRepository _bookRepository;
    private readonly BookManager _bookManager;
    private readonly BookMapper _bookMapper;

    public BookAppService(
        IBookRepository bookRepository,
        BookManager bookManager,
        BookMapper bookMapper)
    {
        _bookRepository = bookRepository;
        _bookManager = bookManager;
        _bookMapper = bookMapper;
    }

    public async Task<BookDto> GetAsync(Guid id)
    {
        var book = await _bookRepository.GetAsync(id);
        return _bookMapper.MapToDto(book);
    }

    [Authorize(BookStorePermissions.Books.Create)]
    public async Task<BookDto> CreateAsync(CreateBookDto input)
    {
        var book = await _bookManager.CreateAsync(input.Name, input.Price);
        await _bookRepository.InsertAsync(book);
        return _bookMapper.MapToDto(book);
    }

    [Authorize(BookStorePermissions.Books.Edit)]
    public async Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input)
    {
        var book = await _bookRepository.GetAsync(id);
        await _bookManager.ChangeNameAsync(book, input.Name);
        book.SetPrice(input.Price);
        await _bookRepository.UpdateAsync(book);
        return _bookMapper.MapToDto(book);
    }
}

Application Service Best Practices

  • Don't repeat entity name in method names (GetAsync not GetBookAsync)
  • Accept/return DTOs only, never entities
  • ID not inside UpdateDto - pass separately
  • Use custom repositories when you need custom queries, generic repository is fine for simple CRUD
  • Call UpdateAsync explicitly (don't assume change tracking)
  • Don't call other app services in same module
  • Don't use IFormFile/Stream - pass byte[] from controllers
  • Use base class properties (Clock, CurrentUser, GuidGenerator, L) instead of injecting these services

DTO Naming Conventions

Purpose Convention Example
Query input Get{Entity}Input GetBookInput
List query input Get{Entity}ListInput GetBookListInput
Create input Create{Entity}Dto CreateBookDto
Update input Update{Entity}Dto UpdateBookDto
Single entity output {Entity}Dto BookDto
List item output {Entity}ListItemDto BookListItemDto

DTO Location

  • Define DTOs in *.Application.Contracts project
  • This allows sharing with clients (Blazor, HttpApi.Client)

Validation

Data Annotations

public class CreateBookDto
{
    [Required]
    [StringLength(100, MinimumLength = 3)]
    public string Name { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }
}

Custom Validation with IValidatableObject

Before adding custom validation, decide if it's a domain rule or application rule:

  • Domain rule: Put validation in entity constructor or domain service (enforces business invariants)
  • Application rule: Use DTO validation (input format, required fields)

Only use IValidatableObject for application-level validation that can't be expressed with data annotations:

public class CreateBookDto : IValidatableObject
{
    public string Name { get; set; }
    public string Description { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Name == Description)
        {
            yield return new ValidationResult(
                "Name and Description cannot be the same!",
                new[] { nameof(Name), nameof(Description) }
            );
        }
    }
}

FluentValidation

public class CreateBookDtoValidator : AbstractValidator<CreateBookDto>
{
    public CreateBookDtoValidator()
    {
        RuleFor(x => x.Name).NotEmpty().Length(3, 100);
        RuleFor(x => x.Price).GreaterThan(0);
    }
}

Error Handling

Business Exceptions

throw new BusinessException("BookStore:010001")
    .WithData("BookName", name);

Entity Not Found

var book = await _bookRepository.FindAsync(id);
if (book == null)
{
    throw new EntityNotFoundException(typeof(Book), id);
}

User-Friendly Exceptions

throw new UserFriendlyException(L["BookNotAvailable"]);

HTTP Status Code Mapping

Status code mapping is configurable in ABP (do not rely on a fixed mapping in business logic).

Exception Typical HTTP Status
AbpValidationException 400
AbpAuthorizationException 401/403
EntityNotFoundException 404
BusinessException 403 (but configurable)
Other exceptions 500

Auto API Controllers

ABP automatically generates API controllers for application services:

  • Interface must inherit IApplicationService (which already has [RemoteService] attribute)
  • HTTP methods determined by method name prefix (Get, Create, Update, Delete)
  • Use [RemoteService(false)] to disable auto API generation for specific methods

Object Mapping (Mapperly / AutoMapper)

ABP supports both Mapperly and AutoMapper integrations. But the default mapping library is Mapperly. You need to first check the project's active mapping library.

  • Prefer the mapping provider already used in the solution (check existing mapping files / loaded modules).
  • In mixed solutions, explicitly setting the default provider may be required (see docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md).

Mapperly (compile-time)

Define mappers as partial classes:

[Mapper]
public partial class BookMapper
{
    public partial BookDto MapToDto(Book book);
    public partial List<BookDto> MapToDtoList(List<Book> books);
}

Register in module:

public override void ConfigureServices(ServiceConfigurationContext context)
{
    context.Services.AddSingleton<BookMapper>();
}

Usage in application service:

public class BookAppService : ApplicationService
{
    private readonly BookMapper _bookMapper;

    public BookAppService(BookMapper bookMapper)
    {
        _bookMapper = bookMapper;
    }

    public BookDto GetBook(Book book)
    {
        return _bookMapper.MapToDto(book);
    }
}

Note: Mapperly generates mapping code at compile-time, providing better performance than runtime mappers.

AutoMapper (runtime)

If the solution uses AutoMapper, mappings are typically defined in Profile classes and registered via ABP's AutoMapper integration.

Install via CLI
npx skills add https://github.com/abpframework/abp --skill abp-application-layer
Repository Details
star Stars 14,313
call_split Forks 3,697
navigation Branch main
article Path SKILL.md
More from Creator
abpframework
abpframework Explore all skills →