news 2026/10/24 4:25:47

Blazor 后台为什么可以不写 Controller + Service + DTO?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Blazor 后台为什么可以不写 Controller + Service + DTO?

本文对应版本:EasyAdminBlazor 2.3.0+。

源码位置:

  • EasyAdminBlazor.Core/FreeSql/Repository.cs(BasicRepository/DddRepository/RepositoryOptions)
  • EasyAdminBlazor/AdminExtensions.cs(仓储注册与审计)
  • EasyAdminBlazor/Components/AdminTable.razor(页面即入口)
  • EasyAdminBlazor.Test/Program.cs、Components/Admin/Product.razor

先立个结论:不是因为传统分层"错了",而是因为后台 CRUD 这一层被框架化了。

这篇文章想把这个结论讲透,包括"什么时候你仍然需要 Controller 和 DTO"。


一、被省略的是哪一层

先看传统 ASP.NET Core 后台的典型分层:

浏览器(JS / SPA) ↓ HTTP + JSON Controller ← 协议转换、路由、鉴权、参数校验 ↓ Service ← 业务逻辑、事务、组合调用 ↓ DTO ← 跨边界传输对象(列表 DTO / 详情 DTO / Save DTO) ↓ Repository / EF ← 数据访问 ↓ 数据库

这里面Controller 和 DTO 的存在,主要是为了跨 HTTP 边界:

  • 前端在浏览器里,后端在服务器上,两者只能通过 HTTP + JSON 通信;
  • 所以需要有东西"接收 HTTP 请求、反序列化 JSON、返回 JSON"(Controller);
  • 也需要有东西"描述跨边界的数据形状"(DTO)。

而 Blazor Server 的运行模型是这样的:

浏览器(只跑渲染 + 事件) ⇅ SignalR(框架层,自动) 服务端组件(你的 Razor 代码在这里执行) ↓ 直接方法调用 服务 / 仓储 / ORM ↓ 数据库

Razor 组件的代码本来就跑在服务器上,<AdminTable TItem="Product">和_repo.Select之间是同一个进程里的方法调用。既然没有 HTTP 边界,那一层"为了跨边界而存在"的 Controller + DTO 自然就没有存在必要。

这就是"不需要 Controller + Service + DTO"的真正原因——不是省了,而是这一层的职责消失了。


二、逐项对照:能力跑到哪里去了

传统分层里的东西它解决的问题EasyAdminBlazor 里的对应物
Controller + 路由暴露 HTTP 接口@page "/Admin/Product"指令 +AdminTable组件
Service(CRUD 部分)增删改查编排AdminTable内置流程(查询→权限→保存→审批)
Service(业务规则)校验、组合、事务OnBeforeSaveAsync/OnFinishSaveAsync/ 领域服务(仍可写)
Repository数据访问、分页IAggregateRootRepository<T>(DI 自动注册)
DTO跨边界数据形状实体本身 + 表格列(列决定"暴露哪些字段")
参数校验Controller 模型绑定 + DataAnnotationsEditContextCapture.Validate()+ 实体上的数据注解
鉴权[Authorize]/ PolicyAuthPath(路由)+AuthButton(按钮/操作)
数据权限每个查询手写过滤ApplyDataPermission+FilterAuthorizedAsync
前端请求封装(axios)浏览器→服务器 HTTP不存在(同一进程)
跨域配置前后端分离的副作用不存在

仓储注册就是三行:

builder.Services.AddScoped(typeof(IBaseRepository<>),typeof(BasicRepository<>));builder.Services.AddScoped(typeof(BaseRepository<>),typeof(BasicRepository<>));builder.Services.AddScoped(typeof(IAggregateRootRepository<>),typeof(DddRepository<>));

DddRepository的关键在于把Select指向SelectDiy:

publicclassDddRepository<TEntity>:AggregateRootRepository<TEntity>whereTEntity:class{...publicoverrideISelect<TEntity>Select=>base.SelectDiy;}

于是AdminTable拿到的ISelect<TEntity>上可以直接叠加数据权限、动态筛选、排序、分页。


三、"没有 DTO"会不会有风险

会的。两个典型风险,框架都给了对应处理:

1. 敏感字段被返回

如果直接把实体返回给前端,SysUser.Password这类字段就暴露了。代码里的处理是:

  • 表格只渲染声明的列,没在<TableColumns>里出现的字段不会进前端;
  • 导航属性上的[JsonIgnore]阻断序列化;
  • 导出时按可见列动态投影(第 04/12 篇),根本不会SELECT *。

所以"没有 DTO"不等于"整个实体到处传"——列定义就是那道边界。

2. 前端需要的数据形状与实体不一致

比如列表要显示"分类名称"而不是"分类 Id"。做法是:

<TableColumn @bind-Field="context.ClassifyId" Filterable="true"> <Template Context="v">@v.Row.Classify?.ClassifyName</Template> </TableColumn>

Include加载导航属性后,模板里取它即可。真的需要自定义形状时(例如多表聚合报表),可以用 FreeSql 投影到匿名类型或自定义类型:

varlist=awaitselect.ToListAsync(a=>new{a.Id,a.Title,a.Classify.ClassifyName});

“没有 DTO 层"不代表"不能定义 DTO”,只是不再需要为每个实体的增删改查都定义一套 DTO。


四、什么时候仍然需要 Controller 和 DTO

这一点必须说清楚,否则就成了"框架万能论"。以下场景你仍然应该写接口层:

场景为什么
小程序 / App / SPA 等前后端分离的前台前端不在服务端,必须走 HTTP
对接第三方系统(回调、开放平台)需要稳定的协议契约
需要给外部提供 OpenAPI / SDK需要 DTO 描述契约 + 版本管理
团队规范要求接口层与实现分离组织约束,与技术无关
需要独立的接口鉴权策略(API Key、OAuth Scope)与后台登录态不同的认证模型

好消息是:这些场景下,实体、ORM 与业务逻辑仍然可以复用。

演示项目就是这么做的——企业官网的前台是 Razor Pages,直接注入仓储:

publicclassProductsModel(IBaseRepository<Product>productRepository):PageModel{publicList<Product>Products{get;set;}=[];publicintTotalCount{get;set;}publicasyncTaskOnGet(intpageIndex=1,intpageSize=12){varselect=productRepository.Select.Where(x=>x.IsOnSale).OrderByDescending(x=>x.CreatedTime);TotalCount=(int)awaitselect.CountAsync();Products=awaitselect.Skip((pageIndex-1)*pageSize).Take(pageSize).ToListAsync();}}

后台用AdminTable维护同一批Product,前台用 Razor Pages 读同一批Product。一套实体、一套 ORM、两种呈现方式。

如果要给小程序提供 API,则再加一层 Controller,但 Controller 里调的是同一套仓储与实体:

[ApiController][Route("api/products")]publicclassProductsController(IBaseRepository<Product>repo):ControllerBase{[HttpGet]publicasyncTask<IActionResult>Get(intpageIndex=1,intpageSize=12){varlist=awaitrepo.Select.Where(x=>x.IsOnSale).OrderByDescending(x=>x.CreatedTime).Page(pageIndex,pageSize).ToListAsync();returnOk(list);}}

这时候才需要 DTO——因为前端是另一个进程,需要稳定的数据契约。


五、复用什么,不复用什么

资产后台(Blazor Server)前台 API(Controller)
实体 / 映射复用复用
仓储 / ORM复用复用
业务校验(OnBeforeSaveAsync里的逻辑)直接用建议抽到领域服务后复用
数据权限过滤框架自动需要显式调用同一套规则
权限判定AuthButton/AuthPath需要自己加授权策略
DTO通常不需要需要(契约)

一句话:能复用实体和 ORM,不能指望后台的组件级权限自动保护对外 API。对外接口的鉴权必须单独设计。


六、维护性怎么看

"没有分层会不会变成一堆面条代码"是合理担心。判断标准其实不看分层数量,而看三件事:

  1. 重复逻辑有没有地方收敛。分页、权限、数据权限、导入导出都收敛在框架里;业务校验收敛在OnBeforeSaveAsync;跨模块业务逻辑该抽服务就抽服务(EasyAdminBlazor.Test里就有Services命名空间下的业务实体与页面结构)。
  2. 边界是否清晰。后台的边界是"页面 = 一个功能单元",页面文件里既有 UI 也有编排,这在后台这种"以页面为中心"的系统里反而更好定位问题。
  3. 测试怎么写。业务规则如果写在回调里就不容易单测——所以复杂的业务规则应该抽到可注入的领域服务,页面回调只做编排。框架本身也是这么做的(审批的状态机、数据权限的表达式构建都是独立可测的纯逻辑)。

所以推荐的写法是:

页面(AdminTable + EditTemplate) ↓ 只做 UI 编排 领域服务(可注入、可单测) ↓ 业务规则、事务、外部调用 仓储 / ORM

框架省掉的是 Controller + DTO 这两层"协议层",不是"业务逻辑该有的组织"。


七、小结

问题回答
为什么没有 Controller?Blazor Server 组件在服务端执行,没有 HTTP 边界
为什么没有 DTO?数据不跨进程;列定义 +[JsonIgnore]+ 投影承担了"边界"职责
那 Service 呢?CRUD 编排被AdminTable内置;业务规则仍应抽成领域服务
什么时候还要写接口层?前后端分离、第三方对接、开放 API、团队规范
会不会更难维护?关键不在分层数量,而在"重复逻辑是否收敛、业务逻辑是否可测"

如果你正在用 .NET 10 + Blazor 做后台,可以按这个标准判断:后台管理部分用组件化省掉协议层,对外提供的接口单独设计。EasyAdminBlazor 的实体与仓储在这两种场景下都能复用。

  • 文档:https://easyadmin.wang-zhan.com.cn/doc
  • 源码:https://gitee.com/gudufy/EasyAdminBlazor
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 21:43:29

text-to-cad 实战:从自然语言到 STEP/STL 三维模型生成全链路解析

1. 从一句话到三维模型&#xff1a;text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词&#xff0c;我脑子里蹦出来的画面是&#xff1a;对着电脑敲一句“给我画一个 806010mm、四角带 M4 沉头孔的安装板”&#xff0c;然后软件自己把模型建好、导出 STEP 文件丢给我…

作者头像 李华
网站建设 2026/10/19 10:00:32

职臣AI文献综述怎么选?一图看懂适用场景

写文献综述时&#xff0c;真正让人卡住的往往不是“不会写”&#xff0c;而是不知道从哪里开始&#xff1a;研究范围太大&#xff0c;资料零散&#xff0c;国内外观点难以归类&#xff0c;最后还要反复调整结构和格式。职臣AI的文献综述功能&#xff0c;可以看作一个围绕“研究…

作者头像 李华
网站建设 2026/10/22 15:58:33

职臣AI文献综述:从研究问题到可核验的文献脉络

文献综述难写&#xff0c;常常不是因为“字数不够”&#xff0c;而是文献、问题和论证之间还没有连起来。职臣AI的文献综述页面&#xff0c;适合从这个连接过程入手观察&#xff1a;它没有让用户一上来就索要一篇成稿&#xff0c;而是把操作拆成信息设置、参考文献确定、浏览与…

作者头像 李华
网站建设 2026/10/23 9:44:12

如何用AI4AnimationPy的FABRIK求解器实现实时逆运动学:完全指南

如何用AI4AnimationPy的FABRIK求解器实现实时逆运动学&#xff1a;完全指南 【免费下载链接】ai4animationpy A Python framework for AI-driven character animation using neural networks. 项目地址: https://gitcode.com/gh_mirrors/ai/ai4animationpy AI4Animation…

作者头像 李华