🎯

abp-service-patterns

🎯Skill

from thapaliyabikendra/ai-artifacts

VibeIndex|
What it does

Enables rapid development of ABP application services with standardized patterns for creating, mapping, filtering, and managing DTOs using Mapperly and common service implementations.

abp-service-patterns

Installation

Install skill:
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill abp-service-patterns
1
AddedJan 27, 2026

Skill Details

SKILL.md

"ABP Framework application layer patterns including AppServices, DTOs, Mapperly mapping, Unit of Work, and common patterns like Filter DTOs and ResponseModel. Use when: (1) creating AppServices, (2) mapping DTOs with Mapperly, (3) implementing list filtering, (4) wrapping API responses."

Overview

# ABP Service Patterns

Application layer patterns for ABP Framework.

Application Service Pattern

```csharp

public class PatientAppService : ApplicationService, IPatientAppService

{

private readonly IRepository _patientRepository;

private readonly PatientManager _patientManager; // Domain service

private readonly ClinicApplicationMappers _mapper;

public PatientAppService(

IRepository patientRepository,

PatientManager patientManager,

ClinicApplicationMappers mapper)

{

_patientRepository = patientRepository;

_patientManager = patientManager;

_mapper = mapper;

}

[Authorize(ClinicPermissions.Patients.Default)]

public async Task GetAsync(Guid id)

{

var patient = await _patientRepository.GetAsync(id);

return _mapper.PatientToDto(patient);

}

[Authorize(ClinicPermissions.Patients.Create)]

public async Task CreateAsync(CreatePatientDto input)

{

var patient = await _patientManager.CreateAsync(

input.FirstName, input.LastName, input.Email, input.DateOfBirth);

return _mapper.PatientToDto(patient);

}

[Authorize(ClinicPermissions.Patients.Edit)]

public async Task UpdateAsync(Guid id, UpdatePatientDto input)

{

var patient = await _patientRepository.GetAsync(id);

_mapper.UpdatePatientFromDto(input, patient);

await _patientRepository.UpdateAsync(patient);

return _mapper.PatientToDto(patient);

}

[Authorize(ClinicPermissions.Patients.Delete)]

public async Task DeleteAsync(Guid id)

{

await _patientRepository.DeleteAsync(id);

}

}

```

Object Mapping with Mapperly

ABP 10.x uses Mapperly (source generator) instead of AutoMapper.

```csharp

// Application/ClinicApplicationMappers.cs

[Mapper]

public partial class ClinicApplicationMappers

{

// Entity to DTO

public partial PatientDto PatientToDto(Patient patient);

public partial List PatientsToDtos(List patients);

// DTO to Entity (creation)

public partial Patient CreateDtoToPatient(CreatePatientDto dto);

// DTO to Entity (update) - ignores Id

[MapperIgnoreTarget(nameof(Patient.Id))]

public partial void UpdatePatientFromDto(UpdatePatientDto dto, Patient patient);

// Complex mapping with navigation properties

[MapProperty(nameof(Appointment.Patient.FirstName), nameof(AppointmentDto.PatientName))]

[MapProperty(nameof(Appointment.Doctor.FullName), nameof(AppointmentDto.DoctorName))]

public partial AppointmentDto AppointmentToDto(Appointment appointment);

}

```

Register in Module:

```csharp

public override void ConfigureServices(ServiceConfigurationContext context)

{

context.Services.AddSingleton();

}

```

Unit of Work

ABP automatically manages UoW for application service methods.

```csharp

public class AppointmentAppService : ApplicationService

{

// This method is automatically wrapped in a UoW

// All changes are committed together or rolled back on exception

public async Task CreateAsync(CreateAppointmentDto input)

{

var patient = await _patientRepository.GetAsync(input.PatientId);

patient.LastAppointmentDate = input.AppointmentDate;

var appointment = new Appointment(

GuidGenerator.Create(),

input.PatientId,

input.DoctorId,

input.AppointmentDate);

await _appointmentRepository.InsertAsync(appointment);

// Both changes committed together automatically

return _mapper.AppointmentToDto(appointment);

}

}

```

Manual UoW Control:

```csharp

[UnitOfWork(isTransactional: false)] // Disable for read-only

public async Task GenerateLargeReportAsync() { }

public async Task ProcessBatchAsync(List ids)

{

foreach (var id in ids)

{

using (var uow = _unitOfWorkManager.Begin(requiresNew: true))

{

await ProcessItemAsync(id);

await uow.CompleteAsync();

}

}

}

```

Filter DTO Pattern

Separate query filters from pagination for clean, self-documenting APIs.

Filter DTO:

```csharp

public class PatientFilter

{

public Guid? DoctorId { get; set; }

public string? Name { get; set; }

public string? Email { get; set; }

public bool? IsActive { get; set; }

public DateTime? CreatedAfter { get; set; }

public DateTime? CreatedBefore { get; set; }

}

```

AppService with WhereIf:

```csharp

public async Task> GetListAsync(

PagedAndSortedResultRequestDto input,

PatientFilter filter)

{

// Trim string inputs

filter.Name = filter.Name?.Trim();

filter.Email = filter.Email?.Trim();

// Default sorting

if (input.Sorting.IsNullOrWhiteSpace())

input.Sorting = nameof(PatientDto.FirstName);

var queryable = await _patientRepository.GetQueryableAsync();

var query = queryable

.WhereIf(filter.DoctorId.HasValue, x => x.DoctorId == filter.DoctorId)

.WhereIf(!filter.Name.IsNullOrWhiteSpace(),

x => x.FirstName.Contains(filter.Name) || x.LastName.Contains(filter.Name))

.WhereIf(!filter.Email.IsNullOrWhiteSpace(),

x => x.Email.ToLower().Contains(filter.Email.ToLower()))

.WhereIf(filter.IsActive.HasValue, x => x.IsActive == filter.IsActive)

.WhereIf(filter.CreatedAfter.HasValue, x => x.CreationTime >= filter.CreatedAfter)

.WhereIf(filter.CreatedBefore.HasValue, x => x.CreationTime <= filter.CreatedBefore);

var totalCount = await AsyncExecuter.CountAsync(query);

var patients = await AsyncExecuter.ToListAsync(

query.OrderBy(input.Sorting).PageBy(input.SkipCount, input.MaxResultCount));

return new PagedResultDto(totalCount, _mapper.PatientsToDtos(patients));

}

```

ResponseModel Wrapper

```csharp

public class ResponseModel

{

public bool IsSuccess { get; set; }

public T Data { get; set; }

public string Message { get; set; }

public static ResponseModel Success(T data, string message = null)

=> new() { IsSuccess = true, Data = data, Message = message };

public static ResponseModel Failure(string message)

=> new() { IsSuccess = false, Message = message };

}

// Usage

public async Task> GetAsync(Guid id)

{

var patient = await _patientRepository.FirstOrDefaultAsync(x => x.Id == id);

if (patient == null)

return ResponseModel.Failure("Patient not found");

return ResponseModel.Success(_mapper.PatientToDto(patient));

}

```

CommonDependencies Pattern

Reduce constructor bloat by grouping cross-cutting dependencies.

```csharp

public class CommonDependencies

{

public IDistributedEventBus DistributedEventBus { get; set; }

public IDataFilter DataFilter { get; set; }

public ILogger Logger { get; set; }

public IGuidGenerator GuidGenerator { get; set; }

}

// Register

context.Services.AddTransient(typeof(CommonDependencies<>));

// Usage

public class PatientAppService : ApplicationService

{

private readonly IRepository _patientRepository;

private readonly CommonDependencies _common;

public PatientAppService(

IRepository patientRepository,

CommonDependencies common)

{

_patientRepository = patientRepository;

_common = common;

}

public async Task CreateAsync(CreatePatientDto input)

{

_common.Logger.LogInformation("Creating patient: {Name}", input.FirstName);

var patient = new Patient(_common.GuidGenerator.Create(), /.../);

await _patientRepository.InsertAsync(patient);

await _common.DistributedEventBus.PublishAsync(new PatientCreatedEto { Id = patient.Id });

return _mapper.PatientToDto(patient);

}

}

```

Structured Logging

```csharp

public async Task CreateAsync(CreatePatientDto input)

{

_logger.LogInformation(

"[{Service}] {Method} - Started - Input: {@Input}",

nameof(PatientAppService), nameof(CreateAsync), input);

try

{

var patient = await _patientManager.CreateAsync(/.../);

_logger.LogInformation(

"[{Service}] {Method} - Completed - PatientId: {PatientId}",

nameof(PatientAppService), nameof(CreateAsync), patient.Id);

return _mapper.PatientToDto(patient);

}

catch (Exception ex)

{

_logger.LogError(ex,

"[{Service}] {Method} - Failed - Error: {Message}",

nameof(PatientAppService), nameof(CreateAsync), ex.Message);

throw;

}

}

```

Input Sanitization

```csharp

public static class InputSanitization

{

public static string TrimAndLower(this string value) => value?.Trim()?.ToLowerInvariant();

public static string TrimAndUpper(this string value) => value?.Trim()?.ToUpperInvariant();

}

// Usage

public async Task CreateAsync(CreatePatientDto input)

{

input.Email = input.Email.TrimAndLower();

input.FirstName = input.FirstName?.Trim();

// ...

}

```

Mapping Validation Patterns

Common Bug: Copy-Paste Property Mapping

Manual mappings (especially in select new clauses) are prone to copy-paste errors:

```csharp

// ❌ BUG: Wrong property copied - IsPutawayCompleted mapped from wrong source!

select new LicensePlateDto()

{

IsInboundQCChecklistCompleted = lc.IsInboundQCChecklistCompleted,

IsPutawayCompleted = lc.IsInboundQCChecklistCompleted, // BUG! Should be lc.IsPutawayCompleted

IsHold = lc.IsHold

}

// βœ… CORRECT: Use Mapperly to prevent copy-paste errors

[Mapper]

public partial class LicensePlateMapper

{

public partial LicensePlateDto ToDto(LicensePlate entity);

}

// Or if manual mapping is required, double-check similar-named properties

select new LicensePlateDto()

{

IsInboundQCChecklistCompleted = lc.IsInboundQCChecklistCompleted,

IsPutawayCompleted = lc.IsPutawayCompleted, // βœ… Correct property

IsHold = lc.IsHold

}

```

Manual Mapping Checklist

When manual mapping is unavoidable (e.g., complex projections), verify:

  • [ ] Each DTO property maps to the correct entity property
  • [ ] Similar-named properties double-checked (e.g., IsXxxCompleted vs IsYyyCompleted)
  • [ ] Null checks on optional navigation properties
  • [ ] No copy-paste from adjacent lines without modification

High-Risk Property Patterns

Be extra careful with these patterns that look similar:

| DTO Property | Wrong Source | Correct Source |

|--------------|--------------|----------------|

| IsPutawayCompleted | entity.IsInboundCompleted | entity.IsPutawayCompleted |

| UpdatedAt | entity.CreatedAt | entity.LastModificationTime |

| CustomerName | entity.ShipperName | entity.CustomerName |

| TargetDate | entity.SourceDate | entity.TargetDate |

Best Practices

  1. Thin AppServices - Orchestrate, don't implement business logic
  2. Delegate to Domain - Use domain services for complex rules
  3. Use Mapperly - Source-generated mapping for performance (prevents copy-paste bugs)
  4. WhereIf pattern - Clean optional filtering
  5. Structured logging - Consistent format for tracing
  6. Input sanitization - Trim and normalize inputs
  7. Authorization - Always check permissions
  8. Verify manual mappings - Double-check similar-named property assignments

Related Skills

  • abp-entity-patterns - Domain layer patterns
  • abp-infrastructure-patterns - Cross-cutting concerns
  • fluentvalidation-patterns - Input validation

More from this repository10

🎯
clean-code-dotnet🎯Skill

Provides clean code guidelines and refactoring techniques for C#/.NET, focusing on improving code readability, maintainability, and adherence to SOLID principles.

🎯
efcore-patterns🎯Skill

Configures and optimizes Entity Framework Core patterns for ABP Framework, focusing on entity configuration, migrations, and relationship design with PostgreSQL.

🎯
abp-api-implementation🎯Skill

Implements comprehensive REST APIs in ABP Framework with robust AppServices, DTOs, pagination, filtering, and authorization for .NET applications.

🎯
abp-entity-patterns🎯Skill

Implements domain layer patterns for ABP Framework, providing robust entity, aggregate, repository, and domain service implementations following DDD principles.

🎯
fluentvalidation-patterns🎯Skill

Validates input DTOs in ABP Framework using FluentValidation with async checks, conditional rules, custom validators, and localized error messages.

🎯
abp-infrastructure-patterns🎯Skill

abp-infrastructure-patterns skill from thapaliyabikendra/ai-artifacts

🎯
content-retrieval🎯Skill

content-retrieval skill from thapaliyabikendra/ai-artifacts

🎯
system-design-patterns🎯Skill

Designs scalable, reliable distributed systems by applying proven architectural patterns and evaluating trade-offs across performance, consistency, and availability.

🎯
abp-contract-scaffolding🎯Skill

Generates ABP Application.Contracts layer scaffolding, enabling parallel development by creating standardized interfaces, DTOs, and permissions for .NET microservices.

🎯
openiddict-authorization🎯Skill

Implements permission-based OAuth 2.0 authorization for ABP Framework using OpenIddict, enabling fine-grained access control and multi-tenant security.