.agents/skills/abp-application-layer/SKILL.md
Docs: https://abp.io/docs/latest/framework/architecture/domain-driven-design/application-services
GetAsync not GetBookAsyncid as a separate parameter, not inside the DTOIFormFile/Stream in app service: accept byte[] from controllers insteadpublic 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);
}
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);
}
}
GetAsync not GetBookAsync)UpdateAsync explicitly (don't assume change tracking)IFormFile/Stream - pass byte[] from controllersClock, CurrentUser, GuidGenerator, L) instead of injecting these services| 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 |
*.Application.Contracts projectpublic class CreateBookDto
{
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
[Range(0, 999.99)]
public decimal Price { get; set; }
}
Before adding custom validation, decide if it's a domain rule or application rule:
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) }
);
}
}
}
public class CreateBookDtoValidator : AbstractValidator<CreateBookDto>
{
public CreateBookDtoValidator()
{
RuleFor(x => x.Name).NotEmpty().Length(3, 100);
RuleFor(x => x.Price).GreaterThan(0);
}
}
throw new BusinessException("BookStore:010001")
.WithData("BookName", name);
var book = await _bookRepository.FindAsync(id);
if (book == null)
{
throw new EntityNotFoundException(typeof(Book), id);
}
throw new UserFriendlyException(L["BookNotAvailable"]);
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 |
ABP automatically generates API controllers for application services:
IApplicationService (which already has [RemoteService] attribute)[RemoteService(false)] to disable auto API generation for specific methodsABP supports both Mapperly and AutoMapper integrations. But the default mapping library is Mapperly. You need to first check the project's active mapping library.
docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md).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.
If the solution uses AutoMapper, mappings are typically defined in Profile classes and registered via ABP's AutoMapper integration.