[ASP.NET入门随想八]明明白白我的心
——ASP.NET的页面模型
——ASP.NET的页面模型
有一群说着同样语言的人,计划修一座高耸入云的通天高塔直达天庭,以证明族群团结的力量,塔很快就初具规模。这下可惊动天庭的神,他想这人和神都成邻居了,还怎么去统治人类?于是便施魔法扰乱人们的语言,使他们无法沟通,高塔再也无法继续修建。
■ 明明白白我的心 – 人机交互(Human-Computer Interaction)
博客园里有一篇《给妈妈写程序》感人肺腑,说的是作者常常为了指导50出头的妈妈完成COPY之类的操作耗尽心思,最后为其定做一个只需点击两下的程序,由此推出UI设计的重要性。诚然,就用户而言软件等于N个用户界面。他们中的大多数压根不关心里头有多少令开发者陶醉的所谓高级技术,简单、快捷、美观是他们要求的全部。人机交互的研究领域就在捣腾这些看似琐碎的东西。
在系统设计中,需要划分自动化系统边界,即把系统划分成手工操作和系统自动两个部份,两者通过用户界面(User Interface)完成对接,如图8-1。用户界面完成系统的输入输出工作:收集用户触发事件及相关数据,传递给系统内部进行处理并接收处理结果,可视化处理结果。在企业级的c/s应用中,aspx及其后台编码文件功能单纯得只剩下一个——完成人机交互。一个漂亮的网页不是用户界面设计的全部内涵,它更应该象一位面容姣好、语音甜美的接待者,轻声细语地询问用户需求,聆听用户杂乱无章的诉说,耐心指导用户完成操作流程,宽容用户错误,准确为用户送上最终结果——“您的账户里只剩0.4元,无法完成支付操作。”总之一句话,要充分认识和领会白居易同志写诗的伟大精神,让50出头的妈妈愉快地完成人机交互。
■ 人动则影动 – ASP.NET的静态模型
在ASP.NET架构中,服务器端的ASP.NET页面对应客户端的HTML页面,包含一个交互Web Form,继承于FrameWork的Page类。ASPX文件是人,HTML编码临时文件是影,人动则影动,两者合成完整意义上的用户界面。用户对影进行各种操作,系统通过人进行相应处理,“request-response”回馈机制完成两者的映像过程,如图8-2。
HTTP是一个无状态协议,所以WEB服务器是位打个磕睡就忘事的先生,当响应发送后将丢弃所有请求信息。这事必会影响应用程序的可用性,如因为数据验证错误而要求用户重新输入几十个输入框的所有信息,连妈妈的指头都会发出抗议的声音。为了弥补这个缺陷,ASP.NET使用视图状态(ViewState)来完成状态保持。原理很简单,服务器将每次HTTP请求中的页面数据写入生成的HTML文件中,下次页面提交时一并带上,如下例。在ASP时代我们曾用<input type=”hidden”>手工实现,而在.Net中您会发现HTML源代码会自动产生一个 _VIEWSTATE标签,其值即为系统保存的页面数据。
<%@ Page Language="C#" %>
<script runat="server">
protected override void OnLoad(EventArgs e)
{
int val = int.Parse(_sum.InnerText);
_sum.InnerText = (val+1).ToString();
base.OnLoad(e);
}
</script>
<html><body>
<form runat="server">
<span id="_sum" runat="server">0</span>
<input type="submit" />
</form>
</body></html>
<script runat="server">
protected override void OnLoad(EventArgs e)
{
int val = int.Parse(_sum.InnerText);
_sum.InnerText = (val+1).ToString();
base.OnLoad(e);
}
</script>
<html><body>
<form runat="server">
<span id="_sum" runat="server">0</span>
<input type="submit" />
</form>
</body></html>
ViewState减少了不少麻烦,但也存在问题。在默认情况下它将被启用,即使没有任何用处也去收集所有页面信息并穿梭往返于C/S两端,其用base 64编码的庞大身躯常吞噬大量带宽,并存在被胁持的可能而引起系统安全性问题。
用户会对界面做出各种操作,并希望得到系统的回应,这些用户界面的操作被.Net统称为事件(event,系统内部也会产生事件),而对应的系统回应称为事件处理。在《随想四》中的Login页面,当用户点击btnLogin按钮后, btnLogin对象(这时它被称为事件源)大叫:“我被点啦,十年了,我终于第一次被点击啦,555~~~”但Button类本身并未定义相应的事件处理程序,那么又是谁来完成系统的实际响应操作呢?
在编写Login类时我们已经定义成员btnLogin_Click方法,并希望由此完成btnLogin.click事件的处理,但MS工程师在定义Button类时并不知道其作用,所以需要一个媒介将两者连接起来,这时我们不禁回想起《随想七》中的委托。.net用EventHandler委托作为连接事件源与事件处理程序的媒介,将两者绑定:
// 该段程序为《随想四》中Login.aspx.cs的代码片段并有所修改
// EventHandler为System命名空间中定义的公共委托
// sender为事件源对象,EventArgs类负责收集事件数据
public delegate void EventHandler(object sender,EventArgs e);
// Login为受托类,完成事件处理定义和连结
public class Login : System.Web.UI.Page
{
……
// Login.OnInit 为Page类受保护方法,执行创建和设置实例所需的初始化步骤
// 调用OnInit方法时引发 Init事件
override protected void OnInit(EventArgs e)
{
// new System.EventHandler生成一委托实例
// += 完成事件处理委托的连接过程,同一事件可以触发多个处理程序,称为多路广播
// base.OnInit(e)用于调用父类的同名方法
this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
base.OnInit(e);
}
private void btnLogin_Click(object sender, System.EventArgs e){……}
}
// EventHandler为System命名空间中定义的公共委托
// sender为事件源对象,EventArgs类负责收集事件数据
public delegate void EventHandler(object sender,EventArgs e);
// Login为受托类,完成事件处理定义和连结
public class Login : System.Web.UI.Page
{
……
// Login.OnInit 为Page类受保护方法,执行创建和设置实例所需的初始化步骤
// 调用OnInit方法时引发 Init事件
override protected void OnInit(EventArgs e)
{
// new System.EventHandler生成一委托实例
// += 完成事件处理委托的连接过程,同一事件可以触发多个处理程序,称为多路广播
// base.OnInit(e)用于调用父类的同名方法
this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
base.OnInit(e);
}
private void btnLogin_Click(object sender, System.EventArgs e){……}
}
■ 苦旅 – ASP.NET的动态模型
每一个HTTP请求出发前可谓悲壮,壮士一去兮不复返!Login.aspx页面请求将URL和相关数据打包背上,胸前挂着自己的IP地址身份牌,从客户端浏览器出发,翻越电信网通人为制造的各种障碍,爬雪山过草地,步履蹒跚地走到Web服务器的IIS门前,正想狂喜一阵,当看到大门口排着长长的数以万计的请求队列,还常有些饿死骨无人理彩时,它终于倒下了。
矇眬中Login.aspx页面请求被传送到HttpRuntime实例,HttpRuntime实例将:
•获取一个HTTP Application 对象;
•让HTTP Application 对象读取配置文件;
•由HTTP Module 实例们分别提供会话维护、验证或配置文件维护等各项服务;
•HTTP Handler接口实例化具体Page类实现处理。
Login是HTML页面生产车间的工人,每天他负责按照严格的标准和固定的流程为客户制作HTML编码的Login.aspx页面。一天早上,login正在硬盘里惬意地喝着咖啡,身宽体胖的主管HttpHandler大叔通知他立即开工。
今天是第一次进入内存,所以他先准备好页面构架和所有控件零件并设置其默认状态;然后严格按OnInit方法完成Init事件;之后会在页面请求中寻找_VIEWSTAT。如果找到就对数据进行读取和解码;并让控件更新其状态以准确反映客户端相应元素状态;
随后他拿出OnLoad方法操作手册来处理Load事件;然后应付一系列被触发的页面事件,如果页面正在被回送,还会包括用户触发的事件;用onPreRender方法处理的PreRender事件可以改变提交页面的方式;然后把当前的页面状态保存到新的视图状态中。
完成所有操作后即可生成HTML编码文件,期间可通过覆写(override)Render方法以附加一些HTML代码,为页面做最后的修饰。
最后Login释放所有占用的文件、图形对象、数据库连接等关键资源,匆忙跑出内存,“真是太闷了!”出门时他小声地嘀咕了一声。