前言
参数验证是一个常见的问题,无论是前端还是后台,都需对用户输入进行验证,以此来保证系统数据的正确性。对于web来说,有些人可能理所当然的想在前端验证就行了,但这样是非常错误的做法,前端代码对于用户来说是透明的,稍微有点技术的人就可以绕过这个验证,直接提交数据到后台。无论是前端网页提交的接口,还是提供给外部的接口,参数验证随处可见,也是必不可少的。总之,一切用户的输入都是不可信的。
参数验证有许多种方式进行,下面以mvc为例,列举几种常见的验证方式,假设有一个用户注册方法
[HttpPost] public ActionResult Register(RegisterInfo info)
一、通过 if-if 判断
if(string.IsNullOrEmpty(info.UserName))
{
return FailJson("用户名不能为空");
}
if(string.IsNullOrEmpty(info.Password))
{
return FailJson("用户密码不能为空")
}
逐个对参数进行验证,这种方式最粗暴,但当时在WebForm下也确实这么用过。对于参数少的方法还好,如果参数一多,就要写n多的if-if,相当繁琐,更重要的是这部分判断没法重用,另一个方法又是这样判断。
二、通过 DataAnnotation
mvc提供了DataAnnotation对Action的Model进行验证,说到底DataAnnotation就是一系列继承了ValidationAttribute的特性,例如RangeAttribute,RequiredAttribute等等。ValidationAttribute 的虚方法IsValid 就是用来判断被标记的对象是否符合当前规则。asp.net mvc在进行model binding的时候,会通过反射,获取标记的ValidationAttribute,然后调用 IsValid 来判断当前参数是否符合规则,如果验证不通过,还会收集错误信息,这也是为什么我们可以在Action里通过ModelState.IsValid判断Model验证是否通过,通过ModelState来获取验证失败信息的原因。例如上面的例子:
public class RegisterInfo
{
[Required(ErrorMessage="用户名不能为空")]
public string UserName{get;set;}
[Required(ErrorMessage="密码不能为空")]
public string Password { get; set; }
}
事实上在webform上也可以参照mvc的实现原理实现这个过程。这种方式的优点的实现起来非常优雅,而且灵活,如果有多个Action共用一个Model参数的话,只要在一个地方写就够了,关键是它让我们的代码看起来非常简洁。
不过这种方式也有缺点,通常我们的项目可能会有很多的接口,比如几十个接口,有些接口只有两三个参数,为每个接口定义一个类包装参数有点奢侈,而且实际上为这个类命名也是非常头疼的一件事。
三、DataAnnotation 也可以标记在参数上
通过验证特性的AttributeUsage可以看到,它不只可以标记在属性和字段上,也可以标记在参数上。也就是说,我们也可以这样写:
public ActionResult Register([Required(ErrorMessage="用户名不能为空")]string userName, [Required(ErrorMessage="密码不能为空")]string password)
这样写也是ok的,不过很明显,这样写很方法参数会难看,特别是在有多个参数,或者参数有多种验证规则的时候。
四、自定义ValidateAttribute
我们知道可以利用过滤器在mvc的Action执行前做一些处理,例如身份验证,授权处理的。同理,这里也可以用来对参数进行验证。FilterAttribute是一个常见的过滤器,它允许我们在Action执行前后做一些操作,这里我们要做的就是在Action前验证参数,如果验证不通过,就不再执行下去了。
定义一个BaseValidateAttribute基类如下:
public class BaseValidateAttribute : FilterAttribute
{
protected virtual void HandleError(ActionExecutingContext context)
{
for (int i = ValidateHandlerProviders.Handlers.Count; i > 0; i--)
{
ValidateHandlerProviders.Handlers[i - 1].Handle(context);
if (context.Result != null)
{
break;
}
}
}
}
HandleError 用于在验证失败时处理结果,这里ValidateHandlerProviders提过IValidateHandler用于处理结果,它可以在外部进行注册。IValidateHandler定义如下:
public interface IValidateHandler
{
void Handle(ActionExecutingContext context);
}
ValidateHandlerProviders定义如下,它有一个默认的处理器。
public class ValidateHandlerProviders
{
public static List<IValidateHandler> Handlers { get; private set; }
static ValidateHandlerProviders()
{
Handlers = new List<IValidateHandler>()
{
new DefaultValidateHandler()
};
}
public static void Register(IValidateHandler handler)
{
Handlers.Add(handler);
}
}
这样做的目的是,由于我们可能有很多具体的ValidateAttribute,可以把这模块独立开来,而把最终的处理过程交给外部决定,例如我们在项目中可以定义一个处理器:
public class StanderValidateHandler : IValidateHandler
{
public void Handle(ActionExecutingContext filterContext)
{
filterContext.Result = new StanderJsonResult()
{
Result = FastStatnderResult.Fail("参数验证失败", 555)
};
}
}
然后再应用程序启动时注册:ValidateHandlerProviders.Handlers.Add(new StanderValidateHandler());
举个两个栗子:
ValidateNullttribute:
public class ValidateNullAttribute : BaseValidateAttribute, IActionFilter
{
public bool ValidateEmpty { get; set; }
public string Parameter { get; set; }
public ValidateNullAttribute(string parameter, bool validateEmpty = false)
{
ValidateEmpty = validateEmpty;
Parameter = parameter;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
string[] validates = Parameter.Split(',');
foreach (var p in validates)
{
string value = filterContext.HttpContext.Request[p];
if(ValidateEmpty)
{
if (string.IsNullOrEmpty(value))
{
base.HandleError(filterContext);
}
}
else
{
if (value == null)
{
base.HandleError(filterContext);
}
}
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
ValidateRegexAttribute:
public class ValidateRegexAttribute : BaseValidateAttribute, IActionFilter
{
private Regex _regex;
public string Pattern { get; set; }
public string Parameter { get; set; }
public ValidateRegexAttribute(string parameter, string pattern)
{
_regex = new Regex(pattern);
Parameter = parameter;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
string[] validates = Parameter.Split(',');
foreach (var p in validates)
{
string value = filterContext.HttpContext.Request[p];
if (!_regex.IsMatch(value))
{
base.HandleError(filterContext);
}
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
更多的验证同理实现即可。
这样,我们上面的写法就变成:
[ValidateNull("userName,password")]
public ActionResult Register(string userName, string password)
综合看起来,还是ok的,与上面的DataAnnotation可以权衡选择使用,这里我们可以扩展更多有用的信息,如错误描述等等。
总结
当然每种方式都有有缺点,这个是视具体情况选择了。一般参数太多建议就用一个对象包装了。
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]