整洁架构下.NET Blazor应用的模型与实体处理方案咨询
根据整洁架构的核心依赖规则:所有依赖必须指向内层(领域层→应用层→外层),内层绝不能依赖外层,你的问题核心是要解决应用层与展示层的耦合问题,同时保证领域实体的封装性。下面是具体的规范处理方式:
不推荐:将展示层模型移到应用层
展示层的ViewModel(你说的WebUI层模型)是和UI绑定的细节(比如Blazor组件的字段验证、显示格式),属于外层实现细节。如果把它移到应用层,会导致应用层依赖UI细节,违反整洁架构的依赖规则——以后UI需求变更(比如修改字段名、新增显示属性),会直接影响应用层,破坏了业务逻辑与UI的解耦。
推荐:应用层定义DTO,展示层负责映射到ViewModel
这是符合整洁架构规范的标准做法,具体流程如下:
应用层定义DTO(数据传输对象)
DTO是应用层对外暴露的数据契约,只包含业务逻辑需要传递的核心数据,与UI展示细节无关。应用层的服务接口直接返回DTO,这样应用层不需要依赖任何外层组件,完全符合依赖方向。展示层(WebUI)定义ViewModel
ViewModel是专门为Blazor组件设计的模型,包含UI所需的所有细节(比如显示名称、验证规则、绑定属性)。展示层负责将应用层返回的DTO映射为ViewModel,这个映射可以用AutoMapper简化,也可以手动实现。领域实体仅在内部使用
领域层的实体只在领域层和应用层内部流转,绝不暴露到展示层,保证领域逻辑的封装性,避免UI层意外修改实体状态或依赖实体内部的业务细节。
代码示例
领域层实体
// 领域层/Entities/SomeEntity.cs public class SomeEntity { public int Id { get; set; } public string Name { get; set; } public string InternalBusinessField { get; set; } // 无需暴露给UI的业务字段 }
应用层DTO
// 应用层/Dtos/SomeDto.cs public class SomeDto { public int Id { get; set; } public string Name { get; set; } // 仅包含需要传递给UI的核心数据 }
应用层服务接口与实现
// 应用层/Interfaces/ISomeService.cs public interface ISomeService { Task<SomeDto> GetSomeByIdAsync(int id); } // 应用层/Services/SomeService.cs public class SomeService : ISomeService { private readonly IRepository<SomeEntity> _entityRepository; private readonly IMapper _mapper; public SomeService(IRepository<SomeEntity> entityRepository, IMapper mapper) { _entityRepository = entityRepository; _mapper = mapper; } public async Task<SomeDto> GetSomeByIdAsync(int id) { var entity = await _entityRepository.GetByIdAsync(id); return _mapper.Map<SomeDto>(entity); } }
展示层ViewModel与映射
// WebUI/ViewModels/SomeViewModel.cs public class SomeViewModel { public int Id { get; set; } [Display(Name = "商品名称")] public string DisplayName { get; set; } // UI专属的显示字段 } // WebUI/MappingProfiles/SomeMappingProfile.cs public class SomeMappingProfile : Profile { public SomeMappingProfile() { CreateMap<SomeDto, SomeViewModel>() .ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => $"[{src.Id}] {src.Name}")); } } // Blazor组件中使用 @inject ISomeService SomeService @inject IMapper Mapper <div>@_model.DisplayName</div> @code { private SomeViewModel _model; protected override async Task OnInitializedAsync() { var dto = await SomeService.GetSomeByIdAsync(1); _model = Mapper.Map<SomeViewModel>(dto); } }
折中简化(个人项目可选)
如果你的个人项目中ViewModel和DTO字段几乎完全一致,不想写重复代码,可以让ViewModel继承DTO,但要注意:一旦UI需求出现差异化(比如新增UI专属字段),必须立即拆分,避免长期耦合。
内容的提问来源于stack exchange,提问作者maaax

