架构深渊

慢慢走进程序的深渊……关注领域驱动设计、测试驱动开发、设计模式、企业应用架构模式……积累技术细节,以设计架构为宗。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

服务端与客户端验证一体化方案(附项目源码)

Posted on 2009-04-27 19:57  chen eric  阅读(1001)  评论(1编辑  收藏  举报

 这篇文章发出来有段时间了,不过看的人不多,建议的人也少!

今天在首页看到有朋友把验证方案发到首页去了,为了赚些评论我也反这边编辑下发到首页上给大家扔些砖!

一、起源

      在项目开发中数据有效性验证肯定是必须的,那么在哪里验证呢!?怎么去验证呢?

      针对web项目而言,客户端验证+服务端的验证缺一不可,客户端的脚本验证用于提高用户体验!

      服务端的验证主要是为了数据的安全性、合法性的验证!

      但是我们在实施这两种验证的时候会发现有以下几个可能出现的问题:

            1.客户端代码验证写起来相对比较烦琐,而且易出错!(主要是拼写错误,例如js方法名拼写错误)

            2. 服务端的验证和客户端验证要保持一致性一样很烦琐。(客户验证输入不能超过10个字符,那后服务器端也要相应的控制在10个字符,而且修改时,需要将客户端和服务端同步修改,提示信息,前后端都要再输入一次!一个字“烦”),总觉得时间不应该花在这个上面!

            3.验证与赋值;一般情况下客户端提交的数据,我们验证通过后,把变量的值赋给实体或是相关的变量,如果不通过有些时候我们有时候还会需要给变量或实体属性值加上一个默认值,这个时候我们写的代码可能不避免的出现一些if的判断语句,让代码显得不够简洁。

            其它的细节问题,这里就不一一列举了,我针对上面的问题,自己做了一个下面的小组件!

二、 思路说明:

        为了解决第一个问题和第二个问题,我决定客户端的验证代码由服务端的验证代码直接生成页面中,这样可以避免客户端的代码在编写上的错误!而服务端的程序代码每次被访问时,通过验证状态开关来开启验证,用户可以根据自己的业务逻辑来开启验证,我先定义了一个验证容器ValidatorContainer和一个验证器Validator两个概念,验证容器是指包括了一个或多个验证器对象的容器类,验证器是针对每一条数据项所添加的验证信息类!

三、调用说明:

         可能这一部分的内容可以让你更直观的了解这个验证组件的一些原理和信息:

         第一步:先添加引用,这个不用多说,Validator.dll引用进去(最下面附有项目的源码)。

         第二步:代码都在下面了,不用再说了

          cs页面的代码:

using Validator;
using My.Framework.Utility;
public partial class _Default : System.Web.UI.Page
{
    
protected ValidatorContainer vc;
    
protected void Page_Load(object sender, EventArgs e)
    {
        
//是否开启服务端验证
        bool isStartCheck = false;
        
if (Request["command"== "postBack")
        {
            
/*-------------------------------------------------------------------------------------
             * 此处的bool值主要控制验证容器处在何种状态
             * 状态false:只注册客户端代码不实施服务端的验证
             * 状态true:验证客户端的数据并生成客户端代码字符串
             * -------------------------------------------------------------------------------
*/
            isStartCheck 
= true;
        }

        
//初始化验证容器的集合,并传入isStartCheck来控制验证的开启
        vc = new ValidatorContainer(isStartCheck);

        
//设置当前的验证组(些处可以根据你自己的页面逻辑来选择验证,不设置为验证所有)
        vc.GroupName = "A";

        
//获取并验证客户端的数据
        int month = vc.Add(Validator<int>.Init(MyRequest.GetInt("monthTextBox",0)))//注意这一行,我们需要把验证器放置到对应的容器中再进行验证
            .Integer("月份必须为1到12的整数")
            .Range(
1,12,"月份必须在1到12月之间")//验证数字和范围
            .SetGroupName("A")//设置其所属的验证组,可以不设置(不设置会被分配到default组)
            .RegClientValidator("monthTextBox""请输入出生月""monthErrorSpan")//将验证代码注册到客户端页面(也可以选择不注册)。
            
//参数说明,参数1:"monthTextBox"表示html控件的id,[参数2]:"请输入出生月"表示提示Div中的文字(该参数为重载),[参数3]:"monthErrorSpan"表示错误信息所显示的框(该参数为重载)
            .Retrun(1);//返回验证通过后的值,否则返回默认值(此处返回的是1);


        
string userName = vc.Add(Validator<string>.Init(MyRequest.GetString("userNameTextBox")))
            .Chinese(
"用户名必须为中文!")
            .Length(
4,16,"用户名必须在2到8个字以内!")
            .SetGroupName(
"A")
            .RegClientValidator(
"userNameTextBox")
            .Retrun(
"给你个默认值");


        
string rePostDateString = vc.Add(Validator<string>.Init(MyRequest.GetString("rePostDateTextBox")))
            .Date(
"日期格式不正确")
            .Range(DateTime.Now, DateTime.MaxValue, 
"重发日期不能少于当前日期!")//这个功能客户端的还没写
            .SetGroupName("A")
            .IsRequired(
false)//设置其为非必填项
            .RegClientValidator("rePostDateTextBox","这个是非必填项,当然你如果要填的话,我肯定是要验证的啦!")
            .Retrun(
"2009-12-12");

        
//注册客户端代码到页面中,这里必须要声明运行于服务端的Header或是Form才能进行注册
        vc.RenderClinetValidator();

        
//判断验证是否通过
        if (isStartCheck)
        {
            
if (vc.IsAllPass)
            {
                
//你页面要做的逻辑
            }
        }
    }
}

 

aspx页面的调用代码:


<head>
<!--这里加runat="server"或是如现在的页面有一个 <form id="form1" runat="server">是runat='server'的-->
    
<title>验证器测试页</title>
</head>
<body style="padding-top:50px; background-color:Black">
    
<form id="form1" runat="server">
    
<div>
    
<br />
        请输入出生月份:
<input type="text" id="monthTextBox" value="aa" /><span id="monthErrorSpan" style=" background-color:Red">这里将用来显示验证反馈信息</span><br />
        请输入用户昵称:
<input type="text" id="userNameTextBox" value="aa" /><br />
        更新日期:
<input type="text" id="rePostDateTextBox" value="aa" /><br />
        
<input type="button" id="" value="统一验证" onclick="<%=vc.Checked("A")%>" />
<!--上面这个地方就是调用的地方,"<%=vc.Checked("A")%>"会产生一个js调用函数,A代表要验证的组,产生的这个调用的函数执行完会返回一个表单是否验证通过的bool值 -->

    
</div>
    
</form>
</body>
</html>

 

上面这段代码就实现了客户端验证代码的注册及调用,应该来说还是比较简单的!

上面的调用上都写了相关的注释,如果有不明确的地方接着往下看:

 

  //是否开启服务端验证
        bool isStartCheck = false;
        
if (Request["command"== "postBack")
        {
            
/*-------------------------------------------------------------------------------------
             * 此处的bool值主要控制验证容器处在何种状态
             * 状态false:只注册客户端代码不实施服务端的验证
             * 状态true:验证客户端的数据并生成客户端代码字符串
             * -------------------------------------------------------------------------------
*/
            isStartCheck 
= true;
        }

        
//初始化验证容器的集合,并传入isStartCheck来控制验证的开启
        vc = new ValidatorContainer(isStartCheck);

        
//设置当前的验证组(些处可以根据你自己的页面逻辑来选择验证,不设置为验证所有)
        vc.GroupName = "A";

 

上面这段代码里面是初始化开关条件变量以及初始化验证器,以及验证的组名,这个地方组名其实是非必须的,如查不添加这个组名,组件会自动分

配到"Default"组中;在new  ValidatorContainer(isStartCheck)中的isStartCheck是用为启动验证的,如果这个值是false的话,那么

ValidatorContainer就只会生成客户端代码而不执行数据有效性验证!

接下来再看下面的代码:

 

 //获取并验证客户端的数据
        int month = vc.Add(Validator<int>.Init(MyRequest.GetInt("monthTextBox",0)))//注意这一行,我们需要把验证器放置到对应的容器中再进行验证
            .Integer("月份必须为1到12的整数")
            .Range(
1,12,"月份必须在1到12月之间")//验证数字和范围
            .SetGroupName("A")//设置其所属的验证组,可以不设置(不设置会被分配到default组)
            .RegClientValidator("monthTextBox""请输入出生月""monthErrorSpan")//将验证代码注册到客户端页面(也可以选择不注册)。
            
//参数说明,参数1:"monthTextBox"表示html控件的id,[参数2]:"请输入出生月"表示提示Div中的文字(该参数为重载),[参数3]:"monthErrorSpan"表示错误信息所显示的框(该参数为重载)
            .Retrun(1);//返回验证通过后的值,否则返回默认值(此处返回的是1);

这个里面我们可以看出来,我们用vc.Add(Validator<T> v)方法来往验证器里面添加验证器,特别注意的地方是

Validator本身具备了Integer、Range 等方法,但是我们这里不可以直接调用,如果直接调用,在这里它的验证结果将不能被vc容监控到!

所以要特别注意。

Validator<int>.Init(MyRequest.GetInt("monthTextBox",0))

这小段代码是为MyRequest.GetInt(“monthTextBox",0)这个整数做一个校验,这个地方这个方法是另一个组件里的,大家应该看得明白,用

来获取客户端的一个提交值的,如果不为整数的话,默认为0,这个地方有点多余,因为验证器里本来就有一个针对整数的验证了,这个我们就先不

管它了,反正我这个组件的思路是不相干的!

后面调用的Integer、Range 等方法就不多做解释了,它们只是一个验证方法而已,关键RegClientValidator方法,这个方法有三个重载版本。

RegClientValidator(string elementName);这个表示为html页面中id或是name为elementName的html控件添加js验证的脚本代码,(注:这一步其实只做了一个脚本代码的生成,并没有输出。)

RegClientValidator(string elementName,string tipString);这个重载表示,为页面控件添加验证,并在输入时添加提示信息,提示信息的位置现在是

默认的,在控件的上方(具体效果如图,我会在以后的代码更新中会加入一个可以自定义显示的位置,给用灵活的配置或是使用如图的默认)

如果不使用这个重载,自然没有这个提示信息了!

然后讲第三个重载:RegClientValidator(string elementName,string tipString,string errspanName)

这个重载前两上参数都讲过了,第三个参数其实是控制是否在自定义的html元素里面显示错误提示信息

我代码中"monthErrorSpan"其实就是图上红色的那块区域的html元素的id,这样如果验证不通过,信息将在红色区域中显示,如果没有使用这个重载,

我们会自动生成一个div来显示,效果如下图:

注:这个错误提示的内容就是在cs文件里面写的那些字符串,在哪一下的验证中不通过,就会显示相应的错误提示!

 接下来看:Retrurn(T tValue);

这个方法其实就是用来返回值的,意思就是如果验证通过返回用户提交的数据给接收变量,如果不通过将Retrun(T tValue)中定义的值赋给接收变量,这个应该比较好理解,如果你没有定义接收变量,这个方法也可以省略~

  然后再看这两个方法

.SetGroupName("A")
           

.IsRequired(false)//设置其为非必填项

上面那个SetGroupName("A")表示把该验证添加到A组中,其实这个地方我要声明的是,一般90%甚至更多的页面的验证是不需要分组的,当然这个也用不着,我在这个地方只是为以演示这个分组功能才加上去的,分组验证的情况,有用过的朋友,估计会明白,我这个地方就不多说了,不需要分组的就不用调用这个方法咯!

下面这个IsRequired(false)这个字面上应该也很好理解,就是设置成非必填项,当然如果用户输入了的话,还是会有验证的!

好了,上面对该解释的都解释了,现在看最后的几代码:

     //注册客户端代码到页面中,这里必须要声明运行于服务端的Header或是Form才能进行注册
        vc.RenderClinetValidator();

        
//判断验证是否通过
        if (isStartCheck)
        {
            
if (vc.IsAllPass)
            {
                
//你页面要做的逻辑
            }
        }

 

 vc.RenderClientValidator();这个方法做的工作是客户端验证生成的最后一步,它负责把容器里所有的客户端验证代码生成后写入到html代码中~!

有多关键,不用我说了吧!

下面vc.IsAllPass这个也不用说了,就是容器里的验证是否都通过了!

差不多了,剩下的代码就是前端的调用,这个地方有些不合理,我估计在下一个更新中会处理掉!

 

<input type="button" id="" value="统一验证" onclick="<%=vc.Checked("A")%>" />

 

客户端的代码生成后,在什么地方激活,怎么调用,要看客户的需求了,这个地方需要调用vc.Checked(string groupName)来生成js调用代码!

没有这一步的话,程序只会为失去焦点做验证,提交的时候当然就不会做了!

说了蛮多的,不过感觉就是没说清楚!

具体的实现,我挑一两个方法贴出来,更详细的就下载下面的源码吧!最近一直没时间改,这个写了个把月了,一起没有把现在有的问题更新!

不哆嗦了,看代码:

验证器的其中一个验证项方法:

      

  #region 验证数字(int,double)
        
/// <summary>
        
/// 验证数字(int,double)
        
/// </summary>
        
/// <param name="errorMessage">错误提示字符串</param>
        
/// <returns></returns>
        public Validator<T> Double(string errorMessage)
        {
            tempJsonString 
= new StringBuilder();
            tempJsonString.Append(
"Double:{");
            tempJsonString.Append(ConvertToJsonElement(
"Msg", errorMessage));
            tempJsonString.Append(
"}");
            jsonList.Add(tempJsonString.ToString());
            
//判断是否需要忽略验证
            if (ignore)
                
return this;
            
//验证
            if (!IsPattern(validatorRegexs[2]))
            {
                
this.Reject(errorMessage);
            }
            
return this;
        }

        
#endregion

 

验证容器的:

 

 

using System;
using System.Collections.Generic;
using System.Text;
using Validator;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Security.Permissions;


namespace Validator
{
    [AspNetHostingPermission(SecurityAction.Demand, Level 
= AspNetHostingPermissionLevel.Minimal)]
    
public class ValidatorContainer : IObserver
    {
        
/// <summary>
        
/// 是否开启验证
        
/// </summary>
        private bool isStartCheck;

        
/// <summary>
        
/// 验证器所在页对象
        
/// </summary>
        private Page cPage;

        
/// <summary>
        
/// 客户端验证器对象名
        
/// </summary>
        private string clientValidatorName;

        
/// <summary>
        
/// 验证组名
        
/// </summary>
        private string groupName;

        
/// <summary>
        
/// 错误数量
        
/// </summary>
        //private int errorCount;

        
/// <summary>
        
/// 前台呈现脚本代码
        
/// </summary>
        private StringBuilder renderString;

        
/// <summary>
        
/// 验证器列表
        
/// </summary>
        private List<ISubject> validatorList;

        
/// <summary>
        
/// 是否开启验证
        
/// </summary>
        public bool IsStartCheck
        {
            
get { return isStartCheck; }
            
set { isStartCheck = value; }
        }

        
/// <summary>
        
/// 客户端验证器对象名
        
/// </summary>
        public string ClientValidatorName
        {
            
get { return clientValidatorName; }
            
set { clientValidatorName = value; }
        }

        
/// <summary>
        
/// 验证组名
        
/// </summary>
        public string GroupName
        {
            
get { return groupName; }
            
set { groupName = value; }
        }

        
#region 是否通过验证
        
/// <summary>
        
/// 是否通过验证
        
/// </summary>
        public bool IsAllPass
        {
            
get { return GetPassStat(); }
        }

        
/// <summary>
        
/// 获取验证状态
        
/// </summary>
        
/// <returns></returns>
        private bool GetPassStat()
        {
            
foreach (ISubject o in validatorList)
            {
                
if (!o.IsPass)
                    
return false;
            }
            
return true;
        }
        
#endregion

        
/// <summary>
        
/// 是否开启验证
        
/// </summary>
        
/// <param name="_isStartCheck"></param>
        public ValidatorContainer(bool _isStartCheck)
        {
            validatorList 
= new List<ISubject>();
            clientValidatorName 
= "Validator";
            
this.isStartCheck = _isStartCheck;
        }

        
/// <summary>
        
/// 验证器列表
        
/// </summary>
        public List<ISubject> ValidatorList
        {
            
get { return validatorList; }
            
set { validatorList = value; }
        }

        
/// <summary>
        
/// 添加字符串验证器
        
/// </summary>
        
/// <param name="_validator">字符串验证器对象</param>
        public Validator.Validator<string> Add(Validator.Validator<string> _validator)
        {
            
if (this.groupName != "_All")
                
if (this.groupName != _validator.GroupName)
                    _validator.Ignore 
= true;
            validatorList.Add(_validator);
            
return _validator;
        }

        
/// <summary>
        
/// 添加整数验证器
        
/// </summary>
        
/// <param name="_validator">整数验证器对象</param>
        public Validator.Validator<int> Add(Validator.Validator<int> _validator)
        {
            
if (this.groupName != "_All")
                
if (this.groupName != _validator.GroupName)
                    _validator.Ignore 
= true;
            validatorList.Add(_validator);
            
return _validator;
        }

        
/// <summary>
        
/// 获取所有分组的验证不通过数量
        
/// </summary>
        
/// <returns></returns>
        public int GetErrorCount()
        {
            
return 0;
        }

        
/// <summary>
        
/// 获取某分组验证不能过数目
        
/// </summary>
        
/// <param name="_groupName"></param>
        
/// <returns></returns>
        public int GetErrorCount(string _groupName)
        {
            
return 0;
        }

        
#region 实现观察者接口
        
public void GetValidatorStat(ISubject subject)
        {
            
//根据subject中的信息,记录到汇总信息中
            
//subject.GroupName;
            
//subject.Ignore;
            
//subject.IsPass;
            
//subject.ErrorMessage;
        }
        
#endregion


        
/// <summary>
        
/// 生成客户端验证json代码
        
/// </summary>
        
/// <returns></returns>
        public void RenderClinetValidator()
        {
            RenderClinetValidator(
null);
        }

        
/// <summary>
        
/// 是否存在页面注册区域
        
/// </summary>
        
/// <returns>存在的区域枚举</returns>
        private FormOrHeader GetRegisterRegion()
        {
            cPage 
= (Page)HttpContext.Current.Handler;
            
if (cPage.Header != null)
            {
                
return FormOrHeader.Header;
            }
            
else if (cPage.Form != null)
            {
                
return FormOrHeader.Form;
            }
            
return FormOrHeader.Null;
        }

        
/// <summary>
        
/// 生成客户端验证json代码
        
/// </summary>
        
/// <param name="_clientValidatorName">客户端验证器名称</param>
        
/// <returns></returns>
        public void RenderClinetValidator(string _clientValidatorName)
        {
            FormOrHeader formOrHeader 
= GetRegisterRegion();
            
if (formOrHeader == FormOrHeader.Null)
                
throw new Exception("对不起,页面不存在必须存在标记为\"runat=server\"的Form或是Header标记!请确认!");

            
//设置客户端验证对象名
            if (!string.IsNullOrEmpty(_clientValidatorName))
                
this.clientValidatorName = _clientValidatorName;

            
//按组名排序
            validatorList.Sort(new GroupComparer());

            renderString 
= new StringBuilder();
            renderString.AppendLine(
"<script type='text/javascript'>");
            renderString.AppendLine(
"var " + clientValidatorName + ";");
            renderString.AppendLine(
"function InitValidator(){");
            renderString.AppendLine(clientValidatorName 
+ "=new MyValidator(" + clientValidatorName + "Container);");
            renderString.AppendLine(
"}");
            renderString.AppendLine(
"BindEvent(window,'onload',InitValidator);");
            renderString.AppendLine(
"var " + clientValidatorName + "Container={");
            
int i = 0;
            
string currentGroupName;
            
string preGroupName;
            
string nextGroupName;
            
int totalCount = validatorList.Count;
            
foreach (ISubject o in validatorList)
            {
                
//设置组名
                currentGroupName = validatorList[i].GroupName;
                preGroupName 
= validatorList[(i - 1< 0 ? 0 : (i - 1)].GroupName;
                nextGroupName 
= validatorList[(i + 1> totalCount - 1 ? totalCount - 1 : (i + 1)].GroupName;
                
if (i == 0)
                    renderString.Append(currentGroupName 
+ ":{");
                
if (currentGroupName != preGroupName)
                {
                    renderString.Append(
"}");
                    renderString.Append(
"," + currentGroupName + ":{");
                }

                i
++;
                renderString.Append(o.JsonString);
                
if (i != validatorList.Count)
                    
if (currentGroupName == nextGroupName)
                        renderString.Append(o.JsonString 
== string.Empty ? "" : ",");
            }
            renderString.Append(
"}");
            renderString.AppendLine(
"};");
            renderString.AppendLine(
"</script>");

            
//加载图片
            string imageTemplate = "<img id='falseIcon' src='{0}' style='display:none'>";
            LiteralControl imageUrlLiterl 
= new LiteralControl(string.Format(imageTemplate, cPage.ClientScript.GetWebResourceUrl(this.GetType(), "Validator.Resources.icons.gif")));
            cPage.Page.Controls.AddAt(
2, imageUrlLiterl);
            
//装载样式
            string includeTemplate = "<link rel='stylesheet' text='text/css' href='{0}' />";
            
string includeLocation = cPage.ClientScript.GetWebResourceUrl(this.GetType(), "Validator.Resources.validator.css");
            LiteralControl includeLocationLiteral 
= new LiteralControl(String.Format(includeTemplate, includeLocation));
            
//装载js
            string jsTemplate = "<script src='{0}' type='text/javascript'></script>";
            
string scriptLocation = cPage.ClientScript.GetWebResourceUrl(this.GetType(), "Validator.Resources.MyValidator.js");
            LiteralControl scriptLocationLiteral 
= new LiteralControl(String.Format(jsTemplate, scriptLocation));


            
if (formOrHeader == FormOrHeader.Header)
            {
                cPage.Header.Controls.Add(includeLocationLiteral);
                cPage.Header.Controls.Add(scriptLocationLiteral);
                cPage.Header.Controls.Add(
new LiteralControl(renderString.ToString()));
            }
            
else
            {
                cPage.Controls.AddAt(
1, includeLocationLiteral);
                cPage.Controls.AddAt(
1, scriptLocationLiteral);
                cPage.ClientScript.RegisterClientScriptBlock(cPage.GetType(), 
this.clientValidatorName, renderString.ToString());
            }
        }

        
/// <summary>
        
/// 验证所有组
        
/// </summary>
        
/// <returns>组验证调用函数</returns>
        public string Checked()
        {
            
return Checked("_All");
        }

        
/// <summary>
        
/// 验证特定组
        
/// </summary>
        
/// <param name="groupName">组名("_All"表示验证所有组)</param>
        
/// <returns>组验证调用函数</returns>
        public string Checked(string _groupName)
        {
            
string checkGroup = "_All";
            
if (!string.IsNullOrEmpty(_groupName))
                checkGroup 
= _groupName;
            
return clientValidatorName + ".ValidateByGroup('" + checkGroup + "');";
        }
    }

    
/// <summary>
    
/// Form或Header
    
/// </summary>
    public enum FormOrHeader : byte
    {
        
/// <summary>
        
/// 头部
        
/// </summary>
        Header,
        
/// <summary>
        
/// 表单
        
/// </summary>
        Form,
        
/// <summary>
        
/// 无
        
/// </summary>
        Null
    }
}

集成在项目中的js的代码我也不贴了,这些js的引用及css的引用,程序都包办了,调用肯定只要引用Validator.dll就可以了!

代码质量不高,想到哪写到哪,一直没有时间整理,到时整理出来完善版的再发上来,希望大家给予批评!

真诚的感谢您抽时间来看我的文章!

谢了!

 

源码发上来,有兴趣的可以下去看看,(注:TestWeb是上面这个测试页面的站点,另外一个大家自己猜是什么好了!)

 一体化验证源码