| name | application-services |
| description | Writes or reviews an ABP application service — the DTO-taking, DTO-returning boundary between the domain and the presentation/API layer. USE FOR: implementing IApplicationService/ApplicationService for non-CRUD use cases, using AbstractKeyCrudAppService for composite or non-standard keys, and unit-of-work basics. DO NOT USE FOR: exposing application services as auto/conventional API controllers, routes, verbs or [RemoteService] (use expose-http-apis); a complete standard-key CrudAppService workflow (use build-crud-application-services); detailed object-to-DTO mapping mechanics/profiles (use map-objects-and-dtos); DTO validation attributes and business exceptions (use handle-validation-and-errors); permission definitions and authorization policy (use permissions-and-authorization); the overall layered project structure (use layered-architecture).
|
| license | MIT |
ABP Application Services
Application services implement use cases. They take and return DTOs (never entities), orchestrate domain objects and repositories, and are the boundary between the domain and the presentation/API layer.
When to Use
- Implementing an application service (interface in
*.Application.Contracts, class in *.Application).
- Using
AbstractKeyCrudAppService when standard repository key lookup is not available.
- Understanding the ambient unit of work around app service methods.
When Not to Use
- Object-to-DTO mapping mechanics (AutoMapper profiles, Mapperly, custom mappers) — use map-objects-and-dtos; here mapping appears only as
ObjectMapper.Map usage.
- A complete standard-key
CrudAppService workflow — use build-crud-application-services for contract overloads, policies, filtering, sorting, paging, mapping paths, and operation validation.
- DTO validation attributes and business/error exceptions — use handle-validation-and-errors; this skill only notes that validation runs automatically.
- Permission definitions and authorization policy — use permissions-and-authorization;
[Authorize] here is just wiring.
- Overall layered project structure — use layered-architecture.
Base class & interface
Define the interface in *.Application.Contracts and the implementation in *.Application. Implement IApplicationService (namespace Volo.Abp.Application.Services) on the interface and derive from ApplicationService (same namespace) in the class:
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync(Guid id);
Task<BookDto> CreateAsync(CreateBookDto input);
}
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IRepository<Book, Guid> _bookRepository;
public BookAppService(IRepository<Book, Guid> bookRepository)
=> _bookRepository = bookRepository;
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return ObjectMapper.Map<Book, BookDto>(book);
}
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = await _bookRepository.InsertAsync(
new Book(GuidGenerator.Create(), input.Name));
return ObjectMapper.Map<Book, BookDto>(book);
}
}
The ApplicationService base gives you injected members via LazyServiceProvider: ObjectMapper, GuidGenerator, CurrentUser, CurrentTenant, Logger, L (localization), AuthorizationService, etc.
DTOs & object mapping
ObjectMapper is an IObjectMapper (namespace Volo.Abp.ObjectMapping). Key methods:
TDestination Map<TSource, TDestination>(TSource source);
TDestination Map<TSource, TDestination>(TSource source, TDestination destination);
The default implementation (DefaultObjectMapper) delegates to a replaceable IAutoObjectMappingProvider, so the actual mapping is provided by the registered AutoMapper or Mapperly provider — the default app template registers Mapperly (AddMapperlyObjectMapper). One option is AutoMapper: register mappings in an AutoMapper Profile and add it in your module's ConfigureServices:
public class MyApplicationAutoMapperProfile : Profile
{
public MyApplicationAutoMapperProfile()
{
CreateMap<Book, BookDto>();
}
}
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<MyApplicationModule>();
});
CRUD services
Use build-crud-application-services for the complete standard-key ICrudAppService / CrudAppService contract, implementation, authorization, mapping, filtering, sorting, paging, and validation workflow.
AbstractKeyCrudAppService<...> has the same generic overloads and is the variant to use when the entity does not derive from IEntity<TKey> (e.g. composite/non-standard keys). The only members you must implement are the abstract GetEntityByIdAsync and DeleteByIdAsync (key resolution). The mapping methods (MapToEntityAsync, MapToGetOutputDtoAsync, etc.) are virtual with default object-mapping implementations — override them only when you need custom mapping.
Input validation
Input DTOs are validated automatically because ApplicationService implements IValidationEnabled (namespace Volo.Abp.Validation). Use standard data annotations on DTO properties:
public class CreateBookDto
{
[Required]
[StringLength(128)]
public string Name { get; set; }
[Range(typeof(decimal), "0", "999.99")]
public decimal Price { get; set; }
}
Invalid input throws AbpValidationException → HTTP 400 before your method body runs. Any class implementing IValidationEnabled gets this behavior via the validation interceptor.
Authorization
Apply [Authorize] at class or method level; combine with permissions defined in *.Application.Contracts. You can also call CurrentUser / AuthorizationService for imperative checks:
[Authorize(MyPermissions.Books.Default)]
public class BookAppService : ApplicationService, IBookAppService
{
[Authorize(MyPermissions.Books.Create)]
public async Task<BookDto> CreateAsync(CreateBookDto input) { }
}
Exposing the service over HTTP
ABP can expose an application service as a REST API controller automatically, with no controller class — configuring ConventionalControllers.Create, the route/verb conventions, and [RemoteService] toggles is the expose-http-apis skill's job. Write the application service here; publish it there.
Unit of work basics
Every app service method runs inside an ambient unit of work by default (ApplicationService implements IUnitOfWorkEnabled) — repository changes are saved and the DB transaction commits/rolls back around the method automatically, so you rarely call SaveChanges manually.
Use [UnitOfWork] (namespace Volo.Abp.Uow) to tune behavior (e.g. transactional flag, isolation) or to make a normally non-UoW method transactional:
[UnitOfWork(isTransactional: true)]
public async Task DoWorkAsync() { }
If you need the current UoW imperatively, inject IUnitOfWorkManager and use Current / Begin.
Validation
- Confirm the service resolves and returns DTOs (never entities) — the
*.Application.Contracts interface should reference only DTOs.
- After registering the app service assembly via
ConventionalControllers.Create, confirm the auto-generated REST endpoints appear (e.g. in Swagger) with the expected verbs (GetAsync → GET, CreateAsync → POST, etc.).
- Post an invalid DTO and confirm an
AbpValidationException → HTTP 400 before the method body runs.
- Confirm repository changes persist without a manual
SaveChanges (the ambient UoW commits around the method).
Common Pitfalls
- Application services take and return DTOs, never entities — leaking entities across the boundary defeats the layering.
- Use build-crud-application-services for standard-key CRUD instead of reconstructing its contract and pipeline here.
- Use
AbstractKeyCrudAppService (not CrudAppService) when the entity does not derive from IEntity<TKey> (composite/non-standard keys); you must implement GetEntityByIdAsync and DeleteByIdAsync.
- Validation only fires for classes implementing
IValidationEnabled via the interceptor — ApplicationService already does, so validation is automatic there.
- Every app service method runs in an ambient UoW by default, so avoid manual
SaveChanges — but "ambient UoW" doesn't mean "DB transaction": with the default Auto mode, only HTTP GET requests are non-transactional; all other verbs (including QUERY) open a transaction unless a URL is listed in NonTransactionalUrls. Use [UnitOfWork] to tune behavior.