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

ASP.NET Core 6中ViewModel在清洁架构中的分层位置及实现疑问

清洁架构中ViewModel的定位与代码修正

一、核心结论:ViewModel属于表现层(UI层)

清洁架构的核心是依赖倒置:内层(领域层、用例层)不依赖外层(接口适配层、表现层),所有依赖方向必须指向核心领域。ViewModel是为UI展示量身定制的对象,和具体的UI框架(比如MVC的View)强绑定,属于表现层范畴,绝对不能放在领域层同级的类库中——这会导致用例层(内层)依赖表现层对象,直接打破依赖规则。

二、你的代码问题分析

你当前的实现中,用例层的FeatureCommandHandler直接返回SomeViewModel,这是典型的架构违规:用例层不应该知道任何表现层的细节(包括ViewModel)。用例层的职责是处理业务逻辑,返回的应该是与UI无关的业务输出对象(比如领域模型、用例专属DTO),而非面向View的ViewModel。

三、修正后的实现方案

1. 用例层:返回业务输出DTO

// 用例层的命令定义,返回业务输出对象
public class FeatureCommand : IRequest<SomeUseCaseOutput>
{
}

// 用例专属输出DTO,仅包含业务相关数据,与UI无关
public class SomeUseCaseOutput
{
    public int Id { get; set; }
    public string BusinessName { get; set; }
    // 其他业务逻辑需要的字段
}

// 用例Handler,专注业务逻辑,返回业务输出
public class FeatureCommandHandler : IRequestHandler<FeatureCommand, SomeUseCaseOutput>
{
    private readonly IMapper _mapper;
    private readonly ISomeRepo _someRepo;

    // 修正构造函数命名错误
    public FeatureCommandHandler(IMapper mapper, ISomeRepo repo)
    {
        _mapper = mapper;
        _someRepo = repo;
    }

    public async Task<SomeUseCaseOutput> Handle(FeatureCommand request, CancellationToken cancellationToken)
    {
        var domainEntity = await _someRepo.GetFromDBAsync(cancellationToken);
        return _mapper.Map<SomeUseCaseOutput>(domainEntity);
    }
}

2. 表现层:负责转换为ViewModel

// 表现层的Controller,处理UI请求与ViewModel转换
public class FeatureController : BaseController
{
    private readonly IMediator _mediator;
    private readonly IMapper _mapper;

    public FeatureController(IMediator mediator, IMapper mapper)
    {
        _mediator = mediator;
        _mapper = mapper;
    }

    public async Task<IActionResult> Method1()
    {
        var useCaseOutput = await _mediator.Send(new FeatureCommand());
        // 在表现层完成业务输出到ViewModel的转换
        var vm = _mapper.Map<SomeViewModel>(useCaseOutput);
        return View(vm);
    }
}

// ViewModel放在表现层项目中,完全适配View的展示需求
public class SomeViewModel
{
    public int ItemId { get; set; } // 可根据View需求重命名字段
    public string DisplayName { get; set; }
    // 可添加UI交互相关属性(比如是否选中、提示文本等)
}

四、为什么要这么做?

  1. 符合依赖规则:表现层依赖用例层,而非反过来,确保内层(业务逻辑)不受UI变化影响。
  2. 业务与UI解耦:如果后续UI更换框架(比如从MVC换成Blazor),或者调整展示需求,只需要修改表现层的ViewModel和转换逻辑,用例层完全无需改动。
  3. 职责单一:用例层专注处理业务,表现层专注处理UI适配,各层职责清晰。

内容的提问来源于stack exchange,提问作者Eduwow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 22:38:20