[joe 原创]web control 开发系列(四) validation机制_第1页
[joe 原创]web control 开发系列(四) validation机制_第2页
[joe 原创]web control 开发系列(四) validation机制_第3页
[joe 原创]web control 开发系列(四) validation机制_第4页
[joe 原创]web control 开发系列(四) validation机制_第5页
已阅读5页,还剩16页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

/joeliu/archive/2008/11/13/1240206.html Joe 原创Web Control 开发系列(四) Validation 机制 前言: 前一段时间写 Web Control 开发系列的文章,后来由于工作实在忙,就 没有继续写了,如今我要继续写下去,研究了微软的 Web Control 体系结构这 么久,我有一个总体的感觉,就是微软把所有自己认为有用的东西,无论大小, 都设计了,都实现了,以至于我们能发挥的空间很有限了,一旦我们设计一个 自认为更好的结构,虽然确实很好,但是因为和微软的结构不一致,也会很难 和微软的其它 Control 协同工作,所以要做一个 Composite Web Control,最 大的功力就是要彻底弄清楚微软的 Control 体系结构,以求达到 “天人合一 ”的 效果。想起来真是悲哀,我们大量的时间和精力都浪费在学习人家东西上面了, 等你搞熟练了,人家又升级了,所以永远也赶不上,真是悲哀! 正文: WebForm 组件中 Validataion 控件是比较有用的,可以很方便的为我们的应 用程序提供方便的输入校验。这种校验不仅仅在服务器端进行,也同时会在客 户端发生。深入理解 WebForm 的 Validation 机制,对于合理设计我们的 Control 是非常有帮助的,这样可以让我们的 Control 可以和系统的 validator 进行协作。下面我一步一步介绍微软 Validation 的体系结构: 1. IValidator 接口 这个接口的 Member 有:IsValid, ErrorMessage, Validate()。所有的 Validator 必须实现这个接口。在一个 Page 页面上会有一个 ValidatorCollection 类型的属性 Validators,该集合只能添加实现了 IValidator 接口的对象。它负责维护这个页面所有的 Validator。目前系统只有一个类实现 了这个接口,就是 BaseValidator。系统所有的其它的 Validator 类型都是从 BaseValidator 派生的。 2. BaseValidator 类 BaseValidator 实现了 Validator 一些基础的功能,如果我们自己写一个新的 Validator,我建议从这个类派生,如果不从这个类派生,而是直接实现 IValidator 接口 ,从理论上来说是可以的,但是可能会有相当大的挑战,因为 BaseValidator 这个类搭建了.net framework 的整个 Validation 体系结构,即使 自己直接实现 IValidator 接口也必须重写一遍 BaseValidator 所实现的功能, 否则自己写的 Validator 很难表现的和系统的 Validator 效果一致,下面介绍 BaseBalidator 所做的工作 维护 Validator 和 Page 的关系 BaseValidator 是从 Label 派生的,所以也是从 Control 派生的,Control 的 OnInit 函数是在 Control 加入到 Page 的时候执行的,而与之对应 Control 的 OnUnload 函数是在 Control 从 Page 移除的时候执行的,因此如果需要对 Control 一些和 Page 有关的状态初始化的时候,最好在这两个时机处理。 protected internal override void OnInit(EventArgs e) base.OnInit(e); this.Page.Validators.Add(this); protected internal override void OnUnload(EventArgs e) if (this.Page != null) this.Page.Validators.Remove(this); base.OnUnload(e); 通过上面的代码我们可以发现,Validator 总是会在 OnInit 函数里面把自己加 入 Page 的 Validators 集合中,而在 OnUnload 函数里面从 Page 的 Validators 集合中移除自己,这样 Page 上可以拿到所有的 Validators。 实现客户端验证体系 为了实现客户端验证的功能,BaseValidator 必须注册必要的客户端脚本来完成 这个功能。从原理上面讲,就是在客户端也实现了一套和服务器端一样的数据 结构,这个结构包含 Javascript 的 Validator 对象, Validators 集合, 以及 各种 Validator 的 Validate 方法。下面来看看 BaseValidator 是如何搭建这一套 客户端数据结构的。 a. 在 Render 函数中注册当前的 Validator 到客户端的 Validators 集合中。 首先需要把当前页面的所有 Validator 都在客户端生成 JavaScript 的 Validator 对象,这个是通过 BaseValidator 的 RegisterValidatorDeclaration 函数完成的。 protected virtual void RegisterValidatorDeclaration() string arrayValue = “document.getElementById(“ + th is.ClientID + “)“; if (!this.Page.IsPartialRenderingSupported) this.Page.ClientScript.RegisterArrayDeclaration(“ Page_Validators“, arrayValue); else ValidatorCompatibilityHelper.RegisterArrayDeclara tion(this, “Page_Validators“, arrayValue); ValidatorCompatibilityHelper.RegisterStartupScrip t(this, typeof(BaseValidator), this.ClientID + “_DisposeS cript“, string.Format(CultureInfo.InvariantCulture, “rn document.getElementById(0).dispose = function() r n Array.remove(1, document.getElementById(0);r nrn“, new object this.ClientID, “Page_Validators “ ), true); 上面的函数调用 ScriptManager.RegisterArrayDeclaration 在注册一个数组 item,这个操作实际就是在 ScriptManager 的一个指定的数组里面增加一个 item,当 Page Render 到客户端的时候,该数组会自动 Render 为一个客户端 的 JavaScript 的数组声明语句。改语句在直接写在页面里面,当页面加载的时 候立即执行。生成的脚本如下: / / 这里页面上只有一个 validator,所以数组中只有一个 这样 Page 上所有的 Validator 都会在客户端加入 Page_Validators 变量里面。 当页面提交的时候,可以遍历所有 Page_Validators 里面的 Validator,调用其 evaluationfunction 来做客户端校验。 b.在 OnPreRender 函数中首先通过下面的函数判断客户端是否支持脚本 protected virtual bool DetermineRenderUplevel() Page page = this.Page; if (page = null) | (page.RequestInternal = null) return false; return (this.EnableClientScript 如果支持脚本,就调用 RegisterValidatorCommonScript()函数来注册脚本,这 个函数主要做了三件事: 1. 注册.net framework 的脚本文件 WebUIValidation.js (这个文件包含了 客户端 Validator 的主要脚本实现) 2. 注册一段初始化脚本。该脚本会对本 Validator 的客户端对象做一些初始 化的工作。把服务器端 Validator 的属性值 Clone 到客户端 Validator 里面。 3. 注册一段脚本,当 Form 在提交(submit)的时候执行,这是通过 ScriptManger.RegisterOnSubmitStatement 语句完成的,该函数可以在 Form 的 OnSubmit 脚本函数里面插入指定的脚本语句 。这个主要是在 Form 提交 的时候查看页面的 Validation 结果变量,如果为 false,那么阻止提交,否则就 允许提交。 完成上面三个注册任务的是下面函数: protected void RegisterValidatorCommonScript() if (!this.Page.IsPartialRenderingSupported) if (!this.Page.ClientScript.IsClientScriptBlockRe gistered(typeof(BaseValidator), “ValidatorIncludeScript“) this.Page.ClientScript.RegisterClientScriptRe source(typeof(BaseValidator), “WebUIValidation.js“); this.Page.ClientScript.RegisterStartupScript( typeof(BaseValidator), “ValidatorIncludeScript“, “rnrnrnrn “); this.Page.ClientScript.RegisterOnSubmitStatem ent(typeof(BaseValidator), “ValidatorOnSubmit“, “if (type of(ValidatorOnSubmit) = “function“ “); else ValidatorCompatibilityHelper.RegisterClientScript Resource(this, typeof(BaseValidator), “WebUIValidation.js “); ValidatorCompatibilityHelper.RegisterStartupScrip t(this, typeof(BaseValidator), “ValidatorIncludeScript“, “rnrnrnrn “, false); ValidatorCompatibilityHelper.RegisterOnSubmitStat ement(this, typeof(BaseValidator), “ValidatorOnSubmit“, “ if (typeof(ValidatorOnSubmit) = “function“ “); 注册的脚本内容是 / ValidatorOnLoad 函数里面做了下面的事情: 一方面初始化校验函数, if (typeof(val.evaluationfunction) = “string“) / 该操作对 所有的 Validators 执行 eval(“val.evaluationfunction = “ + val.evaluationfunc tion + “;“); / val 就是客户端的 Validator 对象,执行这个代码来给这个对象赋值 一个函数,这个函数就是客户端调用函数 另一方面给 Control 的客户端的 Html 元素(INPUT,TEXTAREA,SELECT 等)挂相应的 event 处理函数,这样当 event 发生的时候,就可以自动调用该 Html 元素关联的所有 validator 的校验脚本函数。 c. 在 AddAttributesToRender 函数里面注册 Client Validator 对象的初始 化脚本,主要调用 ScriptManger.RegisterExpandoAttribute 完成的,这个脚本 也是在页面加载的时候立即执行的。,如下: / 如果设置了更多的属性,生成的脚本会更多,其中 evaluationfunction 的值是 一个 JavaScript 函数的名称,对于.net framework 定义的几种 Validator,这些 函数都定义在 WebUIValidation.js 里面. 通过上面三个地方脚本的注册,Validator 就可以实现了客户端的验证的功能。 客户端验证的过程 1. 验证的过程可以是 IButtonControl(Button ,LinkButton,ImageButton 等) 在提交的时候调用 WebForm_DoPostBackWithOptions()脚本,如果 IButtonControl.CauseValidation=true,那么它就会调用 Page_ClientValidate()函 数,这个是 Validator 客户端体系的一个重要入口,它首先会遍历页面的所有 Validator 对象,依次执行它们的校验函数进行校验,其次会处理 ValidationSummary 对象,设置其输出错误信息。 2.我们通过上面注册脚本的过程了解到在 ValidatorOnLoad()里面调用了 ValidatorHookupControlID()函数,这个函数实际就是监听特殊的 Html Element 的校验时机,这包括 “INPUT“,”TEXTAREA“,“SELECT“ 元素,而 且查找这些元素的逻辑递归向下的,一个都不放过。如何监听分为两种,一种 是 type 为 radio 的 INPUT 元素,通过挂 onclick 事件得到通知,其它的元素都 是挂 onchange 事件得到通知。如果需要在校验失败的时候把焦点置回去,那 么还需要挂 onblur 事件。这样当这些元素的 onclick 或者 onchange 事件发生 的时候,就会执行该 Validator 的校验逻辑。 以上是两个执行客户端校验的时机,下面给出两点建议: 1. 如果我们做一个 Control,该 Control 具有引起页面 Postback 的能力,那么 我们最好通过 ScriptManager.GetPostBackEventReference()函数来注册客户端 的 WebForm_DoPostBackWithOptions()脚本,该脚本不仅仅具备引起 Postback 的能力,还具有执行客户端校验的能力。如果自己通过 Form.submit 来做就和 A 体系结构结合的不是很紧密了。 2. 如何可以让系统的 Validator Control 可以用来在客户端(服务器端后面讲) 校验我们的 Control 呢? 通过分析源码发现,标准的 Validator 总是递归查找 待校验的 Control 所生成的 Html Elment 节点上的 value 属性,然后拿着 value 属性进行校验。因此如果希望我们的 Control 可以用系统的 Validator 进行校验, 就必须在生成的 Html Element 里面有一个节点有 value 属性,而且该属性的 值就是希望被校验的值。 3. Validator 的服务器端工作原理 由上面的分析,可以看出,BaseValidator 类搭建了一套完整的客户端验证体系 结构,但这并不是 IValidator 接口所必须的,IValidator 接口定义的是服务器 端的验证,那么在服务器端 Validator 如何和整个 WebForm 里面的 Control 进 行协作工作的呢?我们从下面三个方面来阐述:Validate 的调用入口, Validate 的调用时机,Validate 的执行过程 Validate 调用入口 在 Page 上管理所有的 IValidator 对象的集合,并且由 Page 执行相应的 Validate 的调用,因此 Validate 调用的入口是在 Page 上面的。通过前面的介 绍,我们了解到,当 Validator 在 OnInit 的阶段,它会把自己添加到 Page 的 Validators 的集合里面,而在 OnUnload 阶段它会把自己从 Page 的 Validators 集合里面移除。Page 上关于 Validate 的调用方法有两个(如下),总体思想很 简单,就是一个遍历。 public virtual void Validate() this._validated = true; if (this._validators != null) / 遍历页面所有的 Validator,进行 Validate 的函数调用 for (int i = 0x0; i this.Validators.Count; i+) this.Validatorsi.Validate(); public virtual void Validate(string validationGroup) this._validated = true; if (this._validators != null) ValidatorCollection validators = this.GetValidato rs(validationGroup); if (string.IsNullOrEmpty(validationGroup) else / 遍历页面所有 validationGroup 为指定值的 Validato r,进行 Validate 的函数调用 for (int i = 0x0; i validators.Count; i+) validatorsi.Validate(); Validate 的调用时机 Validate 的时机仅仅发生在 PostBack 阶段,在我前面的两篇文章中( Web Control 开发系列(二) 深入解析 Page 的 PostBack 过程和 IPostBackDataHandler,Web Control 开发系列(三) 解析 IPostBackEventHandler 和 WebForm 的事件机制 ),我详细讲解了页面在 PostBack 过程中的两个重要阶段:1. ProcessPostData, 2. ProcessPostEvent。如 果对这个不是很了解的话,就需要复习复习了:-) Validate 的调用就是在这两个阶段执行的。 主要调用的地方有三处: SomeControl.RasiePostDataChangedEvent() SomeControl.RaisePostBackEvent(String) Page.RaisePostBackEvent(NameValueCollection nameValueCollection) 当调用 Page 上面的 Validate 方法的时候,需要了解引起 PostBack 的起源,这 样的起源大致有两类,一类是实现了 IPostBackDataHandler 或者 IPostBackEventHandler 接口的标准 A Control,另一类是 Html 的 Input 元素或者一些引发回传的脚本等。 为什么要了解引起 PostBack 的起源呢?因为有些引起 PostBack 的 Control 可能有个属性叫 CauseValidation,当设置为 false 的时候,不希望引起 Validation。而且还有的 Control 会有 ValidationGroup 这样的属性,来控制部 分 Control 进行 Validation,可见了解 PostBack 的起源对于调用 Page.Validate()和 Page.Validate(validationGroup)是至关重要的。而对于无 法得到 PostBack 起源 Control 的情况,则一律简单调用 Page.Validate()进行页 面内所有未分组的 Control 的校验。 对于第一类,又有两种情况:实现了 IPostBackDataHandler 的 Control, 实现了 IPostBackEventHandler 的 Control,这些 Control 对 Validate 的调用 分别在 SomeControl.RasiePostDataChangedEvent() SomeControl.RaisePostBackEvent(String) 函数里面调用,其实现基本都是一直的,见下面的代码: / 下面是 TextBox 里面引发 Page.Validation(validationGroup)调 用入口的函数,其它的 / Control,如 CheckBox,RadioBox 的实现和这个是一致的。 protected virtual void RaisePostDataChangedEvent() / 下面的条件是判断引起这次 Postback 的 Control 就是当前 Contr ol if (this.AutoPostBack if (this.CausesValidation) this.Page.Validate(this.ValidationGroup); this.OnTextChanged(EventArgs.Empty); 上面的代码是对于实现了 IPostDataHandler 接口的 Control 的处理。下面来 看看实现了 IPostBackEventHandler 接口的 Control 的处理,以 Button 为列, 其它的 LinkButton,ImageButton 等 Control 实现差不多都这样 protected virtual void RaisePostBackEvent(string eventArg ument) / 先校验 Event base.ValidateEvent(this.UniqueID, eventArgument); / 程序走到这里,就认为引起 PostBack 的起源就是当前 Control, 所以如果当前 Control 的 / 的 CauseValidation 为 true,那么调用页面的 Page.Validate (validationGroup)函数进行 / Validate 操作 if (this.CausesValidation) this.Page.Validate(this.ValidationGroup); this.OnClick(EventArgs.Empty); this.OnCommand(new CommandEventArgs(this.CommandName, this.CommandArgument); 上面解释了对于第一类情况,我们都能查找到引起 PostBack 的起源 Control, 那么对于第二类情况如何处理呢,这个时候是无法查到引起 PostBack 的 Control 的,看下面的代码: private void RaisePostBackEvent(NameValueCollection postD ata) / 1. 假如已经在 Page 上显式的注册了引起 PostBackEvent 的 Con trol,就直接处理 if (this._registeredControlThatRequireRaiseEvent != n ull) / 这个函数会直接调用 Control 上面的 RaisePostBackEvent (String)函数,这个函数里面 / 会进行 Page.Validate 函数的调用。上面刚刚解释了。 this.RaisePostBackEvent(this._registeredControlTh atRequireRaiseEvent, null); else / 这部分代码,我自己按照 Reflector 反编译的结果重新组织了 ,但是逻辑 / 没有任何变化,只是方便阅读理解 / 2. 假如没有注册,就查找_EVENTTARGET 记录的 Control 来处理 string eventTarget = postData“_EVENTTARGET“; bool hasEventTarget = !string.IsNullOrEmpty(event Target); Control eventTargetControl = null; if (hasEventTarget) eventTargetControl = this.FindControl(eventTa rget); if (eventTargetControl != null) / 这个函数会直接调用 Control 上面的 RaisePostB ackEvent(String)函数,这个函数里面 / 会进行 Page.Validate 函数的调用。上面刚刚解 释了。 this.RaisePostBackEvent(eventTargetContro l.PostBackEventHandler, eventArgument); else if (this.AutoPostBackControl = null) / 这个 AutoPostBackControl 如果不为 null,那么说明 了 AutoPostBackControl 在自己的 / RaisePostDataChanged()函数里面已经调用了 Page. Validate()函数,上面刚刚解释了。 / 这个时候属性无法查找到引起这次 PostBack 的真正起源 Control,也许是一个 Input 元素 / 引起的,也许是一段脚本引起的 PostBack,这样,我们 就对于所有未分组的 Validator 就行 / 一次校验,因此调用 Page.Validate() this.Validate(); 如此我们已经对于三处调用 Page.Validate 方法的调用地方都进行了解释。可 以很了解服务器端的 Validate 的调用时机。 Validate 的执行过程 Validate 的执行主要是调用接口 IValidator.Validate()方法完成的,这个在 BaseValidator 里面已经做了一个基本的实现,可以看看下面的实现代码: public void Validate() this.IsValid = true; if (this.Visible / 检查是否能在当前的 NameContainer 里面查找到待校验的 Co ntrol if (this.PropertiesValid) / 下面的方法是一个 abstract 方法,依赖于具体的 Valid ator 来实现自己的逻辑 this.IsValid = this.EvaluateIsVal

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论