Ready-to-use base classes for CRUD and read-only services with automatic DTO mapping, pagination, and repository integration. Coordinates between domain layer and presentation layer.
public class ProductAppService : CrudAppService<Product, ProductDto, Guid>
{
public ProductAppService(
IServiceProvider serviceProvider,
IRepository<Product, Guid> repository)
: base(serviceProvider, repository)
{
}
// Inherits: GetAsync, GetListAsync, CreateAsync, UpdateAsync, DeleteAsync
}services.AddAetherApplication();
services.AddScoped<IProductAppService, ProductAppService>();
// Mapper setup
services.AddAetherAutoMapperMapper(new List<Type> { typeof(ApplicationMappingProfile) });// Single DTO for all operations
public class CategoryAppService : CrudAppService<Category, CategoryDto, Guid>
{
}
// Separate DTOs
public class OrderAppService : CrudAppService<
Order, // Entity
OrderDto, // Get DTO
Guid, // Key
PagedAndSortedResultRequestDto, // List input
CreateOrderDto, // Create DTO
UpdateOrderDto> // Update DTO
{
}public class ProductQueryService : ReadOnlyAppService<Product, ProductDto, Guid, ProductFilterDto>
{
public ProductQueryService(
IServiceProvider serviceProvider,
IReadOnlyRepository<Product, Guid> repository)
: base(serviceProvider, repository)
{
}
}public class CategoryService : CrudEntityAppService<Category, Guid>
{
// Works directly with entities
}public class ProductDto : EntityDto<Guid>
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class CreateProductDto
{
[Required]
public string Name { get; set; }
[Range(0.01, double.MaxValue)]
public decimal Price { get; set; }
}
public class ProductFilterDto : PagedAndSortedResultRequestDto
{
public string? Category { get; set; }
public decimal? MinPrice { get; set; }
}public class OrderAppService : CrudAppService<Order, OrderDto, Guid, CreateOrderDto, UpdateOrderDto>
{
protected override Task<Order> MapToEntityAsync(CreateOrderDto input)
{
var order = new Order(input.CustomerName);
foreach (var item in input.Items)
{
order.AddItem(item.ProductId, item.Quantity);
}
return Task.FromResult(order);
}
}public class ProductAppService : CrudAppService<Product, ProductDto, Guid, ProductFilterDto>
{
protected override async Task<IQueryable<Product>> CreateFilteredQueryAsync(ProductFilterDto input)
{
var query = await Repository.GetQueryableAsync();
if (!string.IsNullOrEmpty(input.Category))
query = query.Where(p => p.Category == input.Category);
if (input.MinPrice.HasValue)
query = query.Where(p => p.Price >= input.MinPrice.Value);
return query;
}
}public class OrderAppService : CrudAppService<Order, OrderDto, Guid>
{
[UnitOfWork]
public async Task<OrderDto> PlaceOrderAsync(Guid id)
{
var order = await Repository.GetAsync(id);
order.PlaceOrder(); // Domain logic
await Repository.UpdateAsync(order);
return await MapToGetOutputDtoAsync(order);
}
}public class ApplicationMappingProfile : Profile
{
public ApplicationMappingProfile()
{
CreateMap<Product, ProductDto>();
CreateMap<CreateProductDto, Product>();
CreateMap<Order, OrderDto>()
.ForMember(d => d.Items, opt => opt.MapFrom(s => s.Items));
}
}- Keep services thin - Domain logic belongs in entities, not services
- Use appropriate service type - CrudAppService for full CRUD, ReadOnly for queries
- Separate DTOs by operation - CreateDto, UpdateDto, GetDto for clarity
- Override only what you need - Base classes handle common operations
- Repository Pattern - Data access
- Object Mapping - DTO mapping
- Unit of Work - Transaction management