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

Clean Architecture+MediatR如何处理SelectListItem不破坏依赖链

问题背景

我正在开发一款「管理端应用」,用于管理另一款社区驱动的歌词Web应用。注册用户可提交新歌手、歌词信息,该管理端供管理员、审核员完成新提交内容审批等操作。

管理端基于Jason Taylor的Clean Architecture解决方案模板搭建。原模板前端采用Angular构建的SPA应用,我需要使用常规MVC搭配Razor视图的模式,因此移除了Client项目,修改控制器使其返回视图而非JSON数据。
前期开发进展顺利。

问题描述

我现在需要实现新增歌手功能,包含两类请求动作:初始GET请求用于加载表单,POST请求用于处理表单提交。
处理GET请求时,返回给Razor视图的视图模型需要包含SelectListItem类型属性,用于渲染性别选择下拉框。

ArtistsController.cs

[HttpGet]
public async Task<ActionResult> Create()
{
    CreateArtistGenderDto viewModel = await Mediator.Send(new CreateArtistQuery());
    
    return View(viewModel);
}

[HttpPost]
public async Task<ActionResult<int>> Create(CreateArtistCommand command)
{
    return await Mediator.Send(command);
}

CreateArtistQuery.cs

public class CreateArtistQuery : IRequest<CreateArtistGenderDto>
{
}

public class CreateArtistQueryHandler : IRequestHandler<CreateArtistQuery, CreateArtistGenderDto>
{
    public async Task<CreateArtistGenderDto> Handle(
        CreateArtistQuery request,
        CancellationToken cancellationToken)
    {
        CreateArtistGenderDto model = new CreateArtistGenderDto();

        return model;
    }
}

CreateArtistGenderDto.cs

namespace bejebeje.admin.Application.Artists.Queries.CreateArtist;

public class CreateArtistGenderDto
{
    public Gender SelectedGender { get; set; }
    
    public List<SelectListItem> GenderOptions = new List<SelectListItem>
    {
        new SelectList(Enum.GetValues(typeof(Gender))
    };
}

public enum Gender
{
    Female,
    Male,
    NotApplicable
}

当前遇到的问题是:如果遵循MediatR开发规范,将Query类放在Application项目中,该项目无法识别SelectListItem类型;如果为Application项目引入对应依赖包,就会破坏Clean Architecture原本的依赖规则。

clean architecture依赖示意图

Clean Architecture要求依赖始终向内流动,引入该依赖后会出现向外的依赖引用,违反架构设计原则,最终会导致项目结构混乱。

selectlistitem依赖问题示意图

核心诉求:在不破坏Clean Architecture依赖流向规则的前提下,正确实现下拉选项绑定需求。


解决方案

核心原则是UI层专属类型绝对不能出现在Application层,SelectListItem是ASP.NET Core MVC专属的UI渲染类型,本身就属于Presentation层的职责范畴,不要让Application层感知它的存在,有两种成熟实现方案可选:

方案1:分层定义DTO,UI层做映射(最推荐,完全符合架构规范)

  1. 修改Application层的返回DTO,只返回和业务相关的原始数据,完全移除UI专属类型:
    // Application层DTO,不依赖任何UI框架
    namespace bejebeje.admin.Application.Artists.Queries.CreateArtist;
    public class CreateArtistGenderDto
    {
        public Gender SelectedGender { get; set; }
        // 只返回业务层面的枚举值列表,不需要关心前端渲染逻辑
        public IReadOnlyList<Gender> AvailableGenders { get; set; } = Enum.GetValues<Gender>().ToList();
    }
    
    public enum Gender
    {
        Female,
        Male,
        NotApplicable
    }
    
  2. 在Web(Presentation)层定义专属视图模型,接收Application层返回的业务DTO后,在Controller中映射为视图需要的结构:
    // Web层视图模型,可正常引用SelectListItem
    namespace bejebeje.admin.Web.ViewModels.Artists;
    public class CreateArtistViewModel
    {
        public Gender SelectedGender { get; set; }
        public List<SelectListItem> GenderOptions { get; set; } = new();
    }
    
  3. 修改Controller的GET方法,完成映射逻辑:
    [HttpGet]
    public async Task<ActionResult> Create()
    {
        var businessDto = await Mediator.Send(new CreateArtistQuery());
        var viewModel = new CreateArtistViewModel
        {
            SelectedGender = businessDto.SelectedGender,
            GenderOptions = businessDto.AvailableGenders.Select(g => new SelectListItem
            {
                Text = g.ToString(),
                Value = ((int)g).ToString()
            }).ToList()
        };
        return View(viewModel);
    }
    

这种方案职责完全清晰,Application层只处理业务逻辑,和UI技术栈完全解耦,未来如果换成API返回JSON、Blazor或者其他前端框架,Application层代码不需要做任何修改。

方案2:UI层直接构造固定下拉选项(轻量场景首选)

针对性别枚举这类固定不变的选项,完全没必要通过Mediator传递选项数据,可以把构造下拉选项的逻辑完全放在UI层:

  1. Application层的DTO只保留和业务提交相关的字段即可:
    public class CreateArtistGenderDto
    {
        public Gender SelectedGender { get; set; }
    }
    
  2. 直接在Razor视图中用内置方法生成下拉选项,不需要额外传递数据:
    <select asp-for="SelectedGender" asp-items="Html.GetEnumSelectList<Gender>()" class="form-control"></select>
    

这种方案代码量最少,GetEnumSelectList是Razor内置的辅助方法,可以直接根据枚举类型生成符合要求的SelectListItem集合,不需要额外写映射逻辑。


不要为了省事给Application层引入Microsoft.AspNetCore.Mvc.ViewFeatures包(SelectListItem所在的依赖包),一旦打破分层依赖约束,后续会有越来越多UI逻辑泄露到Application层,最终Clean Architecture的分层设计会完全失效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 04:42:27