Master backend architectures, REST API design, microservices, and server-side development across multiple languages. Use this as a central index to access specialized sub-skills.
中文简介正在准备中。当前显示原文说明。
Implement REST APIs in ABP Framework with AppServices, DTOs, pagination, filtering, and authorization. Use when building API endpoints for ABP applications.
中文简介正在准备中。当前显示原文说明。
查看安装方法安装前可以查看交给 AI 代理的指令内容。
Implement REST APIs in ABP Framework using AppServices, DTOs, pagination, filtering, and authorization. This skill focuses on C# implementation - for design principles, see api-design-principles.
For Design: Use
api-design-principlesfor API contract design decisions.
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace MyApp.Patients;
public class PatientAppService : ApplicationService, IPatientAppService
{
private readonly IRepository<Patient, Guid> _patientRepository;
public PatientAppService(IRepository<Patient, Guid> patientRepository)
{
_patientRepository = patientRepository;
}
// GET /api/app/patient/{id}
[Authorize(MyAppPermissions.Patients.Default)]
public async Task<PatientDto> GetAsync(Guid id)
{
var patient = await _patientRepository.GetAsync(id);
return ObjectMapper.Map<Patient, PatientDto>(patient);
}
// GET /api/app/patient?skipCount=0&maxResultCount=10&sorting=name&filter=john
[Authorize(MyAppPermissions.Patients.Default)]
public async Task<PagedResultDto<PatientDto>> GetListAsync(GetPatientListInput input)
{
var query = await _patientRepository.GetQueryableAsync();
// Apply filters using WhereIf pattern
query = query
.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
p => p.Name.Contains(input.Filter!) ||
p.Email.Contains(input.Filter!))
.WhereIf(input.Status.HasValue,
p => p.Status == input.Status!.Value)
.WhereIf(input.DoctorId.HasValue,
p => p.DoctorId == input.DoctorId!.Value);
// Get total count before pagination
var totalCount = await AsyncExecuter.CountAsync(query);
// Apply sorting and pagination
query = query
.OrderBy(input.Sorting.IsNullOrWhiteSpace() ? nameof(Patient.Name) : input.Sorting)
.PageBy(input);
var patients = await AsyncExecuter.ToListAsync(query);
return new PagedResultDto<PatientDto>(
totalCount,
ObjectMapper.Map<List<Patient>, List<PatientDto>>(patients)
);
}
// POST /api/app/patient
[Authorize(MyAppPermissions.Patients.Create)]
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
var patient = new Patient(
GuidGenerator.Create(),
input.Name,
input.Email,
input.DateOfBirth
);
await _patientRepository.InsertAsync(patient);
return ObjectMapper.Map<Patient, PatientDto>(patient);
}
// PUT /api/app/patient/{id}
[Authorize(MyAppPermissions.Patients.Edit)]
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input)
{
var patient = await _patientRepository.GetAsync(id);
patient.SetName(input.Name);
patient.SetEmail(input.Email);
patient.SetDateOfBirth(input.DateOfBirth);
await _patientRepository.UpdateAsync(patient);
return ObjectMapper.Map<Patient, PatientDto>(patient);
}
// DELETE /api/app/patient/{id}
[Authorize(MyAppPermissions.Patients.Delete)]
public async Task DeleteAsync(Guid id)
{
await _patientRepository.DeleteAsync(id);
}
}
Output DTO (Response):
using Volo.Abp.Application.Dtos;
namespace MyApp.Patients;
public class PatientDto : FullAuditedEntityDto<Guid>
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; }
public PatientStatus Status { get; set; }
public Guid? DoctorId { get; set; }
// Computed property
public int Age => DateTime.Today.Year - DateOfBirth.Year;
}
Create DTO (Input):
namespace MyApp.Patients;
public class CreatePatientDto
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; }
public Guid? DoctorId { get; set; }
}
Update DTO (Input):
namespace MyApp.Patients;
public class UpdatePatientDto
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime DateOfBirth { get; set; }
public PatientStatus Status { get; set; }
}
List Input DTO (Query Parameters):
using Volo.Abp.Application.Dtos;
namespace MyApp.Patients;
public class GetPatientListInput : PagedAndSortedResultRequestDto
{
// Search filter
public string? Filter { get; set; }
// Specific filters
public PatientStatus? Status { get; set; }
public Guid? DoctorId { get; set; }
public DateTime? CreatedAfter { get; set; }
public DateTime? CreatedBefore { get; set; }
}
using System.Linq.Dynamic.Core;
public async Task<PagedResultDto<PatientDto>> GetListAsync(GetPatientListInput input)
{
var query = await _patientRepository.GetQueryableAsync();
// WhereIf - only applies condition if value is not null/empty
query = query
// Text search
.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
p => p.Name.Contains(input.Filter!) ||
p.Email.Contains(input.Filter!) ||
p.PhoneNumber.Contains(input.Filter!))
// Enum filter
.WhereIf(input.Status.HasValue,
p => p.Status == input.Status!.Value)
// Foreign key filter
.WhereIf(input.DoctorId.HasValue,
p => p.DoctorId == input.DoctorId!.Value)
// Date range filter
.WhereIf(input.CreatedAfter.HasValue,
p => p.CreationTime >= input.CreatedAfter!.Value)
.WhereIf(input.CreatedBefore.HasValue,
p => p.CreationTime <= input.CreatedBefore!.Value)
// Boolean filter
.WhereIf(input.IsActive.HasValue,
p => p.IsActive == input.IsActive!.Value);
var totalCount = await AsyncExecuter.CountAsync(query);
// Dynamic sorting with System.Linq.Dynamic.Core
var sorting = input.Sorting.IsNullOrWhiteSpace()
? $"{nameof(Patient.CreationTime)} DESC"
: input.Sorting;
query = query.OrderBy(sorting).PageBy(input);
var patients = await AsyncExecuter.ToListAsync(query);
return new PagedResultDto<PatientDto>(
totalCount,
ObjectMapper.Map<List<Patient>, List<PatientDto>>(patients)
);
}
Permission-Based Authorization:
public class PatientAppService : ApplicationService, IPatientAppService
{
// Read permission
[Authorize(MyAppPermissions.Patients.Default)]
public async Task<PatientDto> GetAsync(Guid id) { ... }
// Create permission
[Authorize(MyAppPermissions.Patients.Create)]
public async Task<PatientDto> CreateAsync(CreatePatientDto input) { ... }
// Edit permission
[Authorize(MyAppPermissions.Patients.Edit)]
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input) { ... }
// Delete permission (often more restricted)
[Authorize(MyAppPermissions.Patients.Delete)]
public async Task DeleteAsync(Guid id) { ... }
}
Programmatic Authorization Check:
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input)
{
// Check permission programmatically
await AuthorizationService.CheckAsync(MyAppPermissions.Patients.Edit);
var patient = await _patientRepository.GetAsync(id);
// Resource-based authorization
if (patient.DoctorId != CurrentUser.Id)
{
await AuthorizationService.CheckAsync(MyAppPermissions.Patients.EditAny);
}
// ... update logic
}
Permission Definitions:
public static class MyAppPermissions
{
public const string GroupName = "MyApp";
public static class Patients
{
public const string Default = GroupName + ".Patients";
public const string Create = Default + ".Create";
public const string Edit = Default + ".Edit";
public const string Delete = Default + ".Delete";
public const string EditAny = Default + ".EditAny"; // Admin only
}
}
using FluentValidation;
namespace MyApp.Patients;
public class CreatePatientDtoValidator : AbstractValidator<CreatePatientDto>
{
private readonly IRepository<Patient, Guid> _patientRepository;
public CreatePatientDtoValidator(IRepository<Patient, Guid> patientRepository)
{
_patientRepository = patientRepository;
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required.")
.MaximumLength(100).WithMessage("Name cannot exceed 100 characters.");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required.")
.EmailAddress().WithMessage("Invalid email format.")
.MustAsync(BeUniqueEmail).WithMessage("Email already exists.");
RuleFor(x => x.DateOfBirth)
.NotEmpty().WithMessage("Date of birth is required.")
.LessThan(DateTime.Today).WithMessage("Date of birth must be in the past.")
.GreaterThan(DateTime.Today.AddYears(-150)).WithMessage("Invalid date of birth.");
}
private async Task<bool> BeUniqueEmail(string email, CancellationToken cancellationToken)
{
return !await _patientRepository.AnyAsync(p => p.Email == email);
}
}
public class PatientAppService : ApplicationService, IPatientAppService
{
// Custom action: POST /api/app/patient/{id}/activate
[HttpPost("{id}/activate")]
[Authorize(MyAppPermissions.Patients.Edit)]
public async Task<PatientDto> ActivateAsync(Guid id)
{
var patient = await _patientRepository.GetAsync(id);
patient.Activate();
await _patientRepository.UpdateAsync(patient);
return ObjectMapper.Map<Patient, PatientDto>(patient);
}
// Custom query: GET /api/app/patient/by-email?email=john@example.com
[HttpGet("by-email")]
[Authorize(MyAppPermissions.Patients.Default)]
public async Task<PatientDto?> GetByEmailAsync(string email)
{
var patient = await _patientRepository.FirstOrDefaultAsync(p => p.Email == email);
return patient == null ? null : ObjectMapper.Map<Patient, PatientDto>(patient);
}
// Custom query with lookup data: GET /api/app/patient/lookup
[HttpGet("lookup")]
[Authorize(MyAppPermissions.Patients.Default)]
public async Task<List<PatientLookupDto>> GetLookupAsync()
{
var patients = await _patientRepository.GetListAsync();
return patients.Select(p => new PatientLookupDto
{
Id = p.Id,
DisplayName = $"{p.Name} ({p.Email})"
}).ToList();
}
}
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace MyApp.Patients;
public interface IPatientAppService : IApplicationService
{
Task<PatientDto> GetAsync(Guid id);
Task<PagedResultDto<PatientDto>> GetListAsync(GetPatientListInput input);
Task<PatientDto> CreateAsync(CreatePatientDto input);
Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input);
Task DeleteAsync(Guid id);
// Custom methods
Task<PatientDto> ActivateAsync(Guid id);
Task<PatientDto?> GetByEmailAsync(string email);
Task<List<PatientLookupDto>> GetLookupAsync();
}
using Riok.Mapperly.Abstractions;
namespace MyApp;
[Mapper]
public static partial class ApplicationMappers
{
// Entity to DTO
public static partial PatientDto ToDto(this Patient patient);
public static partial List<PatientDto> ToDtoList(this List<Patient> patients);
// DTO to Entity (for creation)
public static partial Patient ToEntity(this CreatePatientDto dto);
// Update Entity from DTO
public static partial void UpdateFrom(this Patient patient, UpdatePatientDto dto);
}
Usage in AppService:
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
var patient = input.ToEntity();
patient.Id = GuidGenerator.Create();
await _patientRepository.InsertAsync(patient);
return patient.ToDto();
}
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input)
{
var patient = await _patientRepository.GetAsync(id);
patient.UpdateFrom(input);
await _patientRepository.UpdateAsync(patient);
return patient.ToDto();
}
Business Exception:
using Volo.Abp;
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
// Check business rule
if (await _patientRepository.AnyAsync(p => p.Email == input.Email))
{
throw new BusinessException(MyAppDomainErrorCodes.PatientEmailAlreadyExists)
.WithData("email", input.Email);
}
// ... create logic
}
Error Codes:
public static class MyAppDomainErrorCodes
{
public const string PatientEmailAlreadyExists = "MyApp:Patient:001";
public const string PatientNotActive = "MyApp:Patient:002";
public const string PatientCannotBeDeleted = "MyApp:Patient:003";
}
Localization:
{
"MyApp:Patient:001": "A patient with email '{email}' already exists.",
"MyApp:Patient:002": "Patient is not active.",
"MyApp:Patient:003": "Patient cannot be deleted because they have active appointments."
}
ABP auto-generates routes based on AppService naming:
| Method | AppService Method | Generated Route |
|---|---|---|
GetAsync(Guid id) | GET | /api/app/patient/{id} |
GetListAsync(input) | GET | /api/app/patient |
CreateAsync(input) | POST | /api/app/patient |
UpdateAsync(id, input) | PUT | /api/app/patient/{id} |
DeleteAsync(id) | DELETE | /api/app/patient/{id} |
Custom Route Override:
[RemoteService(Name = "PatientApi")]
[Route("api/v1/patients")] // Custom route
public class PatientAppService : ApplicationService, IPatientAppService
{
[HttpGet("{id:guid}")]
public async Task<PatientDto> GetAsync(Guid id) { ... }
}
| Need | Skill |
|---|---|
| API design decisions | api-design-principles |
| Response wrappers | api-response-patterns |
| Input validation | fluentvalidation-patterns |
| Entity design | abp-entity-patterns |
| Query optimization | linq-optimization-patterns |
| Authorization | openiddict-authorization |
还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
Master backend architectures, REST API design, microservices, and server-side development across multiple languages. Use this as a central index to access specialized sub-skills.
中文简介正在准备中。当前显示原文说明。
在每次回复中使用固定前缀并列出本次实际参考的指令/文件,便于校验是否遵循上下文与规则。
中文简介正在准备中。当前显示原文说明。
Automate 21risk tasks via Rube MCP (Composio). Always search tools first for current schemas.
中文简介正在准备中。当前显示原文说明。
Automate 2chat tasks via Rube MCP (Composio). Always search tools first for current schemas.
中文简介正在准备中。当前显示原文说明。
Arquitecto de Soluciones Principal y Consultor Tecnológico de Andru.ia. Diagnostica y traza la hoja de ruta óptima para proyectos de IA en español.
中文简介正在准备中。当前显示原文说明。
[00] META. Orchestrate skills into dynamic chains for complex tasks. Analyzes the task, discovers available skills, builds an optimal chain, explains why each skill is needed, and executes step-by-step with user confirmation. Use for any complex task requiring multiple thinking/research/analysis steps. Triggers on "plan this", "how to approach", "what's the strategy", "build a plan", or any multi-step problem requiring skill orchestration.
中文简介正在准备中。当前显示原文说明。