Master ABP Framework patterns including repository pattern, unit of work, domain services, application services, authorization, multi-tenancy, background jobs, and distributed events. Use when: (1) building ABP-based applications with DDD architecture, (2) creating CRUD services with Entity, AppService, DTOs, validators, (3) handling authorization/permissions, (4) generating ABP module code.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add mattnigh/skills_collection --skill collection --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Collection?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mattnigh-collection-d3d66cc8)More formats (shields.io, HTML) on the badges page.
---
name: abp-framework-patterns
description: "Master ABP Framework patterns including repository pattern, unit of work, domain services, application services, authorization, multi-tenancy, background jobs, and distributed events. Use when: (1) building ABP-based applications with DDD architecture, (2) creating CRUD services with Entity, AppService, DTOs, validators, (3) handling authorization/permissions, (4) generating ABP module code."
layer: 2
tech_stack: [dotnet, csharp, abp, efcore]
topics: [entity, appservice, dto, repository, mapping, permissions, domain-service, background-jobs]
depends_on: [csharp-advanced-patterns, dotnet-async-patterns]
complements: [efcore-patterns, fluentvalidation-patterns, openiddict-authorization]
keywords: [Entity, AppService, DTO, Mapperly, Repository, UnitOfWork, IRepository, ApplicationService, CrudAppService]
---
# ABP Framework Patterns
Master ABP Framework patterns for building maintainable, scalable applications following Domain-Driven Design principles.
**This skill orchestrates three focused skills:**
| Skill | Focus | Key Patterns |
|-------|-------|--------------|
| `abp-entity-patterns` | Domain layer | Entity, Repository, DomainService, DataSeeding |
| `abp-service-patterns` | Application layer | AppService, DTOs, Mapperly, UoW, Filter DTOs |
| `abp-infrastructure-patterns` | Cross-cutting | Permissions, BackgroundJobs, Events, Multi-tenancy |
## Quick Reference
### Architecture Layers
```
Domain.Shared → Constants, enums, shared types
Domain → Entities, repositories, domain services, domain events
Application.Contracts → DTOs, application service interfaces
Application → Application services, mapper profiles, validators
EntityFrameworkCore → DbContext, repository implementations
HttpApi → Controllers (auto-generated by ABP)
HttpApi.Host → Startup, configuration
```
### Common Patterns
| Pattern | Location | When to Use |
|---------|----------|-------------|
| Entity | Domain | Business data with identity |
| Repository | Domain + EFC | Custom data access queries |
| Domain Service | Domain | Cross-entity business logic |
| AppService | Application | Orchestration, authorization, mapping |
| CrudAppService | Application | Simple CRUD without custom logic |
| Validator | Application | Input DTO validation |
| Permission | Domain.Shared | Access control |
| Background Job | Application | Async/delayed processing |
| Distributed Event | Domain/App | Module decoupling |
## Entity Patterns
### Standard Entity with Encapsulation
```csharp
public class Patient : FullAuditedAggregateRoot<Guid>
{
public string FirstName { get; private set; } = string.Empty;
public string LastName { get; private set; } = string.Empty;
public string Email { get; private set; } = string.Empty;
public bool IsActive { get; private set; } = true;
// Required for EF Core
protected Patient() { }
public Patient(Guid id, string firstName, string lastName, string email) : base(id)
{
SetName(firstName, lastName);
SetEmail(email);
}
public void SetName(string firstName, string lastName)
{
FirstName = Check.NotNullOrWhiteSpace(firstName, nameof(firstName), maxLength: PatientConsts.MaxFirstNameLength);
LastName = Check.NotNullOrWhiteSpace(lastName, nameof(lastName), maxLength: PatientConsts.MaxLastNameLength);
}
public void SetEmail(string email)
{
Email = Check.NotNullOrWhiteSpace(email, nameof(email), maxLength: PatientConsts.MaxEmailLength);
}
public void Activate() => IsActive = true;
public void Deactivate() => IsActive = false;
}
```
### Constants Class
```csharp
// Domain.Shared/{Feature}/{Entity}Consts.cs
namespace {ProjectName}.{Feature};
public static class PatientConsts
{
public const int MaxFirstNameLength = 100;
public const int MaxLastNameLength = 100;
public const int MaxEmailLength = 256;
}
```
## AppService Patterns
### Standard AppService (Recommended)
```csharp
[Authorize({ProjectName}Permissions.Patients.Default)]
public class PatientAppService : ApplicationService, IPatientAppService
{
private readonly IRepository<Patient, Guid> _patientRepository;
public PatientAppService(IRepository<Patient, Guid> patientRepository)
{
_patientRepository = patientRepository;
}
public async Task<PagedResultDto<PatientDto>> GetListAsync(GetPatientsInput input)
{
var queryable = await _patientRepository.GetQueryableAsync();
var query = queryable
.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
x => x.FirstName.Contains(input.Filter!) || x.LastName.Contains(input.Filter!))
.WhereIf(input.IsActive.HasValue, x => x.IsActive == input.IsActive);
var totalCount = await AsyncExecuter.CountAsync(query);
var patients = await AsyncExecuter.ToListAsync(
query
.OrderBy(input.Sorting.IsNullOrWhiteSpace() ? nameof(Patient.LastName) : input.Sorting)
.PageBy(input));
return new PagedResultDto<PatientDto>(totalCount, patients.ToDto());
}
public async Task<PatientDto> GetAsync(Guid id)
{
var patient = await _patientRepository.GetAsync(id);
return patient.ToDto();
}
[Authorize({ProjectName}Permissions.Patients.Create)]
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
var patient = new Patient(
GuidGenerator.Create(),
input.FirstName,
input.LastName,
input.Email);
await _patientRepository.InsertAsync(patient, autoSave: true);
return patient.ToDto();
}
[Authorize({ProjectName}Permissions.Patients.Edit)]
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input)
{
var patient = await _patientRepository.GetAsync(id);
patient.SetName(input.FirstName, input.LastName);
patient.SetEmail(input.Email);
await _patientRepository.UpdateAsync(patient, autoSave: true);
return patient.ToDto();
}
[Authorize({ProjectName}Permissions.Patients.Delete)]
public async Task DeleteAsync(Guid id)
{
await _patientRepository.DeleteAsync(id);
}
}
```
### CrudAppService Base (For Simple CRUD)
```csharp
// Use when no custom business logic is needed
public class PatientAppService : CrudAppService<
Patient, // Entity
PatientDto, // Output DTO
Guid, // Primary key
GetPatientsInput, // GetList input
CreatePatientDto, // Create input
UpdatePatientDto>, // Update input
IPatientAppService
{
public PatientAppService(IRepository<Patient, Guid> repository)
: base(repository)
{
}
// Override specific methods if needed
protected override async Task<IQueryable<Patient>> CreateFilteredQueryAsync(GetPatientsInput input)
{
var query = await base.CreateFilteredQueryAsync(input);
return query
.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
x => x.FirstName.Contains(input.Filter!));
}
}
```
## Mapperly Patterns
### Static Extension Methods (RECOMMENDED)
```csharp
// Application/{Feature}/{Entity}Mappers.cs
using Riok.Mapperly.Abstractions;
namespace {ProjectName}.{Feature};
[Mapper]
public static partial class PatientMappers
{
// Single entity mapping
public static partial PatientDto ToDto(this Patient patient);
// Collection mapping
public static partial List<PatientDto> ToDto(this List<Patient> patients);
// IEnumerable for LINQ
public static partial IEnumerable<PatientDto> ToDto(this IEnumerable<Patient> patients);
// Update entity from DTO (ignores Id)
[MapperIgnoreTarget(nameof(Patient.Id))]
public static partial void UpdateFrom(this Patient patient, UpdatePatientDto dto);
}
// Usage in AppService (no injection needed):
return patient.ToDto();
return patients.ToDto().ToList();
```
### Instance Mapper (Alternative)
```csharp
// Only if DI is required (e.g., for resolving navigation properties)
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.None)]
public partial class PatientApplicationMappers
{
public partial PatientDto Map(Patient patient);
public partial List<PatientDto> MapList(List<Patient> patients);
}
// Register in Module:
context.Services.AddSingleton<PatientApplicationMappers>();
```
## Permission Patterns
### Permission Constants
```csharp
// Application.Contracts/Permissions/{Entity}Permissions.cs
namespace {ProjectName}.Permissions;
public static class PatientPermissions
{
public const string GroupName = "{ProjectName}.Patients";
public const string Default = GroupName;
public const string Create = GroupName + ".Create";
public const string Edit = GroupName + ".Edit";
public const string Delete = GroupName + ".Delete";
}
```
### Permission Definition Provider
```csharp
public class {ProjectName}PermissionDefinitionProvider : PermissionDefinitionProvider
{
public override void Define(IPermissionDefinitionContext context)
{
var myGroup = context.AddGroup({ProjectName}Permissions.GroupName);
var patientsPermission = myGroup.AddPermission(
PatientPermissions.Default,
L("Permission:Patients"));
patientsPermission.AddChild(
PatientPermissions.Create,
L("Permission:Patients.Create"));
patientsPermission.AddChild(
PatientPermissions.Edit,
L("Permission:Patients.Edit"));
patientsPermission.AddChild(
PatientPermissions.Delete,
L("Permission:Patients.Delete"));
}
private static LocalizableString L(string name) =>
LocalizableString.Create<{ProjectName}Resource>(name);
}
```
## Filter DTO Pattern
### Standard Filter DTO
```csharp
using Volo.Abp.Application.Dtos;
namespace {ProjectName}.{Feature};
public class GetPatientsInput : PagedAndSortedResultRequestDto
{
// Text search filter
public string? Filter { get; set; }
// Status filter
public bool? IsActive { get; set; }
// Foreign key filter
public Guid? DoctorId { get; set; }
// Date range filters
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
}
```
### WhereIf Extension Usage
```csharp
var query = queryable
.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
x => x.FirstName.Contains(input.Filter!) ||
x.LastName.Contains(input.Filter!) ||
x.Email.Contains(input.Filter!))
.WhereIf(input.IsActive.HasValue, x => x.IsActive == input.IsActive)
.WhereIf(input.DoctorId.HasValue, x => x.DoctorId == input.DoctorId)
.WhereIf(input.FromDate.HasValue, x => x.CreationTime >= input.FromDate)
.WhereIf(input.ToDate.HasValue, x => x.CreationTime <= input.ToDate);
```
## Best Practices
### Do's
1. **Entity encapsulation** - Use private setters and domain methods
2. **Thin AppServices** - Orchestrate, don't implement business logic
3. **Domain Services** - For cross-entity business rules
4. **Static Mappers** - Use extension methods for cleaner code
5. **WhereIf pattern** - Clean optional filtering
6. **Permission checks** - Class-level `[Authorize]` + method-level for mutations
7. **autoSave: true** - Use for single operations (avoid extra SaveChanges)
8. **Constants classes** - Define max lengths in Domain.Shared
### Don'ts
1. **Don't duplicate authorization** - If class has `[Authorize]`, methods with same permission don't need it
2. **Don't inject mappers** - Use static extension methods instead
3. **Don't check null after WhereIf** - The `.HasValue` check handles it
4. **Don't use AutoMapper** - Use Mapperly for source generation
5. **Don't expose entities** - Always return DTOs
## Code Quality Checklist
Before completing implementation:
- [ ] Entity has private setters and domain methods
- [ ] Entity has parameterless protected constructor for EF Core
- [ ] AppService class has `[Authorize(Permission.Default)]`
- [ ] All mutations have specific `[Authorize(Permission.Action)]`
- [ ] Mapper uses static extension methods pattern
- [ ] Filter DTO extends `PagedAndSortedResultRequestDto`
- [ ] Filter uses WhereIf pattern for optional parameters
- [ ] Constants defined in Domain.Shared
- [ ] Permissions follow `{Project}.{Resource}.{Action}` pattern
- [ ] InsertAsync uses `autoSave: true` for single operations
## Detailed Reference
For comprehensive patterns, see the focused skills:
- **`abp-entity-patterns`** - Entity base classes, repositories, domain services, data seeding
- **`abp-service-patterns`** - AppServices, DTOs, Mapperly, UoW, Filter DTOs, CommonDependencies
- **`abp-infrastructure-patterns`** - Permissions, background jobs, distributed events, multi-tenancy
- **`abp-contract-scaffolding`** - Interface and DTO generation for parallel workflows
## External Resources
- **ABP Documentation**: https://docs.abp.io/
- **ABP Community**: https://community.abp.io/
- **ABP GitHub**: https://github.com/abpframework/abp
- **Mapperly**: https://mapperly.riok.app/
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!