比较Cool 的客户端和服务端验证 (链式编程)

从丑陋到优雅,让代码越变越美(客户端检测方法思考)
大家都知道,客户端检测不单可以让用户获得更好的体验,而且可以通过校验数据大大减少客户端和服务器端的往返次数,减少服务器负担。在这里,小弟打算回顾一下自己在客户端检测方面的学习历程和采用方法,如果大家有什么更好的方法或者建议,欢迎提出来共享!共同进步!

为了方便举例和说明,先构建一个简单的html页面,如下:

html
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JS Verify</title>
</head>
<body>
    <div>
        <span>请输入用户名,年龄和自我介绍:</span>
        <br />
        <span>用户名:<input type="text" id="txtName" size="20" /></span>
        <br />
        <span>年龄:<input type="text" id="txtAge" size="5" />岁</span>
        <br />
        <span>自我介绍:</span>
        <br />
        <span><textarea id="txtIntro" rows="10" cols="50"></textarea></span>
        <br />
        <span><input type="button" value="提交信息" /></span>
    </div>
</body>
</html>
第一阶段是:续项强写

每个人都基本会经过这个阶段,就是对检测内容每个都手工校验。很惭愧,自己也写过不少这样的代码。。这个阶段的代码如下:

Code1
function SubmitInfo()
{
    var name = document.getElementById("txtName");
    var age = document.getElementById("txtAge");
    var intro = document.getElementById("txtIntro");
    if(name == null || name.value == "")
    {
        alert("请输入用户名!");
        return false;
    }
    if(!/^[/u4E00-/u9FA5a-z0-9_]*$/gi.test(name.value))
    {
        alert("用户名只能由中文,英文,数字及下划线组成!");
        return false;
    }
    if(age == null || age.value == "")
    {
        alert("请输入年龄!");
        return false;
    }
    if(!/^[1-9]/d$/.test(age.value))
    {
        alert("年龄必须为正整数!");
        return false;
    }
    if(intro == null || intro.value == "")
    {
        alert("请输入自我介绍!");
        return false;
    }
    alert("提交成功!");
    return true;
}
不评价这个了。。因为每个人都可能因为写这些方法检测逻辑写得眼冒星星手抽筋的!弄的经常下班了还在扑哧扑哧的写啊写。。。。

第二阶段:集中消灭

相信不少初学者现在还是处于这个阶段,这阶段的同学们已经被第一阶段折磨怕了。很快就想出了集中消灭相同类型检测的方法。就是写检测函数,如下:

Code2
function isEmpty(obj)
{
    if(obj == null || obj.value == "")
        return false;
    return true;
}

function isInt(val)
{
    return /^[1-9]/d$/.test(val);
}

function isSafeString(val)
{
    return /^[/u4E00-/u9FA5a-z0-9_]*$/gi.test(val);
}
同学们很可能还将上边代码独立成一个公共类,叫Common.js什么的,然后实际检测引用一下,就容易多了:

Code3
function SubmitInfo()
{
    var name = document.getElementById("txtName");
    var age = document.getElementById("txtAge");
    var intro = document.getElementById("txtIntro");
    if(!isEmpty(name))
    {
        alert("请输入用户名!");
        return false;
    }
    if(!isSafeString(name.value))
    {
        alert("用户名只能由中文,英文,数字及下划线组成!");
        return false;
    }
    if(!isEmpty(age))
    {
        alert("请输入年龄!");
        return false;
    }
    if(!isInt(age.value))
    {
        alert("年龄必须为正整数!");
        return false;
    }
    if(!isEmpty(intro))
    {
        alert("请输入自我介绍!");
        return false;
    }
    alert("提交成功!");
    return true;
}
看,不用每次写那些该死检测逻辑了。。要检测什么只要调用一下已经写好的公共检测方法就行了。轻松吧?!不过,还能更轻松吗?当然!请看:

第三阶段:链式的威力

看着一大堆if else总是心里觉得不舒服,对吧?一串串的又不是羊肉串,虽然不能吃,也要消灭它们!这时候,是Javascript的prototype出场的时候了。通过扩展prototype,可以获得简洁优雅的代码:

Code4
function SubmitInfo()
{
    var name = document.getElementById("txtName");
    var age = document.getElementById("txtAge");
    var intro = document.getElementById("txtIntro");

    if(!name.value.initVerify().isEmpty("请输入用户名!").isSafeString("用户名只能由中文,英文,数字及下划线组成!").verifyComplete())
        return false;
   
    if(!age.value.initVerify().isEmpty("请输入年龄!").isInt("年龄必须为正整数!").verifyComplete())
        return false;
   
    if(!intro.value.initVerify().isEmpty("请输入自我介绍!").verifyComplete())
        return false;
   
    alert("提交成功!");
    return true;
}
很简洁吧?相对前边一大串的羊肉串,是不是顺眼多了啊?哦,如何实现?其实很简单:

Code5
var validateStatus;
var validateMessage;

String.prototype.initVerify = function()
{
    validateStatus = true;
    validateMessage = "";
    return this;
}

String.prototype.isEmpty = function(msg)
{
    if(validateStatus)
    {
        if(this == null || this == "")
        {
            validateStatus = false;
            validateMessage = msg;
        }
    }
    return this;
}

String.prototype.isInt = function(msg)
{
    if(validateStatus)
    {
        if(!/^[1-9]/d$/.test(this))
        {
            validateStatus = false;
            validateMessage = msg;
        }
    }
    return this;
}

String.prototype.isSafeString = function(msg)
{
    if(validateStatus)
    {
        if(!/^[/u4E00-/u9FA5a-z0-9_]*$/gi.test(this))
        {
            validateStatus = false;
            validateMessage = msg;
        }
    }
    return this;
}

String.prototype.verifyComplete = function()
{
    if(!validateStatus)
        alert(validateMessage);
    return validateStatus;
}
怎么样?是不是很简单啊?这个prototype真是一个好东西啊!!链式编程,赞!!呵呵,怎么?你还不满足,代码太长?人心不足啊。我试试吧。。

第四阶段:自定义属性

这个阶段的提交函数如下:

Code6
function SubmitInfo()
{
    if(!Verify(document.getElementById("txtName"))) return false;
    if(!Verify(document.getElementById("txtAge"))) return false;
    if(!Verify(document.getElementById("txtIntro"))) return false;
   
    alert("提交成功!");
    return true;
}
想不通吧?怎么所有检测都一样啊?这不忽悠人吗?哈哈,要实现这个需要在html代码加点酱料:

Code7
    <div>
        <span>请输入用户名,年龄和自我介绍:</span>
        <br />
        <span>用户名:<input type="text" id="txtName" size="20" verifyOptions='{"Empty":{"Flag":false,"Message":"请输入用户名!"},"SafeString":{"Message":"用户名只能由中文,英文,数字及下划线组成!"}}' /></span>
        <br />
        <span>年龄:<input type="text" id="txtAge" size="5" verifyOptions='{"Empty":{"Flag":false,"Message":"请输入年龄!"},"Int":{"Message":"年龄必须为正整数!"}}' />岁</span>
        <br />
        <span>自我介绍:</span>
        <br />
        <span><textarea id="txtIntro" rows="10" cols="50" verifyOptions='{"Empty":{"Flag":false,"Message":"请输入自我介绍!"}}' ></textarea></span>
        <br />
        <span><input type="button" οnclick="return SubmitInfo();" value="提交信息" /></span>
    </div>
看到了吧,我们自定义了个叫verifyOption的属性,就是根据它们实现的分别对待的。就好像每个人都有银行卡,但是里面的钱都不一样一样(这个比喻好像比较牵强^_^)

还是看看Verify函数到底干了什么东西吧:

Code8
function Verify(obj)
{
    if(obj.attributes["verifyOptions"] == undefined)
    {
        alert("请定义verifyOptions!")
        return false;
    }
    var options = obj.attributes["verifyOptions"].nodeValue;
    if(!options.isEmpty("检测参数不正确!"))
        return false;
   
    options = options.parseJSON();
   
    if(options.Empty != undefined && options.Empty.Flag == false)
    {
        if(!obj.value.isEmpty(options.Empty.Message))
            return false;
    }
    if(options.Int != undefined)
    {
        if(!obj.value.isInt(options.Int.Message))
            return false;
    }
    if(options.SafeString != undefined)
    {
        if(!obj.value.isSafeString(options.SafeString.Message))
            return false;
    }
    return true;
}
就是检测自定义属性里面的设置,根据设置来进行相应检测。这下大家满足了吧?每次检测都一律一句Verify(*)就搞掂了!!QA的MM说检测不对?哦,不用急不用急,修改一下自定义属性verifyOption就好了。哈哈~~~

第五阶段:可配置

怎么还有第五阶段?疯狂了疯狂了~~(小子去死!!番茄,鸡蛋都丢上来了~~)唉,大家要注意文明礼貌嘛,丢着我没有关系,丢着花花草草也不好嘛。

Code9
<div>
    <span>请输入用户名,年龄和自我介绍:</span>
    <br />
    <span>用户名:<input type="text" id="txtName" size="20" /></span>
    <br />
    <span>年龄:<input type="text" id="txtAge" size="5" />岁</span>
    <br />
    <span>自我介绍:</span>
    <br />
    <span><textarea id="txtIntro" rows="10" cols="50"></textarea></span>
    <br />
    <span><input id="btnSubmit" type="button" value="提交信息" /></span>
</div>
第五阶段的html代码已经回复清爽了,毕竟自定义属性好像不太友善,不标准!既然不标准就放弃吧!是不是有同学怀疑,这样检测函数岂不是要写更对逻辑,一定比较臃肿吧?好吧,大家看检测函数:

Code10
function SubmitInfo()
{
    if(!VerifyComplete())
        return false;
   
    alert("提交成功!");
    return true;
}
不是吧??骗人的吧?但是,事实如此,第五阶段就是这样简洁,这样优雅的实现了和上边几个阶段同样的功能。不相信,那就来看看吧!不过,第五阶段为了方便,引用了JQuery,找东西,它的搜索器还真好用。

这个阶段我们的检测配置都写到一个变量里面了:

Code11
var verifyConfig = [
{"Id":"txtName", "Option":{"Empty":{"Flag":false,"Message":"请输入用户名!"},"SafeString":{"Message":"用户名只能由中文,英文,数字及下划线组成!"}}},
{"Id":"txtAge", "Option":{"Empty":{"Flag":false,"Message":"请输入年龄!"},"Int":{"Message":"年龄必须为正整数!"}}},
{"Id":"txtIntro", "Option":{"Empty":{"Flag":false,"Message":"请输入自我介绍!"}}}];
这个变量我叫配置变量,建议大家将这些变量都放到同一个文件,叫verifyConfig.js?反正我是这么叫的,要修改检测逻辑,就修改这个配置文件就好了,当然,例子由于要强调第五阶段的简洁,就将VerifyComplete()函数参数设置为空,其实它应该有一个参数,用来传入设置变量。这样才通用。

最后奉上VerifyComplete()函数:

Code12
function VerifyComplete()
{
    var controls = $(":input");
    var verifyFlag = true;
    $.each(controls, function(i, n) {
        if(verifyFlag)
            verifyFlag = Verify(n);
    });
    return verifyFlag;
}


function Verify(obj)
{
    var options;
    $.each(verifyConfig, function(i, n) {
        if(n.Id == obj.id)
            options = n.Option;
    });
   
    if(options == undefined)
        return true;
   
    if(options.Empty != undefined && options.Empty.Flag == false)
    {
        if(!obj.value.isEmpty(options.Empty.Message))
            return false;
    }
    if(options.Int != undefined)
    {
        if(!obj.value.isInt(options.Int.Message))
            return false;
    }
    if(options.SafeString != undefined)
    {
        if(!obj.value.isSafeString(options.SafeString.Message))
            return false;
    }
   
    return true;
}
斗胆第一次上首页,希望能得到大家指点,进入第六阶段。第六阶段是怎么样?我还真想不到!

附上上述完整代码:点击下载

从丑陋到优雅,让代码越变越美续集之服务器端数据校验
数据校验是两方面的,客户端校验虽然可以大大减少服务器回调次数提升用户体验.但是客户端校验并不是万能的,从原理上说,客户端返回的数据都是不可信任的,服务器端校验必不可少.


总的来说,服务器端代码也经历了相似的几个过程:

以判断一个输入是否是可以转换成整数为例,开始大家都会续项强写:

Code1
string str = txtTest.Text;
if (!string.IsNullOrEmpty(str))
{
    int? intResult = 0;
    if (int.TryParse(str, out intResult))
    {
        if(intResult>0 && intResult<100)
        {
            //success
        }
        else
        {
            ShowMessage("输入必须大于0小于100");
        }
    }
    else
    {
        ShowMessage("不能格式化为Int类型");
    }
}
else
{
    ShowMessage("输入为空");
}
看着就够麻烦,然后大家都会总结经验,将检测写成一个一个的函数:

Code2
protected bool IsInt(string str)
{
    if (!string.IsNullOrEmpty(str))
    {
        int? intResult = 0;
        if (int.TryParse(str, out intResult))
        {
            return true;
        }
    }

    return false;
}

protected bool IsInRange(int max,int min,int input)
{
    if (input > min && input < max)
        return true;
    else
        return false;
}
然后轻松调用:

Code3
if (IsInt(str))
{
    if(IsInRange(100,0,int.Parse(str))
    {
        //success
    }
    else
    {
        ShowMessage("输入必须大于0小于100");
    }
}
else
{
    ShowMessage("不能格式化为Int类型");
}
这样界面果然清爽多了..但是这样就满足了吗? 当然不是,这样的写法还是有很多缺点的,例如一大堆各种类型的判断函数很难记得住,加的方法多了还很容易重复,还有例如随着判断条件的增多那一堆"If"就让人火大~不优雅啊!!在这个追求优雅的时代,怎么能让这样的代码出在我们追求完美的人的手中? 一定要干掉它们!

下面,我们就介绍一种经过改良的客户端检测解决方案:

Code4
vInfo info = vHelper.StartVerify(txtTest.Text)
    .IsNotNullOrEmpty("输入为空")
    .IsInt("不能格式化为Int类型")
    .Min(0, "输入小于0")
    .Max(100, "输入大于100")
    .EndVerify();

ShowMessage(info);
上边这个是检测字符串是否只能转换成int并在0到100范围的.下边这个是检测int类型是否属于0到100范围的.

Code5
vInfo info = vHelper.StartVerify(10)
    .IsNotNullOrEmpty("输入为空")
    .Min(0, "输入小于0")
    .Max(100, "输入大于100")
    .EndVerify();

ShowMessage(info);
相似吧? 无论在检测的数据是任何类型,都只是同一种写法,浅显易记又优雅(囧!),返回的vInfo是一个贫血的实体类:

Code6
public class vInfo
{
    private bool _Status;

    public bool Status
    {
        get { return _Status; }
        set { _Status = value; }
    }

    private string _Message;

    public string Message
    {
        get { return _Message; }
        set { _Message = value; }
    }
}

返回info是因为我们在后台代码中,往往需要根据检测结果做其他业务方面的处理,如果你不需要返回信息,并不想单独在外边写ShowMessage函数,可以在vInfo里面增加一个ShowMessage函数,让贫血的实体类不在贫血,实现像下面这样看起来更优雅的检测:

Code7
vInfo info = vHelper.StartVerify(10)
    .IsNotNullOrEmpty("输入为空")
    .Min(0, "输入小于0")
    .Max(100, "输入大于100")
    .EndVerify()
    .ShowMessage();
当然,不写在实体类,写在我们的检测基类中也可以.不过,ShowMessage的方式往往涉及具体业务逻辑,个人认为还是放到外层比较合适.

废话不说了.先看看我们是如何实现这个检测的,我们先定义个检测基类:

Code8
public class v
{
    protected bool Status;
    protected string Message;

    public vInfo EndVerify()
    {
        vInfo info = new vInfo();
        info.Status = Status;
        info.Message = Message;
        return info;
    }
}
基类只有一个EndVerify(),这个函数用来返回具体检测结果信息.然后,我们根据各种检测类型构建不同的检测子类.校验不是一个类在战斗,这一刻,校验被工厂模式灵魂附体.....成功了,校验成功了,成功不是靠一个类实现的,他不是一个类....(sorry,扯远了..^_^)

String类型检测子类:

Code9
public class vString:v
{
    string stringT = null;

    public vString(string T)
    {
        stringT = T;
        Status = true;
        Message = string.Empty;
    }

    public vString IsNotNullOrEmpty(string msg)
    {
        if (string.IsNullOrEmpty(stringT))
        {
            Status = false;
            Message = msg;
        }

        return this;
    }

    public vString IsInt(string msg)
    {
        if (!Status)
            return this;

        int intTemp = 0;
        Status = int.TryParse(stringT, out intTemp);
        if (!Status)
            Message = msg;

        return this;
    }

    public vString Min(int min, string msg)
    {
        if (!Status)
            return this;

        if (int.Parse(stringT) < min)
        {
            Status = false;
            Message = msg;
        }

        return this;
    }

    public vString Max(int max, string msg)
    {
        if (!Status)
            return this;

        if (int.Parse(stringT) > max)
        {
            Status = false;
            Message = msg;
        }

        return this;
    }
}

int类型检测子类:

Code10
public class vInt:v
{
    int? intT = null;

    public vInt(int? T)
    {
        intT = T;
        Status = true;
        Message = string.Empty;
    }

    public vInt IsNotNullOrEmpty(string msg)
    {
        if (intT == null)
        {
            Status = false;
            Message = msg;
        }

        return this;
    }

    public vInt Min(int min,string msg)
    {
        if (!Status)
            return this;

        if (intT < min)
        {
            Status = false;
            Message = msg;
        }

        return this;
    }

    public vInt Max(int max,string msg)
    {
        if (!Status)
            return this;

        if (intT > max)
        {
            Status = false;
            Message = msg;
        }

        return this;
    }
}
当然..还有更多更多的类在战斗..不过出场机会就是每个类都有.世界是不公平的,总是一部分人先富起来,然后更大部分的人穷下去......Sorry,牢骚了.各位大侠看明白就可以了.废话的看过就算了吧,忘了吧.

最后,万事俱备只欠东风了..粮草兵马都有了,就差一个大将军了,大将军可是调动各个部队的灵魂人物,没有他仗就打不胜利了..下边有请我们的的大将,三军调度员,总设计师,联络官,发言人....(旁白:靠,小样你有完没完啊!!)

Code11
public class vHelper
{
    int? intT = null;
    string stringT = null;

    public static vInt StartVerify(int? T)
    {
        return new vInt(T);
    }

    public static vString StartVerify(string T)
    {
        return new vString(T);
    }
}
vHelper类就是工厂类,它重载了StartVerify()函数,并根据参数的类型调度不同的检测类来完成检测过程.

怎么样?很简单吧,只要扩展得好,检测不再是难事了吧? 把它们封装到一个dll中吧,这样,你就可以随时随地地校验了.

(当然,没有最好只有更好,小弟在坐井观天呢,您有更好的方法吗?欢迎您指教!另外,对于链式编程,VS2005的智能提示好像有Bug.总不显示提示,不知道VS2008有没有修复.)

最后附上完整代码: 点击下载

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/fengart/archive/2009/04/01/4040265.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值