abp-api-implementation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseABP API Implementation
ABP API 实现
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在ABP Framework中使用AppServices、DTOs、分页、筛选和授权实现REST API。本技能聚焦于C#实现 - 若需了解设计原则,请参考。
api-design-principlesWhen to Use This Skill
何时使用此技能
- Implementing REST API endpoints in ABP AppServices
- Creating paginated and filtered list endpoints
- Setting up authorization on API endpoints
- Designing DTOs for API requests/responses
- Handling API errors and validation
- 在ABP AppServices中实现REST API端点
- 创建支持分页和筛选的列表端点
- 为API端点配置授权
- 为API请求/响应设计DTOs
- 处理API错误与验证
Audience
受众
- ABP Developers - API implementation
- Backend Developers - .NET/C# patterns
For Design: Usefor API contract design decisions.api-design-principles
- ABP开发者 - API实现
- 后端开发者 - .NET/C#模式
设计相关:API契约设计决策请使用。api-design-principles
Core Patterns
核心模式
1. AppService with Full CRUD
1. 包含完整CRUD的AppService
csharp
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);
}
}csharp
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);
}
}2. DTO Patterns
2. DTO模式
Output DTO (Response):
csharp
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):
csharp
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):
csharp
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):
csharp
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; }
}输出DTO(响应):
csharp
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;
}创建DTO(输入):
csharp
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; }
}更新DTO(输入):
csharp
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; }
}列表输入DTO(查询参数):
csharp
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; }
}3. WhereIf Pattern for Filtering
3. 用于筛选的WhereIf模式
csharp
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)
);
}csharp
using System.Linq.Dynamic.Core;
public async Task<PagedResultDto<PatientDto>> GetListAsync(GetPatientListInput input)
{
var query = await _patientRepository.GetQueryableAsync();
// WhereIf - 仅当值不为null/空时应用条件
query = query
// 文本搜索
.WhereIf(!input.Filter.IsNullOrWhiteSpace(),
p => p.Name.Contains(input.Filter!) ||
p.Email.Contains(input.Filter!) ||
p.PhoneNumber.Contains(input.Filter!))
// 枚举筛选
.WhereIf(input.Status.HasValue,
p => p.Status == input.Status!.Value)
// 外键筛选
.WhereIf(input.DoctorId.HasValue,
p => p.DoctorId == input.DoctorId!.Value)
// 日期范围筛选
.WhereIf(input.CreatedAfter.HasValue,
p => p.CreationTime >= input.CreatedAfter!.Value)
.WhereIf(input.CreatedBefore.HasValue,
p => p.CreationTime <= input.CreatedBefore!.Value)
// 布尔筛选
.WhereIf(input.IsActive.HasValue,
p => p.IsActive == input.IsActive!.Value);
var totalCount = await AsyncExecuter.CountAsync(query);
// 使用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)
);
}4. Authorization Patterns
4. 授权模式
Permission-Based Authorization:
csharp
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:
csharp
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:
csharp
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
}
}基于权限的授权:
csharp
public class PatientAppService : ApplicationService, IPatientAppService
{
// 读取权限
[Authorize(MyAppPermissions.Patients.Default)]
public async Task<PatientDto> GetAsync(Guid id) { ... }
// 创建权限
[Authorize(MyAppPermissions.Patients.Create)]
public async Task<PatientDto> CreateAsync(CreatePatientDto input) { ... }
// 编辑权限
[Authorize(MyAppPermissions.Patients.Edit)]
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input) { ... }
// 删除权限(通常限制更严格)
[Authorize(MyAppPermissions.Patients.Delete)]
public async Task DeleteAsync(Guid id) { ... }
}程序化授权检查:
csharp
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input)
{
// 程序化检查权限
await AuthorizationService.CheckAsync(MyAppPermissions.Patients.Edit);
var patient = await _patientRepository.GetAsync(id);
// 基于资源的授权
if (patient.DoctorId != CurrentUser.Id)
{
await AuthorizationService.CheckAsync(MyAppPermissions.Patients.EditAny);
}
// ... 更新逻辑
}权限定义:
csharp
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"; // 仅管理员可用
}
}5. Validation with FluentValidation
5. 使用FluentValidation进行验证
csharp
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);
}
}csharp
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("姓名为必填项。")
.MaximumLength(100).WithMessage("姓名长度不能超过100个字符。");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("邮箱为必填项。")
.EmailAddress().WithMessage("邮箱格式无效。")
.MustAsync(BeUniqueEmail).WithMessage("该邮箱已存在。");
RuleFor(x => x.DateOfBirth)
.NotEmpty().WithMessage("出生日期为必填项。")
.LessThan(DateTime.Today).WithMessage("出生日期必须是过去的日期。")
.GreaterThan(DateTime.Today.AddYears(-150)).WithMessage("出生日期无效。");
}
private async Task<bool> BeUniqueEmail(string email, CancellationToken cancellationToken)
{
return !await _patientRepository.AnyAsync(p => p.Email == email);
}
}6. Custom Endpoints
6. 自定义端点
csharp
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();
}
}csharp
public class PatientAppService : ApplicationService, IPatientAppService
{
// 自定义操作: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);
}
// 自定义查询: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);
}
// 带查找数据的自定义查询: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();
}
}7. Interface Definition (Application.Contracts)
7. 接口定义(Application.Contracts)
csharp
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();
}csharp
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);
// 自定义方法
Task<PatientDto> ActivateAsync(Guid id);
Task<PatientDto?> GetByEmailAsync(string email);
Task<List<PatientLookupDto>> GetLookupAsync();
}Mapperly Configuration
Mapperly配置
csharp
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:
csharp
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();
}csharp
using Riok.Mapperly.Abstractions;
namespace MyApp;
[Mapper]
public static partial class ApplicationMappers
{
// 实体转DTO
public static partial PatientDto ToDto(this Patient patient);
public static partial List<PatientDto> ToDtoList(this List<Patient> patients);
// DTO转实体(用于创建)
public static partial Patient ToEntity(this CreatePatientDto dto);
// 从DTO更新实体
public static partial void UpdateFrom(this Patient patient, UpdatePatientDto dto);
}在AppService中使用:
csharp
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();
}Error Handling
错误处理
Business Exception:
csharp
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:
csharp
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:
json
{
"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."
}业务异常:
csharp
using Volo.Abp;
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
// 检查业务规则
if (await _patientRepository.AnyAsync(p => p.Email == input.Email))
{
throw new BusinessException(MyAppDomainErrorCodes.PatientEmailAlreadyExists)
.WithData("email", input.Email);
}
// ... 创建逻辑
}错误码:
csharp
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";
}本地化:
json
{
"MyApp:Patient:001": "邮箱为'{email}'的患者已存在。",
"MyApp:Patient:002": "患者未激活。",
"MyApp:Patient:003": "该患者存在有效预约,无法删除。"
}API Routes
API路由
ABP auto-generates routes based on AppService naming:
| Method | AppService Method | Generated Route |
|---|---|---|
| GET | |
| GET | |
| POST | |
| PUT | |
| DELETE | |
Custom Route Override:
csharp
[RemoteService(Name = "PatientApi")]
[Route("api/v1/patients")] // Custom route
public class PatientAppService : ApplicationService, IPatientAppService
{
[HttpGet("{id:guid}")]
public async Task<PatientDto> GetAsync(Guid id) { ... }
}ABP会根据AppService命名自动生成路由:
| 方法 | AppService方法 | 生成的路由 |
|---|---|---|
| GET | |
| GET | |
| POST | |
| PUT | |
| DELETE | |
自定义路由覆盖:
csharp
[RemoteService(Name = "PatientApi")]
[Route("api/v1/patients")] // 自定义路由
public class PatientAppService : ApplicationService, IPatientAppService
{
[HttpGet("{id:guid}")]
public async Task<PatientDto> GetAsync(Guid id) { ... }
}Integration with Other Skills
与其他技能的集成
| Need | Skill |
|---|---|
| API design decisions | |
| Response wrappers | |
| Input validation | |
| Entity design | |
| Query optimization | |
| Authorization | |
| 需求 | 技能 |
|---|---|
| API设计决策 | |
| 响应包装器 | |
| 输入验证 | |
| 实体设计 | |
| 查询优化 | |
| 授权 | |