3.ASP.NET全栈开发之前端校验(基于Vue的自定义校验)自实现小型验证框架

前面分享了两篇关于.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");
            }
        }

以为可以用了?还早呢,我先去吃个饭,晚上回来接着写,(#^.^#)。

原文地址:https://www.cnblogs.com/Gxqsd/p/9330721.html

时间: 2024-08-08 09:39:01

3.ASP.NET全栈开发之前端校验(基于Vue的自定义校验)自实现小型验证框架的相关文章

spring boot + vue + element-ui全栈开发入门——前端编辑数据对话框

 需求 1.点击“添加”按钮,弹出录入数据的对话框窗口,并录入数据,如果数据有误则不允许提交.数据填写完毕后,点击“保存”按钮,调用http协议提交数据,提交完毕刷新页面数据.点击“取消”按钮关闭对话框. 2.点击列表中的“修改”按钮,弹出数据修改对话框窗口,功能同上. 3.点击列表中的“删除”按钮,弹出删除数据的询问窗口,功能以此类推. 一.添加 在“src\mock\member.js”中,增加模拟保存数据的方法: adapters.push( (mockAdapter) => mockAd

ASP.NET全栈开发日志模块之操作日志的设计

应用程序中的日志大致分为三种 第一种:用于生产模式下追中bug的异常日志.(这类日志相信大家都懂) 第二种:用于记录重要操作的行为日志.(这类日志主要作用是溯源,行为主要有三种:增.删.改) 第三种:用于记录历史浏览的消息日志.(这类日志主要记录用户的浏览痕迹) 消息日志和行为日志有点相似,都有溯源的作用,但它们区别很明显. 消息日志记录的是所有操作信息,比如 "gxqsd 登录了 系统 2018-7-27 23:46:10" , "gxqsd 访问了用户管理首页"

巨蟒python全栈开发数据库前端8:jQuery框架2

1.事件 2.批量操作 3.事件冒泡 4.事件委托 1.事件 常用事件 click(function(){...}) hover(function(){...}) blur(function(){...}) focus(function(){...}) change(function(){...}) //内容发生变化,input,select等 keyup(function(){...}) mouseover 和 mouseenter的区别是:mouseover事件只要你在绑定该事件的对象上移动

spring boot + vue + element-ui全栈开发入门——开篇

最近经常看到很多java程序员朋友还在使用Spring 3.x,Spring MVC(struts),JSP.jQuery等这样传统技术.其实,我并不认为这些传统技术不好,而我想表达的是,技术的新旧程度体现了做项目时的生产力.生产力低了,项目的开发成本就高.反之,生产力高,则成本低.笔者写本系列的目的是让使用“前后端不分离”的老技术的开发者做一个入门级的过度.因为目前流行的“前后端分离”技术足够简单,足够方便,足够易学,也足够完善. 项目所使用的前端技术是:element-ui,文档地址是:ht

spring boot + vue + element-ui全栈开发入门——基于Electron桌面应用开发

 前言 Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库. Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包为Mac,Windows和Linux系统下的应用来实现这一目的. Electron于2013年作为构建Github上可编程的文本编辑器Atom的框架而被开发出来.这两个项目在2014春季开源. 目前它已成为开源开发者.初创企业和老牌公司常用的开发工具. 看看谁在使用Electron

Python 全栈开发【第一篇】:目录

Python 全栈开发[第0篇]:目录 第一阶段:Python 开发入门 Python 全栈开发[第一篇]:计算机原理&Linux系统入门 Python 全栈开发[第二篇]:Python基础语法入门 Python 全栈开发[第三篇]:数据类型.字符编码.文件操作 第二阶段:函数编程&常用标准库 Python 全栈开发[第四篇]:函数.递归.生成器.迭代器 Pyhton 全栈开发[第五篇]:常用模块学习 第三阶段:面向对象编程&网络编程基础 Python 全栈开发[第六篇]:面向对象

5.ASP.NET全栈开发之在Vue中使用前端校验(二)

在全栈开发系列第三篇的时候有讲到使用Vue进行前端验证.在那一篇博文里,详细讲了如何搭建 vuefluentvalidator.js 的过程,并最终把它从需要(实体和实体验证器)到 直接使用,很显然,它很小巧的胜任了工作.(首先声明,这个vuefluentvalidator.js是我上周末也就是7月15号才开始构思和编写的,而我最开始的目的是希望它能轻松完成表单的校验工作,但没想过许多复杂多变的情况.所以这期间出现了多次更改和修正).目前我已将它上传至github,网址为:https://git

前端工程师晋升课程 Vue全家桶+SSR+Koa2全栈开发美团网

第1章 课程导学这门课主讲以Vue SSR+Koa2全栈技术为目标,最终实现美团网项目.本章节旨在告诉大家我们会用到哪些技能.教学方法.课程内容分布.学习方法等.备注:我们会涉及Vue2.5.Nuxt.Koa2.element-ui.Mongodb等 1-1 课程导学第2章 Vue基础知识整个SSR部分都是用的Vue框架,需要给初级用户讲解Vue的基础语法,不会让他们在学习实战的时候感到迷茫,这个章节会通过vue-cli搭建一个简单的demo,让大家快速的掌握Vue的基础应用,即使他没有学习过.

未来物联网全栈开发 —— JavaScript OR Python?

物联网开发涉及面庞杂,开发周期长,所以我们必须寻找一种覆盖面广的编程语言和方法. JavaScript 支持 HTTP 和 JSON .支持函数式编程.可提供交互式环境等特点堪称适用于物联网全栈开发: Python 作为一种胶水语言,可在物联网及嵌入式系统中承担大量任务,并部分替代以上语言. ??物联网是新一代信息技术的重要组成部分,也是"信息化"时代的重要发展阶段,不太清楚的可以看上篇文章<IoT领域的故事.经历.技术实战>,正所谓语言无国度,无论是 Js 还是 Pyth