You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

整洁架构下.NET Blazor应用的模型与实体处理方案咨询

整洁架构下Blazor应用的实体/模型/DTO分层方案

根据整洁架构的核心依赖规则:所有依赖必须指向内层(领域层→应用层→外层),内层绝不能依赖外层,你的问题核心是要解决应用层与展示层的耦合问题,同时保证领域实体的封装性。下面是具体的规范处理方式:

不推荐:将展示层模型移到应用层

展示层的ViewModel(你说的WebUI层模型)是和UI绑定的细节(比如Blazor组件的字段验证、显示格式),属于外层实现细节。如果把它移到应用层,会导致应用层依赖UI细节,违反整洁架构的依赖规则——以后UI需求变更(比如修改字段名、新增显示属性),会直接影响应用层,破坏了业务逻辑与UI的解耦。

推荐:应用层定义DTO,展示层负责映射到ViewModel

这是符合整洁架构规范的标准做法,具体流程如下:

  1. 应用层定义DTO(数据传输对象)
    DTO是应用层对外暴露的数据契约,只包含业务逻辑需要传递的核心数据,与UI展示细节无关。应用层的服务接口直接返回DTO,这样应用层不需要依赖任何外层组件,完全符合依赖方向。

  2. 展示层(WebUI)定义ViewModel
    ViewModel是专门为Blazor组件设计的模型,包含UI所需的所有细节(比如显示名称、验证规则、绑定属性)。展示层负责将应用层返回的DTO映射为ViewModel,这个映射可以用AutoMapper简化,也可以手动实现。

  3. 领域实体仅在内部使用
    领域层的实体只在领域层和应用层内部流转,绝不暴露到展示层,保证领域逻辑的封装性,避免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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 06:28:12