DTO 基类的设计
ABP 提供了标准的 DTO 基类,看其源码继承关系:
csharp
// framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/
// 单个实体输出
public abstract class EntityDto<TKey> : EntityDto, IEntityDto<TKey>
{
public TKey Id { get; set; }
}
// 分页响应
public class PagedResultDto<T> : ListResultDto<T>, IPagedResult<T>
{
public long TotalCount { get; set; }
public PagedResultDto(long totalCount, IReadOnlyList<T> items) : base(items)
{
TotalCount = totalCount;
}
}
// 分页请求参数
public class PagedAndSortedResultRequestDto : PagedResultRequestDto, IPagedAndSortedResultRequest
{
public virtual string? Sorting { get; set; }
}
public class LimitedResultRequestDto : ILimitedResultRequest, IValidatableObject
{
public static int DefaultMaxResultCount { get; set; } = 10; // 默认每页 10 条
public static int MaxMaxResultCount { get; set; } = 1000; // 最大 1000 条
[Range(1, int.MaxValue)]
public virtual int MaxResultCount { get; set; } = DefaultMaxResultCount;
}
LimitedResultRequestDto 自带验证逻辑,当 MaxResultCount > 1000 时会返回验证错误。这个限制可以有效防止恶意请求拉取全量数据。
AutoMapper 集成源码
ABP 的 AutoMapper 集成在 Volo.Abp.AutoMapper 包中。核心是 AbpAutoMapperModule:
csharp
[DependsOn(typeof(AbpObjectMappingModule), typeof(AbpObjectExtendingModule))]
public class AbpAutoMapperModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper();
context.Services.AddSingleton<IConfigurationProvider>(sp =>
{
var options = sp.GetRequiredService<IOptions<AbpAutoMapperOptions>>().Value;
// 执行所有配置器
foreach (var configurator in options.Configurators)
{
configurator(autoMapperConfigurationContext);
}
return new MapperConfiguration(mapperConfigurationExpression);
});
}
}
AbpAutoMapperOptions 支持两种注册 Profile 的方式:
csharp
// 方式 1:通过 AbpAutoMapperOptions 配置
Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<BookProfile>();
options.AddProfile<UserProfile>();
});
// 方式 2:通过 ASP.NET Core 的 AddAutoMapper 扩展
context.Services.AddAutoMapper(typeof(BookProfile), typeof(UserProfile));
Mapperly 方案
Mapperly 是 ABP 提供的另一种映射方案,通过源生成器在编译时生成映射代码:
csharp
// 定义映射
[Mapper]
public static partial class BookMapper
{
public static partial BookDto MapToDto(Book book);
}
// 注入 ObjectMapper 后使用
ObjectMapper.Map<Book, BookDto>(book);
与 AutoMapper 的关键区别:
| AutoMapper | Mapperly | |
|---|---|---|
| 映射时机 | 运行时(反射) | 编译时(源生成器) |
| 性能 | 首次需创建映射配置 | 零反射,直接方法调用 |
| 使用方式 | Profile 配置 | 静态方法标注 |
| 适用场景 | 复杂映射、现有项目 | 性能敏感、新项目 |
实战:Profile 配置的最佳实践
csharp
// 1. 命名实体和 DTO 一一对应
CreateMap<Book, BookDto>(); // 按属性名自动匹配
// 2. 明确忽略不应由客户端传入的字段
CreateMap<CreateBookDto, Book>()
.ForMember(x => x.Id, opt => opt.Ignore()) // ID 由服务端生成
.ForMember(x => x.CreationTime, opt => opt.Ignore()) // 时间由服务端设置
.ForMember(x => x.TenantId, opt => opt.Ignore()); // 租户由上下文注入
// 3. 敏感字段不出现在 DTO 中
public class UserDto : EntityDto<Guid>
{
public string UserName { get; set; }
// PasswordHash 不在这里,自动忽略
}
使用注意事项
csharp
// 一般不建议把实体直接返回
[HttpGet("{id}")]
public async Task<User> GetUser(Guid id) // ❌ 直接暴露实体
{
return await _userRepo.GetAsync(id);
}
// 使用 DTO
[HttpGet("{id}")]
public async Task<UserDto> GetUser(Guid id) // ✅ 使用 DTO
{
var user = await _userRepo.GetAsync(id);
return ObjectMapper.Map<User, UserDto>(user);
}