ASP.NET Core 问答 02:Minimal API、MVC、路由、绑定与验证
021 Minimal API 与 Controller 如何选择?
难度: 基础
查看参考答案
结论: 按端点复杂度、团队约定和 MVC 扩展需求选择,两者可以共存
原因: Minimal API 适合轻量端点,Controller 提供成熟的控制器、过滤器和约定模型
边界: 性能差异通常不是唯一决策因素,认证授权和业务分层两者都能实现
最小示例: 简单内部 API 用 Minimal API,复杂公共 API 用 Controller
022 MapGroup 有什么价值?
难度: 基础
查看参考答案
结论: 它为共同前缀、元数据、过滤器和授权创建端点组
原因: 分组减少重复配置并让版本或业务边界更清晰
边界: 组级元数据会传播到端点,但端点仍可能追加或覆盖部分设置
最小示例: app.MapGroup(“/api/orders”).RequireAuthorization()
023 TypedResults 为什么有利于 API 设计?
难度: 基础
查看参考答案
结论: 它返回具体结果类型,改善编译期信息、测试和 OpenAPI 元数据推断
原因: 具体类型让状态码与响应模型更明确
边界: 多种结果可用 Results 联合类型表达,但仍要处理领域错误
最小示例: return TypedResults.NotFound()
024 路由约束应解决什么问题?
难度: 基础
查看参考答案
结论: 约束用于缩小候选和消除路由歧义,不是业务数据验证
原因: 路由匹配发生在业务处理前,约束失败通常表现为没有匹配端点
边界: 把数据库校验写成路由约束会增加耦合和不可预期开销
最小示例: 使用 {id:int} 约束格式
025 LinkGenerator 为什么优于手写 URL?
难度: 基础
查看参考答案
结论: 它根据端点名称和路由值生成链接,能跟随路由模板演进
原因: 集中路由知识可减少路径字符串散落
边界: 反向代理场景还要正确提供 scheme、host 和 PathBase
最小示例: GetPathByName(“order-detail”, new { id })
026 Minimal API 参数从哪里绑定?
难度: 基础
查看参考答案
结论: 框架按显式特性、特殊类型、DI、路由、查询、头和正文等规则推断来源
原因: 参数类型和端点签名会影响绑定来源
边界: 复杂请求应显式标注以提升契约可读性,正文通常只能读取一次
最小示例: [FromRoute] int id 与 [FromBody] CreateOrder body
027 自定义类型如何参与参数绑定?
难度: 进阶
查看参考答案
结论: 可实现 TryParse、BindAsync 或相关静态抽象契约
原因: 这让值对象在边界一次完成解析并返回绑定失败
边界: 解析只处理传输格式,业务存在性和授权仍应在后续验证
最小示例: OrderId.TryParse
028 模型绑定失败与业务验证失败应如何区分?
难度: 进阶
查看参考答案
结论: 绑定失败表示无法构造输入,业务验证失败表示输入可解析但违反规则
原因: 区分两者有助于返回稳定的 400 问题详情和字段错误
边界: 资源不存在、冲突等不是模型验证,应使用相应 404 或 409 语义
最小示例: 日期格式错误属于绑定,结束时间早于开始时间属于验证
029 [ApiController] 提供哪些关键行为?
难度: 进阶
查看参考答案
结论: 它启用属性路由要求、自动 400、绑定源推断和 ProblemDetails 等 API 约定
原因: 这些约定减少样板,但会改变 Action 是否被调用
边界: 自定义 InvalidModelStateResponseFactory 时仍要保持一致错误契约
最小示例: 无效 ModelState 时 Action 通常不执行
030 DataAnnotations 的边界是什么?
难度: 进阶
查看参考答案
结论: 适合简单字段与对象规则,复杂跨资源规则应放在应用服务或专用验证器
原因: 特性易于声明但不应访问数据库或承担授权
边界: 自动验证是否启用取决于使用的框架路径,Minimal API 需确认当前版本能力
最小示例: Required 与 StringLength 处理输入形状
031 端点过滤器适合处理什么?
难度: 进阶
查看参考答案
结论: 适合 Minimal API 的端点级前后逻辑,如验证、审计和结果转换
原因: 它能读取参数和元数据,作用范围比全局中间件更靠近端点
边界: 通用 HTTP 安全能力仍宜放中间件或授权系统,不能堆进过滤器
最小示例: AddEndpointFilter 统一验证命令对象
032 MVC 过滤器有哪些主要阶段?
难度: 进阶
查看参考答案
结论: 授权、资源、动作、异常和结果过滤器位于 MVC 执行管道不同阶段
原因: 选择正确阶段才能观察所需上下文并控制短路
边界: 中间件包围整个 HTTP 管道,过滤器只作用于 MVC Action
最小示例: 异常过滤器不捕获中间件早期异常
033 异常过滤器与异常中间件如何分工?
难度: 进阶
查看参考答案
结论: 异常中间件作为全局兜底,过滤器只处理 MVC 内部且有控制器上下文的异常
原因: 中间件覆盖面更广,过滤器适合特定 Action 语义
边界: 不要同时重复记录和转换同一异常,否则会产生双重响应或噪声
最小示例: 全局 UseExceptionHandler 输出 ProblemDetails
034 内容协商如何选择响应格式?
难度: 进阶
查看参考答案
结论: 根据 Accept、已注册输出格式化器和返回类型选择可接受表示
原因: 服务器只支持已配置格式,不会自动满足任意媒体类型
边界: API 通常以 JSON 为主,启用 XML 要明确契约和测试
最小示例: 客户端请求 application/xml 但未注册 XML 时按配置处理
035 ActionResult<T> 有什么作用?
难度: 实战
查看参考答案
结论: 它允许控制器返回 T 或具体 ActionResult,并帮助描述成功模型
原因: 适合同时表达正常数据与 NotFound、BadRequest 等结果
边界: 它不会自动决定业务状态码,分支仍需显式选择
最小示例: return entity is null ? NotFound() : entity
036 为什么不应直接返回 EF 实体作为公共 API 契约?
难度: 实战
查看参考答案
结论: 应优先使用 DTO 控制字段、版本和序列化形状
原因: 实体导航、循环引用和敏感字段可能意外暴露,数据库模型演进也会破坏 API
边界: 内部原型可以权衡简化,但公共契约要稳定
最小示例: Select 投影为 OrderSummaryDto
037 文件上传应关注哪些边界?
难度: 实战
查看参考答案
结论: 限制大小和类型、使用流式处理或受控缓冲,并生成安全文件名
原因: 不可信文件可能耗尽内存、路径穿越或携带恶意内容
边界: ContentType 可伪造,关键场景需检查内容并隔离存储
最小示例: 不要使用客户端文件名拼接服务器路径
038 IFormFile 与流式上传如何选择?
难度: 实战
查看参考答案
结论: 小型受限文件可用 IFormFile,超大或持续上传应考虑流式读取
原因: IFormFile 可能使用内存或临时磁盘缓冲,流式方案更可控但实现更复杂
边界: 两者都必须设置请求限制、取消和清理失败残留
最小示例: 大视频分块写入对象存储
039 分页 API 为什么需要稳定排序?
难度: 实战
查看参考答案
结论: 没有唯一稳定排序时,分页可能漏项或重复项
原因: 并发写入会放大偏移分页的不稳定,键集分页常更适合大数据
边界: 排序字段重复时应追加唯一键作为次级排序
最小示例: OrderBy(CreatedAt).ThenBy(Id)
040 如何设计统一的 ProblemDetails 响应?
难度: 实战
查看参考答案
结论: 集中映射异常和业务错误到 type、title、status、detail 与扩展字段
原因: 一致格式便于客户端解析和可观测性关联
边界: 不要把堆栈、SQL 和内部路径暴露给外部客户端
最小示例: 扩展 traceId 便于日志关联