Client请求包含Server控件Web Page 的处理过程

       试图理解使用Server控件的Web Page的处理过程,不知道是否准确,请大家多发表意见。

       含有服务器控件的页面的处理过程,同传统的asp页面的处理过程相似,都是一系列Request请求页面,然后Server返回Response。
      Server生成页面,把数据发送给Client,关闭Http连接,忘记刚才的Request。

       但是含有服务器控件的页面却有同传统页面不同的机制,下面介绍几个概念:

Server Round-Trip(同服务器的交互):
        对于其他动态页面生成系统(asp,jsp) ,对Client和Server工作分工都有明确定义,Client负责捕获用户操作,显示Html元素,执行Script代码,Server负责动态生成页面,把页面发送给Client。Server还可以管理Client在Server的状态,方便处理Client不同的请求。对于上面的这种分工,Client的操作只对Client可见,Server的处理只对Server可见。
        Asp.Net 提出了Server控件的概念,允许Client的操作对于Server来说可见,使用的方法是:Client对Server控件的任何操作,通过Http Request发送给Server。除了HttpRequest,还存在其他方法可以向Server发送信息,让Server执行,如简单Socket,Dcom,Java Rmi,但是在Asp.Net规范中,只有HttpRequest这一种方法。
        看一下Web程序中Client同Server的交互,相同Page的代码在Client和Server间交互执行,这就是我们说的Round-Trip。

        如果要引发Rount-Trip,需要用户在Client执行某些操作。
        简单的Mouse的按键的Up,Down是否可以定义为引发Round-Trip的事件呢?从技术上说,可以;从用户感觉上说,不可以,因为会引起频繁的网络数据传输,导致程序使用感觉糟糕;所以,对于可以引发Round-Trip的事件要严格定义。

Page ViewState:
        在Round-Trip的WebPage模型中,Server 要处理比传统模式下更多的交互,但是Http协议是无状态的,即Server不会保留上次Request的任何信息(因为对于Web程序,要记录Client请求的所有信息,如表字段名称,各种对象实例的状态等,对Server来说是非常大的开销,Server可以使用特定技术保留Request的信息的特定部分,如用Session来标记请求的特定变量;同样为了节约Server资源,仅限于少量变量,不要期望Server可以记录ClientRequest的所有信息),所以Server在Rount-Trip中要额外处理来重新生成Page页面,这就用到了ViewState。
        Page本身在送给Server的Request时,保留了自身的ViewState。ViewState包括了Page中所有
控件的状态,以名称-值对的形式存储,使用的是System.Web.UI.StateBag 对象。ViewState作为字符变量由Client送给Server,Server处理后再送回Client,这个字符变量是以隐藏的表字段形式存在的。

<html>
<head>
<form name="_ctl0" method="post" action="NewASPNet.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE"
value
="dDw0NDY2MjMzMjt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU+Oz47bDx0PHQ8O3Q8aTwz
PjtAPFNwZWVkeSBFeHByZXNzO1VuaXRlZCBQYWNrYWdlO0ZlZGVyYWwgU2hpcHBpbmc7PjtAPDE7Mjs
zOz4+Oz47Oz47dDxwPHA8bDxUZXh0Oz47bDxZb3VyIG9yZGVyIHdpbGwgYmUgZGVsaXZlcmVkIHZpYS
BVbml0ZWQgUGFja2FnZTs+Pjs+Ozs+Oz4+Oz4+Oz4="
 />
</head>
</html>

       上面ViewState的值Web Form Processor 可以理解并可以根据内容恢复页面Server控件的状态。
使用ViewState的优点:
1.节约Server资源:存在于Page,不是在Server,节约Server资源;
2.独立:包含的ViewState,可以被其他客户端使用;
缺点
如果Page中的控件数量比较大,导致ViewState要存储很多的名称-值对,会在Round-Trip传输中耗时,影响性能。
Page默认使用所有Server控件的ViewState,可以关闭整个Page的ViewState,或者关闭某些Server控件的ViewState,以保证在Round-Trip中快速通过。

下面介绍Page处理过程:

       Client向Server请求页面后,Server都会直行相同的步骤,执行代码,对于任何一次的Client请求,执行的操作都相同。
分为以下4步:
1.Configuration
2.Event Handling
3.Rending
4.Cleaning up

 如果我们增加自定义处理过程,那么可以根据实际状况加入到以上4步中。
Configuration Stage:
        如果Page第一次Load,那么需要初始化Page的各个控件,将各个控件的状态写入ViewState,发送给Client;如果是在PostBack中,即不是第一次加载页面,Page 和ViewState将恢复。这时有2个事件相继触发,Page_Init()和Page_Load(),这2个事件的区别:在Page_Init()中可以访问Page的控件,但是因为没有加载ViewState,所以只可以访问控件的默认值,不是PostBack后由Server设置的值。
在 Page_Load()事件,我们可以访问Page的所有控件的当前值,可以处理即将显示给用户的控件的状态。
//

void Page_Load(Object sender, EventArgs e) 
{
//数据库操作类实例
IBuyAdv.CartDB cart = new IBuyAdv.CartDB(getDSN());
//Request实例包括向Server请求的参数,这个参数集合包括QueryString,Form,ServerVariables,//Cookie

if (Request.Params["ProductCode"!= null
{
cart.AddShoppingCartItem(GetCustomerID(), Request.Params[
"ProductCode"]);
}

/*在Page_Load中最后1步是在Page的控件中加载数据,因为各个控件的ViewState在每次的同Server的交互中保存,我们只需在页面第一次Load时加载数据。通过检查IsPostBack可以确定页面是否是第一次加载,如果是,那么需要从数据库中取出数据,加入到控件中,如果不是,那么只需要从ViewState中恢复即可。*/
If (Page.IsPostBack == false
{
PopulateShoppingCartList();
UpdateSelectedItemStatus();
}

}



Event Handling Stage:
        服务器控件可以触发服务器的事件,而Client可以使用Http Post引起同Server的交互,如果Client Post到达Server,我们需要知道哪个控件引发了事件,并且正在执行处理过程。
        虽然引起client-Server交互的是单个Server 控件的事件,但是客户端却可以存在触发Server事件的、还没有执行的Server控件的事件,如文本框文字变化,CheckBox选择的变化等等。它们都会在Event Handling Stage处理。没有特定的顺序要求哪个控件的事件先处理,但是所有的控件事件都会在触发Client-Server交互的事件前处理,即引起Client-Server交互的事件肯定是最后处理。
//

void Recalculate_Click(Object sender, EventArgs e) {
UpdateShoppingCartDatabase();
PopulateShoppingCartList();
UpdateSelectedItemStatus();
}

void Checkout_Click(Object sender, EventArgs e) {
UpdateShoppingCartDatabase();
Response.Redirect(
"secure/Checkout.aspx");
}


Recalculate_Click()事件将在用户点击Recalculate按钮时触发,这是个按钮空间,client-Server交互将马上进行,而Checkout_Click()事件将在用户点击Checkout按钮时触发。

Rending Stage:
这个阶段就是Html元素遇到浏览器的阶段,在这个阶段,静态的Html元素,Response.Write的输出,Server控件的输出,都在这个阶段送给浏览器。同时,任何脚本代码也将执行。

Clean Up Stage:
这时最后阶段,所有的Html元素都在浏览器显示。这时执行的事件示Page_Unload(),在这里,执行释放资源的代码,如关闭数据库,关闭打开的文件等等。

关于Web Form Events: 
       WebForm的事件同传统的事件驱动模型的事件不同,WebForm的事件是由Client发起,Server执行的。这种传递是由Http Post完成的。
关于Server 控件的事件的触发,是有严格定义的,因为我们并不想连续的从Client传递控件给Server。

Void MyButton_OnClick(Object sender, EventArgs e)
{

}


       第一个参数sender,是一个触发事件的控件的引用,当把一个Button控件放在Page中,需要明确定义是否可以触发事件,以及触发事件的名称。

<asp:Button id="PlaceOrder" Text="Place Order"
onClick
="PlaceOrder_Click" runat="server"/>


       对于上面的Button定义,它的Id=PlaceOrder,当用户在客户端点击这个控件时,这个控件会被Http Post发送给Server。在Server端,PlaceOrder_Click将被调用。因为有一个触发事件控件的引用-Sender也传递给Server,所以Server可以知道具体是哪个控件引发了事件。
     第2个参数EventArgs包括了关于事件本身的一些信息,同时又是基类,他的衍生类可以包括特定事件的特定信息。

   
参考:
Professional Asp.Net 1.0 Specific Edition
© 2002 Wrox Press

       构造函数是特殊的方法,用在建立对象后初始化时。当建立引用类型的对象时,如果你不显式建立构造函数,系统也会建立一个默认的构造函数。

我们建立对象时的步骤:
1.分配内存:使用new 修饰符 在堆上分配内存
2.使用构造函数初始化对象

上面的2个步骤,实际只用1行代码即可以完成,如建立对象when:

Date when=new Data();


详细说明以上2个步骤:
        对于步骤1:给对象分配内存。所有的对象都使用修饰符new来建立,这个没有例外的。我们建立对象时可以显式的使用new,如果我们没有这样做,那么编译器会做这部分工作。
请看下面的代码和应用中完整的代码:

string s = "Hello"string s = new string("Hello");
int[ ] array = {1,2,3,4}int[ ] array = new int[4]{1,2,3,4};

 

  • new 对性能的影响
    一般来说,new在2个方面对性能有影响。
    1.bool测试
            堆,指的是特定大小的连续内存。有一个特定的指针标记当前内存分配的位置,指针的一端,内存已经被使用new分配完毕,另一端,还是可用的。Bool测试即指根据指针的当前位置和堆的末尾位置计算剩余的可用的内存数;然后会用这个数值同新的new请求的内存作比较。
    2.指针向末尾移动
            如果堆中剩余的内存大于new请求的内存,那么指针将移动到请求内存增加后的位置,标记这部分内存已被使用。分配的内存地址将被返回。

    以上步骤将保证动态分配堆的内存同动态分配堆栈内存一样迅速。
    注意:上面的说法只是对单个变量有效。对于分配多个变量,基于堆栈的变量可以一次性的分配,但是基于堆的变量需要多个步骤完成。


    对于步骤2:使用构造函数初始化对象

构造函数获取使用new分配的内存。
存在2种构造函数:
  实例的构造函数: 用来初始化对象的
  静态的构造函数:用来初始化类自身的

new和构造函数实例是怎样协同工作的呢?
New的唯一目的是:获取原始的,没有初始化的内存。
构造函数实例的唯一目的是:初始化分配的内存并转换为可以使用的对象。

New不参与对象的初始化,而构造函数实例也不参与请求内存。
虽然new和构造函数实例起的作用不同,但是在程序中确实用一行代码实现的,不可以单独使用这2项功能。这是C#可以确保使用的内存的有效性的方法。
在C++中,允许单独分配内存而不初始化,也可以初始化曾将分配做的内存,这个在C#中不允许。

使用默认的构造函数:
默认构造函数的特点:
public的可访问性
名称同类名相同
没有返回类型
没有参数
将所有的字段初始化为false,zero 或者null

当你建立一个对象时,C# 编译器会建立一个默认的构造函数,如:

class Date
{
private int ccyy, mm, dd;
}

class Test
{
static void Main( )
{
Date when 
= new Date( );

}

}


      上面的Test.Main()方法,建立了一个叫when的Date对象的实例,同时执行了Date实例的默认构造函数,虽然我们没有建立构造函数,没有初始化任何字段。因为编译器为我们建立了默认的构造函数,代码如下:

class Date
{
public Date( )
{
ccyy 
= 0;
mm 
= 0;
dd 
= 0;
}

private int ccyy, mm, dd;
}


可以看到:
Public的可访问性。
名称同类名相同,
没有返回类型,
没有参数,
所有字段初始化为0,对于编译器建立的构造函数,会初始化所有non-static字段,并赋初始值:
对于数值型字段,初始化为0;
对于Bool型字段,初始化为false;
引用类型初始化为null;
结构类型的字段初始化后,其所有元素的不包含数值。

重写默认构造函数:
  默认的构造函数给所有字段赋初值,但是不见得是我们想要的,如出现下列状况:
1.不需要Public的可访问性:
 如公式函数需要Private的构造函数,一些特殊模式的方法需要non-public的构造函数。
2.不需要基于0的字段初始值。
3.看不到的代码不易于维护。

我们来写字己的构造函数:

class DefaultInit
{
public int a, b;
public DefaultInit( )
{
= 42;
// b retains default initia lization to zero
}

}

class Test
{
static void Main( )
{
DefaultInit di 
= new DefaultInit( );
Console.WriteLine(di.a); 
// Writes 42
Console.WriteLine(di.b); // Writes zero
}

}


对于处理构造函数中的异常,只可以使用thow new Exception方法。

重载构造函数:
构造函数也是一种方法,允许重载:
相同的使用范围,相同的名称,不同的参数;
允许使用多种方法初始化对象。
注意: 如果我们给类写了构造函数,那么编译器就不会再定义一个构造函数。


参考:MSDN Training
            Introduction to C# Programming
            for the Microsoft® .NET Platform
            (Prerelease)
            Workbook
            Course Number: 2124A

       现在存在很多企业级的数据库,如Oracle,SqlServer等等,功能强大,使用方便,唯一的缺点是 占用资源多,一般情况下,应该把一台计算机作为单独的数据库服务器,这不是所有人都能做到的。如果把应用程序,数据库装在同一台计算机,效率会低一些的。
       那么使用简单的数据库工具可以解决这个问题,当然也可以使用其它解决方案,如xml文件,不过这里只讨论常见的数据库工具-Access。

    本人使用Access的原因,基于处理一下问题:
     服务器管理用户状态,使用web.config设置,存在3种方式:
<SessionState>
  mode="Off":关闭用户Session管理;
  mode="InProc":IIS管理用户Session;
  mode="StateServer":可以在其它运行AspState Service的Win2000机器上管理;
  mode="SqlServer":使用SqlServer数据库管理;

       如果要维护用户状态,那么最后3种方式可以使用,不论是“InProc”还是“StateServer”,都要占用系统资源,对于少量信息维护,可以胜任,对于大量数据维护,“InProc”方式力不从心,“StateServer”方式的扩展性不好。

      用数据库方式,可以管理海量数据,扩展性好,唯一不足是速度比其他稍慢。
      微软提供了使用SqlServer管理Session的方法,前台将web.config的SessionState中的mode设为“SqlSerer”,后台的数据库脚本在/windows/Microsoft.NET\Framework\v1.0.3215\installsqlsate.sql获得,2者配合,可以使用。

        不过不是所有人都使用SqlServer的,对于其他数据库,微软没有提供解决方法。微软的数据库脚本文件,我没有仔细看,因为对SqlServer不熟悉。
        根据对Session的理解,自己写管理方法,数据库为常见的Access。

使用Access后,总结几点经验:
1.虽然Access小巧,但是使用起来比操作大型数据库,如Oracle, 麻烦的多,因为小巧的缘故吧。
2.Access的表中少用Date型字段,因为使用Asp.net 不好操作,没有找到对应的数据类型,解决办法是改为字符型的,然后使用CDate()作转换。
3.Access中可以使用存储过程(即查询) ,没有触发器;

关于使用查询:
使用的方法,同调用Oracle等大型数据库的存储过程相同,如:

//Con是OleDbConnection实例
if(Con.State==ConnectionState.Closed)
                
{
                    Con.Open();
                }

                OleDbCommand Cmd
=Con.CreateCommand();
                Cmd.CommandType
=CommandType.StoredProcedure;
                Cmd.CommandText
="CheckTimeout";                //CheckTimeout是视图名
                

                
int count=Cmd.ExecuteNonQuery();

其中视图CheckTimeout的代码:

UPDATE UserSession SET UserSession.IfEndSession = 1
WHERE (((Now()-CDate([timeout]))>Minute(15)));

属于没有参数的,如果调用有参数的,Asp.Net 部分加入OleDbParameter,其他相同,
视图的代码可为:

PARAMETERS [PersonID] Short;
DELETE *
FROM PersonInfo
WHERE [PersonInfo].[PersonID]=[PersonID];

其中PersonId为参数。

还在测试Session管理的代码,如果基本可用,将贴出来供大家讨论。
Microsoft的产品,入手简单,深入复杂,支持外部的简单的是后台严谨的复杂,我们常在开发Asp.Net程序时使用Session,但是知道Ms怎样处理Session的原理,恐怕知道的人不多,自己写一个Session处理,帮助自己理解Session。
 如果一个系统中存在多个Web服务器,那么有一台服务器作为Session服务器,应该是分布式系统的一个好的选择。
我想把我的想法作为一个开源项目,对此感兴趣的所有人都可以阅读,测试,为了方便测试,选择了Access,如果此项目可用,那么可以容易的应用到其他数据库平台。
有了大家的帮助,我们可以进步更快。

关于数据库分类的概念,谢谢春鱼。

附Access的介绍(从其他网站摘录):

在办公软件Office套件中,最为广大用户熟悉的是Word和Excel,因为它们功能强大且方便易用,更因为它们不仅可用于办公,还可用于个人写作和家庭记帐理财等。同为Office套件中一部分的Access,虽然有着同样强大的功能,但使用的人却相对少些,不像Word和Excel那样广泛。事实上,真正用过Access的用户,对其强大功能和灵活应用均称赞“不错,很好的……。”

  Access 数据库管理系统是Microsoft Office 套件的重要组成部分,是Access的最新版本,可在Windows 95环境下运行。Access适用于小型商务活动,用以存贮和管理商务活动所需要的数据。Access不仅是一个数据库,而且它具有强大的数据管理功能,它可以方便地利用各种数据源,生成窗体(表单),查询,报表和应用程序等。

什么是Access 数据库

  数据库是有结构的数据集合,它与一般的数据文件不同,(其中的数据是无结构的)是一串文字或数字流。数据库中的数据可以是文字、图象、声音等。

Microsoft Access是一种关系式数据库,关系式数据库由一系列表组成,表又由一系列行和列组成,每一行是一个记录,每一列是一个字段,每个字段有一个字段名,字段名在一个表中不能重复。图1是一个“产品”表的例子。“产品”表由10个记录组成,一个记录占一行,每一个记录由产品ID、产品名称、库存量、订货量、单价和折扣率6个字段组成。“产品ID”是字段名,其下面的1,2等是字段的值。
  

表与表之间可以建立关系(或称关联,连接),以便查询相关联的信息。Access数据库以文件形式保存,文件的扩展名是MDB。

Access 的6种对象

   Access 数据库由六种对象组成,它们是表、查询、窗体、报表、宏和模块。

 表(Table) ——表是数据库的基本对象,是创建其他5种对象的基础。表由记录组成,记录由字段组成,表用来存贮数据库的数据,故又称数据表。
  查询(Query)——查询可以按索引快速查找到需要的记录,按要求筛选记录并能连接若干个表的字段组成新表。
  窗体(Form)——窗体提供了一种方便的浏览、输入及更改数据的窗口。还可以创建子窗体显示相关联的表的内容。窗体也称表单。
  报表(Report)——报表的功能是将数据库中的数据分类汇总,然后打印出来,以便分析。
  宏(Macro)——宏相当于DOS中的批处理,用来自动执行一系列操作。Access列出了一些常用的操作供用户选择,使用起来十分方便。
  模块(Module)——模块的功能与宏类似,但它定义的操作比宏更精细和复杂,用户可以根据自己的需要编写程序。模块使用Visual Basic编程。
  与Access 以前的版本比较,Access 新增了许多功能,字段类型增加了OLE对象和超级链接,特别是与Internet的融合,在数据库中可以直接链接到指定的Web页面或网络文件,也可以把Web页面上的表格导入到数据库。Access 可以方便地利用各种数据源,包括dBASE, FoxBase,FoxPro,Excel,Word 等。Access 增加了数据库访问的安全机制,可对表一级设置访问许可权。Access 还可以方便地利用FoxPro数据库、Excel电子表格的数据,还可以和Word混合使用,打印通用信函或信封。

 MS ACCESS使用VBA語法來開發,在mdb的檔案格式中,提供資料與程式同時建置,或是利用資料庫分割,將程式與資料分隔,有


點類似主從架構,使用mdb最大的好處就是可以盡情使用ACCESS的參數,它可以節省寫SQL語法與過多的VBA程式,不過mdb的缺點


是比較適合單機作業,如果想多人使用資料庫,那麼就必須將資料轉換為SQL存取,只要分別在使用端設定ODBC來指向SQL SERVER


,這樣子就符合多人使用的目的與效果了!


         在ACCESS 2000/XP中出現新的格式-ADP,ADP是直接連取SQL SERVER來做資料存取,速度當然比透過ODBC來的快,不過您必


須逐行撰寫 VBA與 SQL語法,在ACCESS 2000中必須安裝OFFICE 2000 SP1更新檔才可以直接存取SQL 2000,無論是MDB或ADP也


好,ACCESS最大的優點在於報表的製作,例如:做識別證,來賓證等,它簡單的報表設定,都是最具有親合力的作,ACCESS的報表當


然也具有試算的能力,只要懂一點ACCESS的使用著都會自己調整報表列印位置,不必依賴程式設計者,給予使用者莫大的幫助

        浏览器访问WebPage时,虽然使用的地址不变,但是端口会发生变化,用刷新页面作测试,2分钟内刷新,用户的端口不变,超过2分钟,那么端口会发生变化。
        Http1.1中说,Connection报头中有持久性连接控制,默认情况下,所有Http1.1连接都认为是持久性的,除非某个请求或者响应包括了一个Connection:close报头。这个持久性,指的好像不是维持相同的Tcp端口。
        如果用Http协议维持会话,那么需要给客户端发送标记符SessionId,客户端再次访问时,提供这个标记,好被Server识别。这个标记存在形式是cookie或者查询字符串,要手工处理Session,需要一些功能来实现,带来如下问题:
        要实现Access存储Session,那么WebApp的配置文件的
<SessionState>
  mode="Off":关闭用户Session管理;
因为不了解IIS的Session管理机制。
那就需要知道怎样处理标记用户请求的SessionID,关于SessionId还在查资料,那位对此有经验,请给与支持。

SessionID:msdn中说,是长度为120Bit的ASC字符,可以在Url中使用,但是经过测试,
//生成SessionId
string id=Session.SessionID;
//显示Id的内容及长度
Response.Write(id+":"+id.Length.ToString()+"<br>");
byte[] by=System.Text.Encoding.ASCII.GetBytes(id);
Response.Write(by.Length.ToString()
+"<br>");

使用Session.SessionID生成的SessionId长度为24个ASC字符,即192Bit,应该是原始的SessionID被加密了,长度发生了变化。
Google

posted on 2005-06-28 11:07  Pierce  阅读(3178)  评论(0编辑  收藏  举报

导航