标签:结构 逻辑 bst bit ... 做了 键值对 假设 push
前面分享了两篇关于.NET的服务端校验的文章,在系统里光有服务端校验虽然能够勉强使用,但会出现许多不愉快的体验,如上一章我在最后提交的时候填写了3个表单,在仅有最后一个表单出现了错误的时候,虽然达到了校验功能,表明了错误,但我前面三个表单的数据都被干掉了啊。再则比如注册的时候我填了那么多东西,我提交后你却告诉我已经被注册了,如果不是真爱,大概会毫不留情的Alt+F4 再也不见啦。
为了解决这个问题,我们必须在系统中采用双校验,前端校验那么多,咱们ASP.NET MVC创建时默认会引入jquery-validate,但在这里我就不过多介绍jquery-validate了,原因是...其实我也没怎么用过都是默认生成的顶多测试时用用,哈哈,这并不是说jquery-validate不好,只是我们现在所要搭建的这个系统是基于Vue的前后端分离项目,而Vue则是以数据为驱动的,所以咱们这暂时就不用讨论jquery-validate了。
如果不熟悉Vue的同学呢,可以先去Vue的官网看看,文档很详细,作者中国人,所以文档的易读性也很高,现在Vue已经是非常火的前端MVVM框架了,各种资料、教程也多,我就不过多重复了(其实我也才接触Vue不到一月,实在不敢瞎扯)。
在Vue的官网上看到表单验证的介绍,大致是通过给form绑定@submit事件,在事件里通过对字段的验证决定要不要阻止提交。
<form id="app" @submit="checkForm" action="https://vuejs.org/" method="post"> <p v-if="errors.length"> <b>Please correct the following error(s):</b> <ul> <li v-for="error in errors">{{ error }}</li> </ul> </p> <p> <label for="name">Name</label> <input type="text" name="name" id="name" v-model="name"> </p> <p> <label for="age">Age</label> <input type="number" name="age" id="age" v-model="age" min="0"> </p> <p> <label for="movie">Favorite Movie</label> <select name="movie" id="movie" v-model="movie"> <option>Star Wars</option> <option>Vanilla Sky</option> <option>Atomic Blonde</option> </select> </p> <p> <input type="submit" value="Submit"> </p> </form>
const app = new Vue({ el:‘#app‘, data:{ errors:[], name:null, age:null, movie:null }, methods:{ checkForm:function(e) { if(this.name && this.age) return true; this.errors = []; if(!this.name) this.errors.push("Name required."); if(!this.age) this.errors.push("Age required."); e.preventDefault(); } } })
Vue官网上的验证,是的,它非常短小精悍的完成了校验。
但说真的,这不是我想要的,我还是需要去写逻辑,我就想像前两章后台校验那样通过函数链式配置完成。
怎么办呢,Vue自己又没有,用别的框架?我咋知道有啥框架可用,我才刚认识它(Vue),对它的六大姑七大婆还不熟呢。
既然使用方式已定,又找不到别的办法,那就只有自己下地狱了。
本章节采用了ES6的语法,如看官还不熟悉ES6请先了解一下ES6基础后再来。
假设我有个实体Student
class Student { constructor() { this.name = undefined; this.age = undefined; this.sex = undefined; } }
首先我确定了我的使用方式像这样
this.ruleFor("name") .NotEmpty() .WithMessage("名称必填") .MinimumLength(5) .WithMessage("最短长度为5"); this.ruleFor("age") .NotEmpty() .WithMessage("年龄必须") .Number(0, 100) .WithMessage("年龄必须在0-100岁之间"); this.ruleFor("sex") .NotEmpty() .WithMessage("性别必填") .Must(m => !m.sex) .WithMessage("gxq is a man");
从FluentValidation认识哪里开始,FluentValidation的使用需要一个验证器,没关系,那我也创建一个验证器名字叫StudentValidator的验证器
class StudentValidator{
constructor(){
}
}
我们知道要想向ruleFor()、NotEmpty()等这样使用 首先我得具有ruleFor这些方法才行啊,反观FluentValidation,我们发现他必须要继承自AbstractValidator<T>,其中T代表验证器要验证的类型。
javascript没有泛型,但我们也可以继承自Validator啊,不支持泛型,我还不能用参数表单我要验证那个实体了么。╭(╯^╰)╮
于是乎
class Validator { constructor(model) { this.model =new model(); } ruleFor(field) { //此处表达式返回一个指定字段哈 } validation(targetElement) { } }
Validator就这么出现了,首先他有一个参数,参数是传递一个构造函数进去的,实体名就是构造函数,比如前面的Student。
内部定义了一个ruleFor方法,有一个参数,表明为指定属性配置验证规则。为了降低使用成本,尽量和前面使用的后台验证框架用法一致。
还定义了一个validation方法有一个参数触发对象,这个对象实际上是sumbit那个按钮,后面在做解释。
现在我们让StudentValidator继承自Validator,这样我们的StudentValidator就变为了
class StudentValidator extends Validator { constructor(typeName) { super(typeName); this.ruleFor("name");this.ruleFor("age"); this.ruleFor("sex"); } }
可以指定字段了,但不能配置这像什么话,二话不说继续改。
从哪里开始改呢?已经调用了ruleFor方法,要想实现接着.NotEmpty()的话那必须从ruleFor下手了。
方向已经明确了,但总的有具体实施方案啊,于是啊苦想半天,我认为我们的ruleFor既然是为了给目标字段定义验证,并且是链式的可以为目标字段配置多个验证规则如
this.ruleFor("age") .NotEmpty() .WithMessage("年龄必须") .Number(0, 100) .WithMessage("年龄必须在0-100岁之间");
那么我们就应该在ruleFor的时候返回一个以字段名为唯一标识的对象,幸好啊,在ES6中提供了一种数据类型叫Map,他有点类似于咱们C#的Dictionary,现在数据类型有了,键有了,那我这个键对应的值该是什么呢?
继续分析,这个值里必须具备NotEmpty、Number等这些函数我才能够调用啊,既然这样,那毫无疑问它又是一个对象了。
于是我创建了RuleBuilderInitial对象,基础结构如下
class RuleBuilderInitial { constructor() { this.length = undefined; this.must = undefined; this.maximumLength = undefined; this.minimumLength = undefined; this.number = undefined; } /* * 非空 */ NotEmpty() { } Length(min, max) { } Must(expression) { } EmailAddress() { } MaximumLength(max) { } MinimumLength(min) { } Number(min, max) { } }
有了RuleBuilderInitial后我们来改造下Validator
class Validator { constructor(model) { this.model =new model(); this.fieldValidationSet = new Map(); } ruleFor(field) { //此处表达式返回一个指定字段哈 // 验证配置以字段作为唯一单位,每个字段对应一个初始化器 this.fieldValidationSet.set(field, new RuleBuilderInitial()); return this.fieldValidationSet.get(field); } validation(targetElement) { } }
在Validator里我们新增了fieldValidationSet 他是一个Map数据结构,前面说到了Map就是一个键值对,只不过他比较唯一,他不允许键重复,如果键值对里已存在Key,则新的Value不会替换掉之前的Value。
我们用这个fieldValidationSet来保存以字段名为单位的验证配置。因为Map的结构原因,ruleFor确保了每个字段的配置项唯一的工作。
在ruleFor时,我们将new 一个RuleBuilderInitial对象,并将它和字段一起添加到Map去,最后将这个RuleBuilderInitial对象从Map里取出并返回。
我们知道在RuleBuilderInitial对象里定义了许多方法如NotEmpty、Number等,所以现在我们可以这样写了。
class StudentValidator extends Validator { constructor(typeName) { super(typeName); this.ruleFor("name") .NotEmpty() this.ruleFor("age") .NotEmpty() this.ruleFor("sex") .NotEmpty() } }
typeName是类型名,略略略,上面忘了解释了,在Validator的构造函数里我们不是有一个model吗,拿到model后我们new model了,所以typeName应该为我们要验证的实体如Student。
首先这显然不是我们想要的,我们希望在NotEmpty后能够接着调用WithMessage()方法,汲取了上面ruleFor的经验,肯定是在NotEmpty方法里返回一个新对象,并且这个新对象具备WithMessage方法。这没问题,太简单了,也许我们在创建一个叫 RuleBuilderOptions的对象就一切OK了,在NotEmpty()中只需要返回这个 RuleBuilderOptions对象就行了,可事实上我们希望的是在返回RuleBuilderOptions对象后调用WithMessage方法后并继续调用Number等其他方法。
梳理一下结构,我们在Validator种以字段作为key创建了RuleBuilderInitial,也就是说每个字段只有一个唯一的RuleBuilderInitial,在RuleBuilderInitial中如果享有多个验证的配置,同样我想也需要一个以验证名为key,RuleBuilderOptions为实例的针对不同验证信息的存储对象。于是我先创建了RuleBuilderOptions
class RuleBuilderOptions { constructor() { this.errorMessage = ‘‘; } WithMessage(errorMessage) { this.errorMessage = errorMessage; } }
紧接着在修改了 RuleBuilderInitial
class RuleBuilderInitial { constructor() { // T:验证的目标类型 TProperty:验证的属性类型 // 以具体的验证方式作为唯一单位 this.validationSet = new Map(); this.length = undefined; this.must = undefined; this.maximumLength = undefined; this.minimumLength = undefined; this.number = undefined; } /* * 非空 */ NotEmpty() { this.validationSet.set("NotEmpty", new RuleBuilderOptions()); return this.validationSet.get(‘NotEmpty‘); } Length(min, max) { this.validationSet.set("Length", new RuleBuilderOptions()); this.length = { min: min, max: max }; return this.validationSet.get("Length"); } Must(expression) { this.validationSet.set("Must", new RuleBuilderOptions()); this.must = expression; return this.validationSet.get("Must"); } EmailAddress() { this.validationSet.set("EmailAddress", new RuleBuilderOptions()); return this.validationSet.get(‘EmailAddress‘); } MaximumLength(max) { this.validationSet.set("MaximumLength", new RuleBuilderOptions()); this.maximumLength = max; return this.validationSet.get(‘MaximumLength‘); } MinimumLength(min) { this.validationSet.set("MinimumLength", new RuleBuilderOptions()); this.minimumLength = min; return this.validationSet.get(‘MinimumLength‘); } Number(min, max) { this.validationSet.set("Number", new RuleBuilderOptions()); this.number = { min: min, max: max }; return this.validationSet.get("Number"); } }
在调用NotEmpty()之后先将我对该字段非空校验的要求保存到 validationSet 里去,具体措施就是以验证名为key,以一个RuleBuilderOptions实例为value 存储到 validationSet 然后返回这个 RuleBuilderOptions,因为它能让我接着调用WithMessage("")
遗憾的是这样做了之后我发现我调用WithMessage("")后不能再接着调用其他的验证方法了。噢,我想到了,我可以给 RuleBuilderOptions 的构造函数添加一个参数,然后在new RuleBuilderOptions 的时候将this传进去 而new RuleBuilderOptions的地方只有
RuleBuilderInitial,这个this 也就自然而然的成了当前所要验证的那个字段的唯一 RuleBuilderInitial 实例,只要在WithMessage之后将这个this 返回回来一切似乎就大功告成了。
于是美滋滋的把代码改成了这样。
class RuleBuilderOptions { constructor(initial) { this.errorMessage = ‘‘; this.ruleBuilderInitial = initial; } WithMessage(errorMessage) { this.errorMessage = errorMessage; return this.ruleBuilderInitial; } }
class RuleBuilderInitial { constructor() { // T:验证的目标类型 TProperty:验证的属性类型 // 以具体的验证方式作为唯一单位 this.validationSet = new Map(); this.length = undefined; this.must = undefined; this.maximumLength = undefined; this.minimumLength = undefined; this.number = undefined; } /* * 非空 */ NotEmpty() { this.validationSet.set("NotEmpty", new RuleBuilderOptions(this)); return this.validationSet.get(‘NotEmpty‘); } Length(min, max) { this.validationSet.set("Length", new RuleBuilderOptions(this)); this.length = { min: min, max: max }; return this.validationSet.get("Length"); } Must(expression) { this.validationSet.set("Must", new RuleBuilderOptions(this)); this.must = expression; return this.validationSet.get("Must"); } EmailAddress() { this.validationSet.set("EmailAddress", new RuleBuilderOptions(this)); return this.validationSet.get(‘EmailAddress‘); } MaximumLength(max) { this.validationSet.set("MaximumLength", new RuleBuilderOptions(this)); this.maximumLength = max; return this.validationSet.get(‘MaximumLength‘); } MinimumLength(min) { this.validationSet.set("MinimumLength", new RuleBuilderOptions(this)); this.minimumLength = min; return this.validationSet.get(‘MinimumLength‘); } Number(min, max) { this.validationSet.set("Number", new RuleBuilderOptions(this)); this.number = { min: min, max: max }; return this.validationSet.get("Number"); } }
至此,我们已经可以这样配置了。
class StudentValidator extends Validator { constructor(typeName) { super(typeName); this.ruleFor("name") .NotEmpty() .WithMessage("名称必填") .MinimumLength(5) .WithMessage("最短长度为5"); this.ruleFor("age") .NotEmpty() .WithMessage("年龄必须") .Number(0, 100) .WithMessage("年龄必须在0-100岁之间"); this.ruleFor("sex") .NotEmpty() .WithMessage("性别必填") .Must(m => !m.sex) .WithMessage("gxq is a man"); } }
以为可以用了?还早呢,我先去吃个饭,晚上回来接着写,(#^.^#)。
3.ASP.NET全栈开发之前端校验(基于Vue的自定义校验)自实现小型验证框架
标签:结构 逻辑 bst bit ... 做了 键值对 假设 push
原文地址:https://www.cnblogs.com/Gxqsd/p/9330721.html