news 2026/6/26 1:27:26

构建盾牌——数据验证与全局异常处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
构建盾牌——数据验证与全局异常处理

前言:别让“脏数据”搞垮你的系统

在上一篇中,我们实现了数据的增删改查。但现实是残酷的:前端可能会传给你一个空的标题、负数的ID,甚至是一段恶意脚本。如果我们不做防御,这些“脏数据”会像病毒一样侵入数据库。

作为一个有追求的开发者,不仅要“能写”,还要“能防”。这一篇,我们来打造系统的“安全盾牌”。

二、第一道防线:模型绑定与基础验证

ASP.NET Core 自带了基础的验证机制。当请求体(Body)中的JSON数据被反序列化为对象时,框架会自动检查数据注解。

2.1 使用 Data Annotations (数据注解)

这是最简单的方式,通过给属性加“标签”来定义规则。

修改Models/TodoItem.cs

using System.ComponentModel.DataAnnotations; public class TodoItem { public int Id { get; set; } [Required(ErrorMessage = "标题不能为空")] // 必填 [StringLength(100, ErrorMessage = "标题长度不能超过100")] // 最大长度 public string? Title { get; set; } public bool IsDone { get; set; } public DateTime CreatedAt { get; set; } = DateTime.Now; }

2.2 在API中检查验证状态

在Minimal API中,我们需要手动检查ValidationContext,这比传统Controller稍微麻烦一点,但更灵活。

using System.ComponentModel.DataAnnotations; app.MapPost("/todos/basic-validate", (TodoItem todo) => { // 手动创建验证上下文 var validationContext = new ValidationContext(todo); var validationResults = new List<ValidationResult>(); // 执行验证 bool isValid = Validator.TryValidateObject(todo, validationContext, validationResults, true); if (!isValid) { // 如果验证失败,返回400错误和具体错误信息 return Results.BadRequest(validationResults.Select(r => r.ErrorMessage)); } // 验证通过,执行业务逻辑... return Results.Ok("数据合法"); });

刚子小贴士: 这种方式虽然简单,但缺点很明显:验证规则写在了实体类里,导致类变得臃肿。而且复杂的逻辑(比如“标题不能包含敏感词”)很难用标签实现。在企业级项目中,我们更推荐下面要讲的FluentValidation

三、进阶利器:FluentValidation

FluentValidation 是一个第三方库,它允许你用流式代码定义验证规则,将验证逻辑与实体类彻底分离。

3.1 安装与基础配置

执行命令安装包:

dotnet add package FluentValidation.AspNetCore

3.2 定义验证器

创建Validators/TodoItemValidator.cs

using FluentValidation; using MyTodoApp.Models; public class TodoItemValidator : AbstractValidator<TodoItem> { public TodoItemValidator() { // 规则1:标题不为空 RuleFor(x => x.Title) .NotEmpty().WithMessage("任务标题必须填写") .MaximumLength(50).WithMessage("标题太长了,别超过50个字"); // 规则2:自定义逻辑验证 RuleFor(x => x.Title) .Must(title => !title.Contains("傻子")).WithMessage("标题包含敏感词,请文明用语"); } }

3.3 注册与自动验证

Program.cs中注册服务:

builder.Services.AddFluentValidationAutoValidation(); // 开启自动验证 builder.Services.AddValidatorsFromAssemblyContaining<Program>(); // 扫描当前程序集的所有验证器

改造API接口: 现在,当请求进入时,框架会自动验证。如果失败,直接返回400。我们可以这样写:

app.MapPost("/todos", (TodoItem todo, AppDbContext db) => { // 如果走到这里,说明验证已经通过了 db.Todos.Add(todo); db.SaveChanges(); return Results.Created($"/todos/{todo.Id}", todo); });

刚子敲黑板: FluentValidation 最强大的地方在于它的可复用性可测试性。你可以单独对这个 Validator 类写单元测试,确保验证逻辑的正确性,而不用担心业务逻辑的干扰。

四、第二道防线:全局异常处理

只有验证是不够的。代码运行时总会遇到意想不到的错误:数据库连接断了、文件找不到了、甚至是你写了int.Parse("abc")

如果不管这些异常,用户会看到浏览器返回一个黄色的错误页面(开发环境)或者裸露的堆栈信息(生产环境),这非常不专业,甚至可能泄露敏感代码路径。

4.1 构建统一响应格式

我们需要定义一个标准的错误返回格式,无论哪里出错,前端收到的结构都是一样的。

// 定义错误响应模型 public class ErrorResponse { public int StatusCode { get; set; } public string Message { get; set; } public string? Detail { get; set; } // 仅开发环境显示 }

4.2 编写异常处理中间件

我们在管道的最前端放置一个“捕鱼网”,捕获所有后续抛出的异常。

修改Program.cs

var app = builder.Build(); // --- 全局异常处理中间件 --- app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { // 1. 获取异常详情 var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>(); var exception = exceptionHandlerFeature?.Error; // 2. 设置响应状态码和内容类型 context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; // 3. 构造返回对象 var response = new ErrorResponse { StatusCode = 500, Message = "服务器内部错误,请稍后重试", // 给用户看的友好信息 Detail = app.Environment.IsDevelopment() ? exception?.Message : null // 开发环境下显示具体错误 }; // 4. 序列化并返回 await context.Response.WriteAsJsonAsync(response); }); }); // ... 其他中间件 ...

4.3 实战模拟异常

我们故意写一个会报错的接口:

app.MapGet("/error-test", () => { throw new Exception("哎呀,这里发生了一个模拟的严重错误!"); return "这里永远走不到"; });

访问这个接口,在生产环境下,用户会看到:

{ "statusCode": 500, "message": "服务器内部错误,请稍后重试", "detail": null }

而在开发环境下,你作为开发者可以看到detail里的具体错误信息,方便调试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 1:25:22

P89LPC92x1中断与I/O配置实战:从原理到避坑指南

1. 项目概述与核心价值如果你正在使用或评估恩智浦&#xff08;NXP&#xff09;的P89LPC92x1系列微控制器&#xff0c;那么理解其中断系统和I/O端口配置&#xff0c;绝对是绕不开的核心课题。这个系列&#xff0c;包括P89LPC9201、9211、922A1、9241和9251&#xff0c;虽然内核…

作者头像 李华
网站建设 2026/6/26 1:21:43

C语言基础回炉第七天:模块化封装传感器数据

前言前六天主要是在补 C 语言基础和嵌入式常用数据处理能力&#xff1a;位运算、字符串、链表、数组、环形缓冲区、UART 帧解析。今天继续往前走&#xff0c;不再停留在单个函数练习&#xff0c;而是开始练真实项目里更常见的写法&#xff1a;把一组相关数据和操作封装成一个 .…

作者头像 李华
网站建设 2026/6/26 1:20:49

java伪共享问题的稳定解法

背景 用户咨询了一个java中cpu缓存伪共享场景&#xff0c; 他通过padding多个long 字段隔离 2 个volatile字段&#xff0c;但是实测效果没有提升。 这是个比较有趣的场景&#xff0c;在 jdk8 有更稳定的方案去解决伪共享带来的性能问题。 下面我们展开介绍 伪共享问题是什么用…

作者头像 李华
网站建设 2026/6/26 1:13:57

ZFX山海证券:出金细节管理体现平台对用户资金体验的重视

对投资者来说&#xff0c;出金体验往往是衡量平台服务质量的重要环节。从处理效率来看&#xff0c;ZFX山海证券更强调让用户在可理解的流程中完成提款。在资料完整、账户状态正常的情况下&#xff0c;相关申请会进入标准审核流程&#xff0c;服务人员也会根据节点及时跟进。从提…

作者头像 李华
网站建设 2026/6/26 1:13:43

AScript如何实现LINQ语法

electMany/Where/Join/GroupJoin/GroupBy/OrderBy/OrderByDescending/Select等方法。 Queryable/Enumerable扩展方法已通过AddFunc方式注入到了CSharpLang语言中&#xff1a; 1 // IEnumerable<T>扩展方法 2 AddFunc(typeof(System.Linq.Enumerable)); 3 // IQueryable…

作者头像 李华