ASP.NET MVC 查询参数封装与自动化验证模式
在 ASP.NET MVC 大量查询场景中,控制器方法常面临参数膨胀、重复校验、SQL 注入防护和时间范围逻辑混乱等问题。传统方式将校验逻辑散落在 Action 内,导致可读性差、复用率低、维护成本高。本文提出一种基于智能属性封装 + 延迟验证计算的轻量级解决方案,将数据接收、清洗、范围约束与时间归一化全部下沉至专用请求模型中。
问题驱动:从"扁平参数"到"结构化契约"
当查询接口需支持 10+ 字段(如用户 ID、姓名、状态码、等级区间、起止时间、分页配置、视图模式等),直接声明 Action 参数会迅速失控:
public ActionResult Search(
int? id, string name, int? status, int? levelMin, int? levelMax,
DateTime? start, DateTime? end, int page = 1, int size = 20,
string viewMode = "list", string keyword = null)
{ ... }
此类签名难以理解语义,且每个字段需独立校验——例如 levelMin 必须 ≤ levelMax,start 不得晚于 end,end 不得超当前时刻,字符串需去空格防注入……逐行编写既冗长又易漏。
核心思想:让属性 getter 承担验证职责
不再依赖 Action 内部手动校验,而是设计一个请求模型类(如 SearchRequest),其所有公开属性均通过 get 访问器实现按需计算 + 自动修复:
- 值为
null时赋予合理默认值(如分页页码默认为 1); - 超出业务范围时自动截断或重置(如等级强制限定在 [0,5]);
- 时间字段自动归一化(如"开始时间"设为当日 00:00:00,"结束时间"设为当日 23:59:59);
- 字符串自动 Trim 并过滤敏感字符(如 SQL 关键字、脚本标签);
- 提供辅助属性(如
StartFormatted、EndFormatted)返回已处理的字符串值。
重构后的控制器代码(极简风格)
public ActionResult Search(SearchRequest req)
{
// ✅ 所有参数已自动清洗、验证、归一化
var sql = $@"SELECT * FROM users
WHERE id = {req.Id}
AND name LIKE '%{req.Name}%'
AND created BETWEEN '{req.StartFormatted}' AND '{req.EndFormatted}'
LIMIT {req.PageSize} OFFSET {(req.PageIndex - 1) * req.PageSize}";
var users = _db.Query<User>(sql);
return View(users);
}
无需任何前置校验代码,模型自身保证输出值始终处于安全、可用、符合业务规则的状态。
关键实现:SearchRequest 模型示例
以下为精简重构版核心逻辑(移除冗余注释与枚举定义,优化正则与时间处理):
public class SearchRequest
{
// 分页控制
public int PageIndex { get; set; } = 1;
public int PageSize { get; set; } = 20;
// 用户基础信息
private string _name;
public string Name
{
get => SanitizeString(_name, allowEmpty: true);
set => _name = value;
}
private int? _id;
public int? Id
{
get => _id ?? null;
set => _id = value >= 0 ? value : null;
}
// 等级范围([0,5])
private int? _level;
public int? Level
{
get => Clamp(_level, min: 0, max: 5);
set => _level = value;
}
// 时间范围(自动归一化)
private DateTime? _startTime;
public DateTime? StartTime
{
get
{
if (!_startTime.HasValue) return null;
var dt = _startTime.Value.Date;
return dt > DateTime.Now.Date ? DateTime.Now.Date : dt;
}
set => _startTime = value;
}
private DateTime? _endTime;
public DateTime? EndTime
{
get
{
if (!_endTime.HasValue) return null;
var dt = _endTime.Value;
if (dt > DateTime.Now) dt = DateTime.Now;
return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59);
}
set => _endTime = value;
}
// 格式化输出(供 SQL 或日志使用)
public string StartFormatted => StartTime?.ToString("yyyy-MM-dd 00:00:00") ?? "";
public string EndFormatted => EndTime?.ToString("yyyy-MM-dd 23:59:59") ?? "";
// 工具方法
private string SanitizeString(string input, bool allowEmpty = false)
{
if (string.IsNullOrWhiteSpace(input))
return allowEmpty ? "" : null;
var cleaned = input.Trim();
if (ContainsDangerousPattern(cleaned))
cleaned = "[CLEANED]";
return cleaned;
}
private int? Clamp(int? value, int min, int max)
{
if (!value.HasValue) return null;
if (value < min) return min;
if (value > max) return max;
return value;
}
private static readonly Regex SqlPattern = new Regex(
@"(?i)\b(select|insert|delete|update|create|drop|exec|declare|union|script|iframe|'|--|\bexec\b)",
RegexOptions.Compiled);
private static bool ContainsDangerousPattern(string text) =>
SqlPattern.IsMatch(text);
}
优势总结
- 零校验侵入:Controller 层彻底摆脱
if校验链,专注业务逻辑; - 强一致性保障:同一字段在任意 Action 中行为完全一致;
- 可扩展性强:新增字段只需添加属性 + 对应 getter 逻辑,不破坏现有结构;
- 调试友好:直接查看模型实例属性值,即为最终生效值;
- 安全基线内置:SQL 注入防护、XSS 防御、越界保护均集中管控。
该模式适用于中大型 MVC 项目中高频查询场景,尤其适合需快速交付、长期维护的后台管理系统。