18第一章 ASP.Net内建对象

时间:2021-05-28 07:58:21

第一章        ASP.Net内建对象


第一章        ASP.Net内建对象

ASP.Net为保持用户的数据和信息,内建了许多对象,包括Application、Response、Request、cookie、Sessions、Cache、Server和ViewState等对象。通过这些对象,可以提供网站一些必不可少的功能,例如当前目录的获得、在线人数、访问网站总人数、网上商店购物筐等。本章介绍这些内建对象的属性和用法。

11.1 Response对象

使用Response对象可以向浏览器发送信息,包括直接发送信息在浏览器中显示、重定向浏览器去显示另一个网页、设置cookie的值。在ASP.Net中一般不用Response对象发送信息到浏览器中显示,可以用其它方法重定向浏览器去显示另一个网页,因此在ASP.Net中使用Response对象的机会越来越少了,本节只对Response对象做简单介绍,设置cookie方法在另一节介绍。

11.1.1用Response发送html标记在浏览器中显示

例子e11_1_1:用Response发送html标记在浏览器中显示的网页文件如下:

<%@ Page language="c#" %>

<html>

<body>

<%   Response.Write("<font Size=7>");

Response.Write("Response对象使用");

Response.Write("</font");

Response.Write("<br>");

%>

</body>

</html>

11.1.2用Response对象重定向浏览器

例子e11_1_2用Response对象重定向浏览器显示新浪网主页的例子如下:

<html>

<script language="c#" runat=server>

void EnterBtn_Click(Object src,EventArgs e)

{  Response.Redirect("http://www.sina.com.cn");

}

</script>

<body>

<form runat=server>

<asp:Label runat=server>单击按钮打开新浪网主页</asp:Label><br>

<asp:button text="打开新浪网" Onclick="EnterBtn_Click" runat=server/>

</form>

</body>

</html>

这里实现的功能完全可以用HyperLink控件实现,请读者试一试。但是如果根据条件用语句实现转向不同网页,使用此语句还是必要的,例如,有些用户企图不经过登录直接访问网站网页,在网站网页的Page_Load方法中要进行判断,如果未登录,可用上述方法直接转向登录界面。例子e11_2_1C中使用Response对象重定向时,向另一个网页传递数据。

11.2 Request对象

Request对象主要有以下用途:第一用来在不同网页之间传递数据,第二是Web服务器可以使用Request对象获取用户所使用的浏览器的信息,第三是Web服务器可以使用Request对象显示Web服务器的一些信息,最后,可以用Request对象获得Cookie信息。本节主要介绍前三种用途,有一节专门介绍Cookie。

11.2.1       用Request对象获取另一个网页传递的数据

从一个网页链接到另一个网页时,可能需要传递一些数据到另一个网页。一般采用如下格式:URL?数据名称=数据值&数据名称=数据值…,其中?表示URL后边要传递数据,数据传递的格式为:数据名称=数据值,两个数据之间用&分割。当数据传递到另一个网页时,另一个网页用Request["数据名称"]的方法取出这个数据。e10_6_7也是两个网页之间传递数据的例子。

子e11_2_1A:本例用HyperLink打开e11_2_1B网页,传递固定数据,网页文件如下:

<html>

<body>

<form method=POST runat=server>

<asp:HyperLink id="hyperlink1" Target="_new"

NavigateUrl="e11_2_1B.aspx?Num=12345&Name=张三"

Text="提交"  runat="server"/>

</form>

</body>

</html>

子e11_2_1B:本例网页显示另一网页传递来的数据,网页文件如下:

<html>

<script language="c#" runat=server>

void Page_Load(Object src,EventArgs e)

{ Label1.Text="另一网页传递的数据是:"+Request["Num"]+Request["Name"];

}

</script>

<body>

<form runat=server>

<asp:Label id="Label1" runat=server />

</form>

</body>

</html>

子e11_2_1C:如传递的数据是变量,例如TexBox控件输入的内容,可用如下办法:

<html>

<script language="c#" runat=server>

void button1_Click(Object src,EventArgs e)

{ string s="Num="+HttpUtility.UrlEncode(Label1.Text);

s+="&Name="+HttpUtility.UrlEncode(textBox1.Text);

Response.Redirect("e11_2_1B.aspx?"+s);

}

</script>

<body>

<form runat=server>

<asp:Label id="Label1" Text="12345" runat=server />

<asp:TextBox id="textBox1" Text="" runat=server />

<asp:button text="提交" Onclick="button1_Click" runat=server/>

</form>

</body>

</html>

在URL中,有些ASCII字符具有特殊含义,必须做特殊处理,例如字符/,用HttpUtility.UrlEncode方法将对具有特殊含义字符做特殊处理。以后将看到,通过Application和Session对象也可以在两个网页之间传递数据。

11.2.2       用Request对象获取客户端浏览器的信息

不同浏览器或相同浏览器的不同版本支持不同的功能,Web应用程序可能要根据不同的浏览器采取不同的措施,可用HttpRequest.Browser属性的HttpBrowserCapabilities对象获得用户使用的浏览器信息。见下例:

<html>

<script language="c#" runat=server>

void Page_Load(Object src,EventArgs e)

{ HttpBrowserCapabilities bc = Request.Browser;

string S="浏览器的特性如下:"+"<br>";

S+="Type="+bc.Type+"<br>";

S+="Name="+bc.Browser+"<br>";

S+="Version="+bc.Version+"<br>";

S+="Major Version="+bc.MajorVersion+"<br>";

S+="Minor Version="+bc.MinorVersion+"<br>";

S+="Platform="+bc.Platform+"<br>";

S+="Is Beta="+bc.Beta+"<br>";

S+="Is Crawler="+bc.Crawler +"<br>";

S+="Is AOL="+bc.AOL +"<br>";

S+="Is Win16="+bc.Win16+"<br>";

S+="Is Win32="+bc.Win32+"<br>";

S+="Supports Frames="+bc.Frames+"<br>";

S+="Supports Tables="+bc.Tables+"<br>";

S+="Supports Cookies="+bc.Cookies+"<br>";

S+="Supports VB Script="+bc.VBScript+"<br>";

S+="Supports Java Script="+bc.JavaScript+"<br>";

S+="Supports Java Applets="+bc.JavaApplets +"<br>";

S+="Supports ActiveX Controls="+bc.ActiveXControls+"<br>";

S+="CDF="+bc.CDF+"<br>";

Label1.Text=S;

}

</script>

<body>

<form runat=server>

<asp:Label id="Label1" runat=server></asp:Label>

</form>

</body>

</html>

11.2.3       用Request对象获取服务器信息

例子e11_2_3:用Request对象获取Web服务器信息的例子如下:

<html>

<script language="c#" runat=server>

void Page_Load(Object src,EventArgs e)

{ string s="Web服务器的特性如下:"+"<br>";

foreach(string Name in Request.ServerVariables)

{  s+=Name+":"+Request.ServerVariables(Name)+"<br>";  }

Label1.Text=s;

}

</script>

<body>

<form runat=server>

<asp:Label id="Label1" runat=server></asp:Label>

</form>

</body>

</html>

11.3 Cookie对象

用户用浏览器访问一个网站,Web服务器并不能知道是哪一个用户正在访问。但一些网站,希望能够知道访问者的一些信息,例如是不是第一次访问,访问者上次访问时是否有未做完的工作,这次是否为其继续工作提供方便等。用浏览器访问一个网站,可以在此网站的网页之间跳转,当从第一个网页转到第二个网页时,第一个网页中建立的所有变量和对象都将不存在。有时希望为这些被访问的网页中的数据建立某种联系,例如一个网上商店,访问者可能从网站中不同的网页选取各类商品,那么用什么办法记录该访问者选取的商品,也就是一般所说的购物筐如何实现。用Cookie对象可以解决以上问题。

11.3.1Cookie对象的用法

Cookie为Web应用程序保存用户相关信息提供了一种有用的方法。支持Cookie对象的浏览器允许Web应用程序将一小段文本信息存储到浏览器所在的计算机中。当用户访问网站时,web应用程序可以利用Cookie保存用户的一些信息,这样,当用户下次访问网站时,Web应用程序就可以检索到以前保存的信息。

Cookie对象采用键/值对的方法记录数据,用法如下:

HttpCookie MyCookie=new HttpCookie("UserInfo");//UserInfo为键

myCookie.Value=2.ToString();//UserInfo键的值为2

myCookie.Expires=DateTime.Now.AddHours(1);//数据1小时后无效,否则退出网页失效

Response.Cookies.Add(MyCookie);//将MyCookie写到浏览器

语句myCookie.Expires=DateTime.Now.AddHours(1)指定Cookie中数据何时失效,这里是1小时后失效。如果不指定失效时间,退出网页立即失效。取出Cookie的值的方法如下:

HttpCookie myCookie=Request.Cookies["UserInfo"];//得到键为UserInfo的Cookie

Int Num=Convert.ToInt16(myCookie.Value);

一个键还可以有若干子键,可以在一个 Cookie 中保存多个键/值对,具体方法如下:

HttpCookie MyCookie=new HttpCookie("UserInfo");//主键="UserInfo"

MyCookie.Values.Add("UserName","张三");//子键1="UserName",其值为"张三"

MyCookie.Values.Add("UserAge","13");//子键2="UserAge",其值为"13"

Response.Cookies.Add(MyCookie);//写到用户计算机中

取出Cookie的值的方法如下:

HttpCookie myCookie=Request.Cookies["UserInfo"];//得到主键为"UserInfo"的Cookie

string s=myCookie.Value("UserName");//得到子键1的值

string s=myCookie.Value("UserAge");//得到子键2的值

11.3.2用Cookie对象记录访问的次数

例子e11_3_1本例用Cookie对象记录访问者是第几次访问本站,并将次数显示出来。

<%@ Page Language="C#" Debug="true" %>

<html>

<script language="c#" runat=server>

void Page_Load(Object src,EventArgs e)

{  if(!Page.IsPostBack)//如果网页响应事件后刷新,访问次数不加1

{  int Num=1;

HttpCookie myCookie=Request.Cookies["VistNum"];//取出Cookies

DateTime now = DateTime.Now;//得到当前时间

if(myCookie!=null)//名称为VistNum的Cookies是否存在

{  Num=Convert.ToInt16(myCookie.Value);

Num++;//如果存在,取出Cookies存的值,加1

}

else

{  myCookie=new HttpCookie("VistNum");//如果不存在,创建Cookies

}

myCookie.Value=Num.ToString();

myCookie.Expires=now.AddHours(1);//数据1小时后无效,否则退出网页失效

Response.Cookies.Add(myCookie);// Cookies中的值不能修改,只能覆盖

label1.Text="您是第"+Num.ToString()+"次访问本站";

}

}

</script>

<body>

<form runat=server>

<asp:Label id="label1" runat=server></asp:Label>

</form>

</body>

</html>

当然,浏览器的Cookies必须设置为允许使用。

11.3.3网上商店购物筐实现

网上商店一般有多个网页,每个网页提供不同种类的商品,供用户选择。用户可以浏览这些网页,从每个网页中选择商品,网上商店网站要记录用户要购买的这些商品,一般把这个功能叫做购物筐,下边的例子介绍用Cookies实现购物筐的方法。

首先用ACCESS数据库系统创建一个仓库管理系统数据库DepotI,仅有1个表goods,记录仓库中的所有商品。一般商品表要包括很多字段:编号、货物名称、包装类型、单价、数量等。这里只是说明问题,为了简单,只包括3个字段:货物编号字段gID,自动编号,主关键字;货物名称字段gName,文本,字段大小26,必填字段,默认值为空;货物数量字段gNum,整型,必填字段,默认值为0。增加4个记录,字段gID、gName、gNum的值分别为:1、香蕉、20;2、苹果、50;3、菊花、30;4、茉莉、20。

例子中有两个网页,一个网页显示水果,这里只有两种:香蕉和苹果;一个网页显示花卉,这里也只有两种:菊花和茉莉。分别在2个网页中用网格控件DataGrid显示,每个网格控件有4列,数据库DepotI的Goods表的3个字段列,还包括1列按钮列,单击按钮把所选货物放到购物筐,单击1次,数量增加1个。

子e11_3_2A:第一个网页显示水果,网页文件如下:(运行效果如上图)

<%@ Import Namespace="System.Data.OleDb" %>

<%@ Import Namespace="System.Data" %>

<html>

<script runat=server Language="C#">

DataView dw;

DataSet ds;

public void Page_Load(Object sender, EventArgs e)

{  string s1="Provider=Microsoft.Jet.OLEDB.4.0;";

s1+="Data Source=D:\\ASP\\DepotI.mdb";//Data Source两词之间有空格

string s2="SELECT * FROM Goods WHERE gID<3";

OleDbConnection conn=new OleDbConnection(s1);

OleDbDataAdapter da=new OleDbDataAdapter(s2,conn);

ds=new DataSet();

da.Fill(ds,"MyTable");

grid.DataSource=ds.Tables["MyTable"];

grid.DataBind();

}

public void HandleCommands(Object sender, DataGridCommandEventArgs e)

{  if(e.CommandName=="BuyButton")//判断是哪一个按钮发的事件

{  string s=e.Item.Cells[1].Text;//得到商品名称

int Num=1;//将要存入Cookie的值,初值为1

HttpCookie myCookie=Request.Cookies[s];//得到主键为s的Cookie

DateTime now = DateTime.Now;//得到当前时间

if(myCookie!=null)//判断有无主键为s的Cookie

{  Num=Convert.ToInt16(myCookie.Value);

Num++;//如果有,购买数量加1

}

else

{  myCookie=new HttpCookie(s);

}//如果没有,创建主键为s的Cookie,值为1

myCookie.Value=Num.ToString();//Cookie只存字符串

myCookie.Expires=now.AddHours(1);//存的数据1小时以后无效

Response.Cookies.Add(myCookie);// Cookies中的值不能修改,只能覆盖

HttpCookieCollection myCookieS=Request.Cookies;

s="";//将用户购买的商品全部显示在出来

if((myCookie=myCookieS["香蕉"])!=null)

s+="香蕉"+myCookieS["香蕉"].Value.ToString()+"<br>";

if((myCookie=myCookieS["苹果"])!=null)

s+="苹果"+myCookieS["苹果"].Value.ToString()+"<br>";

if((myCookie=myCookieS["菊花"])!=null)

s+="菊花"+myCookieS["菊花"].Value.ToString()+"<br>";

if((myCookie=myCookieS["茉莉"])!=null)

s+="茉莉"+myCookieS["茉莉"].Value.ToString()+"<br>";

label1.Text=s;

}

}

</script>

<body>

<form runat=server>

<asp:DataGrid id="grid" runat="server" GridLines="both"

OnItemCommand="HandleCommands" AutoGenerateColumns="false">

<columns>

<asp:BoundColumn runat="server" DataField="gID" HeaderText="编号"/>

<asp:BoundColumn runat="server" DataField="gName" HeaderText="货物名称"/>

<asp:BoundColumn runat="server" DataField="gNum" HeaderText="数量"/>

<asp:ButtonColumn runat="server" Text="购买" CommandName="BuyButton"/>

</columns>

</asp:DataGrid><br>

购物筐<br>

<asp:Label id=label1 Text="没有选择货物" runat="server" /><br>

<asp:HyperLink id="hyperlink1" Target="_self "

NavigateUrl="e11_3_2B.aspx"  Text="选择花卉"  runat="server"/>

</form>

</body>

</html>

例子e11_3_2B:第二个网页显示花卉,它和第一个网页文件只有2条语句不同(背景为黑色的语句),即Page_Load方法的string s2="SELECT * FROM Goods WHERE gID>2"和<asp:HyperLink id="hyperlink1" Target="_self " NavigateUrl="e11_3_2A.aspx" Text="选择水果" runat="server"/>,其余完全一样,这里就不列出第二个网页文件了。其实,本例根本不必用两个网页,一个网页完全能实现以上功能,因此也不必使用Cookie,这里只是为了说明Cookie的用法。请读者改为一个网页实现以上功能,如果不用Cookie又如何实现。

在浏览器中输入地址:http://Localhost/e11_3_2A.aspx,单击按钮选水果,单击1次,数量增加1个。转到第二个网页e11_3_2B.aspx,选花卉。在Label控件处显示所选择的商品。当然,本例只是说明问题,有许多不尽合理之处。

11.4 Application对象

当网站中的ASP.Net网页被第一次访问,网站Application对象被自动创建(网站只能有一个Application对象),如果已没有浏览器访问网站中的ASP.Net网页,Application对象被自动撤销,这个期间是Application对象的生存期。在Application对象中的变量也有相同生存期,并且这些变量可以被网站中的所有网页访问,因此这些变量是网站中所有网页的公用变量。由于存储在Application对象中的变量可以被所有网页读取,所以Application对象的变量也适合在网页之间传递信息。Application对象主要有以下用途:

l  存储记录在线人数或访问网站总人数的变量。

l  存储网站共用最新消息,供所有网页更新。

l  记录网站中各网页同一条广告被点击的次数或时间。

l  存储供所有网页使用的数据库数据。

l  不同用户之间通讯,例如多用户聊天室,多用户游戏等

本节首先介绍Application对象的用法,然后介绍记录访问网站总人数的实现方法。

11.4.1       Application对象方法和事件

这里只介绍Application对象常用的方法和事件。

l  方法Add:加入一个变量到Application对象中,例如Application.Add("string1","test"),表示向Application中加入一个名为string1的变量,其值为字符串"test",其实它的效果和Application("string1")="test"以及Application.item("string1")="test"是一样的。

l  方法Remove:从Application对象中删除变量,例如Application.Remove("string1")。

l  方法RemoveAll和Clear:清除Application对象中所有变量。

l  方法Get:使用名字变量名或者下标,来取得Application对象中变量值。例如object tmp= Application.Get("string1")或object tmp=Application.Get(0)。它等价于object tmp= Application("string1")或object tmp= Application(0)。

l  方法Set:修改Application对象中指定变量的值。例如Application.Set("string1","try")。等价于Application("string1")="try"。

l  方法GetKey:得到Application中指定下标的变量名。例如string s= Application.GetKey(0)。

l  方法Lock :该方法是用来解决多个用户对存储在Application中的同一变量进行修改时的同步问题。由于存储在Application对象中的变量可以被网站中的所有网页存取,为了避免多个用户同时修改同一变量发生错误,当一个用户在修改这个变量时,不允许其它用户修改。该方法阻止其他客户修改存储在Application对象中的变量,以确保在同一时刻仅有一个客户在修改Application变量。如果用户没有明确调用Unlock方法,则Web服务器将在修改变量的网页关闭后或锁定超时后,解除对Application对象的锁定。

l  方法Unlock:和Lock方法相反,Unlock方法将允许其它网页修改Application对象的变量。下例介绍一个修改计数器变量的方法。

Application.Lock;

Application["counter"]=(Int32)Application["counter"]+1;

Application.UnLock;

l  事件Application_OnStart:当网站中的ASP.Net网页被第一次访问时,产生的事件。

l  事件Application_OnEnd:没有浏览器访问网站中的ASP.Net网页后,产生的事件。这两个事件的事件处理函数必须写在global.asax文件之中。

11.4.2       Global.asax文件

Global.asax文件位于Web应用程序项目所在的Web应用目录下,Web应用目录概念见9.3.7节。每个解决方案中每个项目中都可以有一个Global.asax文件。使用VS.Net创建一个项目,将在Web应用目录中自动建立Global.asax文件,读者可以查看该文件的具体内容,这里就不列出了。用户使用浏览器不能下载或查看这个文件的内容。Global.asax文件实际上是一个可选文件,删除它不会出问题,当然是在没有使用它的情况下。在Global.asax 文件中定义了一个HttpApplication 类的派生类Global,在Global类中可以定义变量、方法、事件处理函数。在类中已预先定义了若干事件的事件处理函数,包括以下事件:Application_Start、Application_End、Application_BeginRequest、Application_EndRequest、Session_Start、Session_End等,还可以增加其它事件处理函数。在Global类中也可以放置一些组件,例如放置组件OleDbAdapter或SqlDataAdapter,也支持可视化设计,例如可以从"工具箱"的"数据"选项卡中,将 OleDbDataAdapter 对象拖到窗体上。"数据适配器配置向导"启动,它将帮助您创建连接和适配器。具体步骤可参见例子e8_12。这些组件可以供所有网页使用。

11.4.3       显示访问网站总人数的例子

例子e11_4_3本例记录并显示建站以来访问网站的总人数。在Application对象中增加一个变量AllVister,记录访问网站的总人数。一个用户访问网站首先产生Session_Start事件,在此事件函数中,AllVister加1。用VS.Net实现的具体步骤如下:

(1)   创建一个Web应用项目,项目名为e11_4_3。

(2)   在global.asax文件中的Application_OnStart事件处理函数中增加语句如下:

Application.Add("AllVister",0);

(3)   在global.asax文件中的Session_Start事件处理函数中增加语句如下:

Application.Lock();

Application["AllVister"]=(int)Application["AllVister"]+1;

Application.UnLock();

(4)   单击VS.Net菜单"文件"|"添加新项(w)…"菜单项,打开"添加新项"对话框,在右侧选中"Web窗体"模板,名称为:WebForm2.aspx,单击"打开"按钮,创建WebForm2窗体。

(5)   放Label和HyperLink控件到WebForm1窗体,HyperLink控件NavigateUrl属性为WebForm2.aspx, 属性Text为"转到WebForm2网页"。

(6)   WebForm1的Page_Load方法中增加如下语句:

void Page_Load(Object src,EventArgs e)

{   if(!Page.IsPostBack)//如果网页响应事件后刷新,计数器不加1

{   Application.Lock();

int num=(int)Application["AllVister"];

Application.UnLock();

Label1.Text="您是第"+Convert.ToString(num)+ "位访问者";

}

}

(7)   放Label和HyperLink控件到WebForm2窗体,HyperLink控件NavigateUrl属性为WebForm1.aspx, 属性Text为"转到WebForm1网页"。

(8)   WebForm2的Page_Load方法中增加如下语句:

void Page_Load(Object src,EventArgs e)

{   if(!Page.IsPostBack)//如果网页响应事件后刷新,计数器不加1

{   Application.Lock();

int num=(int)Application["AllVister"];

Application.UnLock();

Label1.Text="您是第"+Convert.ToString(num)+ "位访问者";

}

}

(9)   运行,在浏览器打开WebForm1网页,查看显示的计数器数值,单击刷新按钮,查看显示的计数器数值是否改变,单击超级链接,转到WebForm2网页,查看显示的计数器数值是否改变,单击超级链接,再转回WebForm1网页,查看显示的计数器数值是否改变。关闭所有网页,等待一段时间,再打开WebForm1.aspx网页,显示的计数器值从1开始,这是因为没有网页访问网站时,Web应用程序关闭,Application对象被自动撤销。在打开新网页,产生Application_OnStart事件,将counter置为0。为了解决此问题,可以建立一个文件,记录访问网站总人数,初值为0,Application_OnStart事件函数中,从文件取出已访问网站总人数,赋值给counter,Application_OnEnd事件函数中,将counter存到文件中。

(10)   单击VS.Net菜单"文件"|"添加新项(w)…"菜单项,出现"添加新项"对话框,在右侧选中"文本文件"模板,名称为:TextFile1.txt,单击"打开"按钮,创建新文件。打开该文件,键入字符’0’,保存文件后,关闭该文件。

(11)   修改global.asax文件中的Application_OnStart事件处理函数语句如下:

string s=Server.MapPath("TextFile1.txt");

Application.Add("counterFile",s);//保存变量在Application_OnEnd事件函数使用

System.IO.StreamReader r=new System.IO.StreamReader (s);

s=r.ReadLine();

r.Close();

Application.Add("AllVister",Convert.ToInt32(s));

(12)   在global.asax文件中的Application_ OnEnd事件处理函数中增加语句如下:

//此时Server对象已不存在,无法用Server对象得到counter_File文件绝对路径

string s=(string)Application["counterFile"];//取出保存的文件的全路径地址

System.IO.StreamWriter w=new System.IO.StreamWriter (s,false);//建立新文件

int num=(int)Application["AllVister"];

w.Write(num.ToString());

w.Close();

(13)   再一次访问WebForm1.aspx网页,看是否已解决以上提出的问题。

11.5  Session对象

用浏览器访问一个网站,从网站的一个网页跳转到另一个网页,有时希望为这些被访问的网页中的数据建立某种联系,例如一个网上商店的购物筐,要记录用户在各个网页中所选的商品。前边用Cookie实现了购物筐。用Session对象也可以解决类似问题。

当用户使用浏览器进入网站访问网站中的第一个网页时,Web服务器将自动为该用户创建一个Session对象,在Session对象中可以建立一些变量,这个Session对象和Session对象中的变量只能被这个用户使用,其它用户不能使用。当用户浏览网站中的不同网页时,Session对象和存储在Session对象中的变量不会被清除,这些变量始终存在。当浏览器离开网站或超过一定时间和网站没有联系,Session对象被撤销,同时存储在Session中的变量也不存在了。用在Session对象中建立的变量的方法,可以在网页之间传递数据。

在ASP中,Session对象的功能本质上是用Cookie实现的,如果用户将浏览器上面的Cookies设置为禁用,那么Session就不能工作。但在ASP.Net中,如在config.web文件中,将<sessionstate cookieless="false" />设置为true,不使用Cookies,Session也正常工作。

11.5.1Session对象的属性、方法和事件

这里只介绍Session对象常用的方法和事件。

l  属性Count:在Session对象中建立的变量的项数。只读属性。

l  属性Keys:Session对象中的变量名也叫键,该属性得到所有键的集合。只读属性。

l  属性Mode:Session对象运行模式(只读),有4种模式,InProc:默认值,Session数据被保存在Web服务器的内存中。Off:Session对象被禁用。SQLServer:使用SQL Server数据库存储Session数据。StateServer:将Session数据存储在远程服务器上。

l  属性TimeOut:Session对象超时时限(分钟为单位)。如果用户在该超时时限之内不刷新或请求网页,则该用户的Session对象将终止。默认值是 2 0 分 钟。

l  方法Abandon:删除Session对象中所有的变量并释放Session对象的资源。如果未明确地调用Abandon方法,一旦超过属性TimeOut指定时间,服务器将删除Session对象。

l  方法Add:加入一个变量到Session对象中,例如Session.Add("string1","test"),表示向Session中加入一个名为string1的变量,其值为字符串"test",其实它的效果和Session("string1")="test"以及Session.item("string1")="test"是一样的。

l  方法RemoveAll 和Clear:清除Session对象中所有变量。

l  事件Session_OnStart:当用户使用浏览器进入网站访问网站中的第一个网页时,发生Session_OnStart事件。服务器在响应请求页之前先执行该事件处理函数。在该事件处理函数中可以判断用户是否登录,或判断是否首先访问了主页,这可用判断是否定义了某Session变量来实现,如果答案为否,可用ResPone.Redirect转向登录网页或主页。

l  事件Session_OnEnd:当浏览器离开网站,或超过属性TimeOut指定时间没有请求或刷新网站中的任何网页,该事件在Session对象被撤销前发生。

11.5.2用Session对象实现网上商店购物筐

本例用Session对象实现网上商店购物筐。由于数据库DepotI的goods表要被网站中所有网页使用,因此在Application_OnStart事件处理函数中,建立表goods的两个视图,一个是所有水果的视图,一个是所有花卉的视图,并把视图类引用变量存到Application中,这样视图类引用变量在整个Web应用程序运行期间都有效,在内存中的视图类对象就不会被垃圾收集器撤销,每个网页都可以直接使用这些视图类对象。购物筐也采用一个表,其字段和表goods相同,其中数量记录用户购买的数量。这个表在Session_Start事件处理函数中建立,表类的引用变量存到Session中,表生命周期从用户访问开始,到用户离开网站结束。水果和花卉分别用两个网页显示,每个网页中都有两个DataGrid控件,一个显示水果或花卉,另一个显示用户选择的商品的编号、货物名称、所选商品的数量,即显示购物筐中所选商品。用VS.Net实现的具体步骤如下:

(1)    创建一个Web应用项目,项目名为e11_5_2。

(2)    双击VS.Net集成环境右侧的解决方案管理器中的global.asax文件,打开global窗体,右击global窗体,在弹出快捷菜单中单击"查看代码"菜单项,打开global.asax源文件。在global.asax文件头部增加语句:using System.Data;using System.Data.OleDb;在global.asax文件中的Application_OnStart事件处理函数中增加语句如下:

string s="Provider=Microsoft.Jet.OLEDB.4.0;";

s+="Data Source=D:\\ASP\\DepotI.mdb";//Data Source两词之间有空格

OleDbConnection conn=new OleDbConnection(s);

s="SELECT * FROM Goods WHERE gID<3";

OleDbDataAdapter da=new OleDbDataAdapter(s,conn);

DataSet ds=new DataSet();

da.Fill(ds,"Table1");//Table1是只有水果的表

s="SELECT * FROM Goods WHERE gID>2";

da=new OleDbDataAdapter(s,conn);

da.Fill(ds,"Table2");//Table2是只有花卉的表

DataView dw1=new DataView(ds.Tables["Table1"]);//水果视图

DataView dw2=new DataView(ds.Tables["Table2"]);//花卉视图

Application["dw1"]=dw1;//保存两个视图类引用变量

Application["dw2"]=dw2;

(3)    在global.asax文件中的Session_Start事件处理函数中增加语句如下:

DataView dw1=(DataView)Application["dw1"];//2个dw1引用同一个视图类对象

DataTable dt=dw1.Table.Clone();//创建一个空表,字段和goods表相同

Session["dt"]=dt;//此表作为购物筐,在用户访问网站期间一直可用

(4)    在WebForm1类中定义DataView类和DataTable类变量:

DataView dataView1;DataTable DataTable1;

(5)    在WebForm1窗体中放置两个DataGrid控件,其属性Name分别为DataGrid1、DataGrid2。

(6)    为Page_Load事件处理函数增加语句:

private void Page_Load(object sender, System.EventArgs e)

{  dataView1=(DataView)Application["dw1"];//dataView1引用水果视图类对象

DataGrid1.DataSource=dataView1;//DataGrid1显示水果商品

DataGrid1.DataBind();//数据绑定

DataTable1=(DataTable)Session["dt"];//DataTable1引用购物筐

DataGrid2.DataSource=DataTable1.DefaultView;//DataGrid2显示购物筐中商品

DataGrid2.DataBind();

}

(7)    右击控件DataGrid1,在弹出快捷菜单中单击"属性生成器"菜单项,打开"DataGrid属性"对话框。在对话框左侧选中"常规",如图10.9C,将标题为"数据源(D):"的ComboBox控件置为空。选中"显示页眉"、"显示页脚"多选框,不选中"允许排序"多选框。在"DataGrid属性"对话框左侧选中"列",如图10.9D,不选中"在运行时自动创建列"多选框,在"可用列(A)"列表框中选中绑定列,单击标题为">"的按钮,将其移到"选定的列(S)"列表框,增加一个绑定列,页眉为"编号",数据字段为gID;用同样的办法增加另外2个绑定列,页眉分别为"货物名称"、"货物数量",数据字段分别为gName、gNum。全部为只读列。在"可用列(A)"列表框中单击"按钮列"前的+号,展开树,可以看到选择项,选中选择项,单击标题为">"的按钮,增加一个按钮列,页眉为"单击按钮购买",Text属性为"购买",命令名为:BuyBtn,按钮类型为LinkButton。

(8)    为按钮列增加事件ItemCommand的事件处理函数如下:

private void DataGrid1_ItemCommand(object source,

System.Web.UI.WebControls.DataGridCommandEventArgs e)

{   string s="gID="+e.Item.Cells[0].Text;//DataGrid1当前行第0列文本,即货物编号

DataRow[] foundRows=DataTable1.Select(s);//查找购物筐中是否有此编号商品

if(foundRows.Length==0)//购物筐中没有此编号商品,在记录购物的表中增加新记录

{   DataRow dr=DataTable1.NewRow();//创建DataTable1表新记录

s=e.Item.Cells[0].Text;//DataGrid1当前行第0列文本,即货物编号

dr["gID"]=Convert.ToInt16(s);//新纪录的gID=货物编号

dr["gName"]=e.Item.Cells[1].Text;//DataGrid1当前行第1列文本,即货物名称

dr["gNum"]=1;//购买数量为1

DataTable1.Rows.Add(dr);//DataTable1表增加新记录

}

else//购物筐中已有所选编号商品,在相应记录中数量字段加1

{   object o=foundRows[0]["gNum"];//原购买商品数量

s=o.ToString();//用int n=foundRows[0]["gNum"]不能通过

int n=Convert.ToInt16(s);

n++;//购买商品数量加1

foundRows[0]["gNum"]=n;

}

DataGrid2.DataBind();

}//还应该将所选货物的存量减1,如付款购买,源数据库也应修改,如不买要恢复原数据

(9)    右击DataGrid2,在弹出快捷菜单中单击"属性生成器"菜单项,打开"DataGrid属性"对话框。在对话框左侧选中"常规",如图10.9C,将"数据源(D):"的ComboBox控件置为空。选中"显示页眉"、"显示页脚"多选框,不选中"允许排序"多选框。在"DataGrid属性"对话框左侧选中"列",如图10.9D,不选中"在运行时自动创建列"多选框,在"可用列(A)"列表框中选中绑定列,单击标题为">"的按钮,将其移到"选定的列(S)"列表框,增加一个绑定列,页眉为"编号",数据字段为gID;用同样的办法增加另外2个绑定列,页眉分别为"货物名称"、"购买数量",数据字段分别为gName、gNum。

(10) 放HyperLink控件到窗体,属性Text="选择花卉",NavigateUrl属性为WebForm2.aspx。

(11) 单击VS.Net菜单"文件"|"添加新项(w)…"菜单项,出现"添加新项"对话框,在右侧选中"Web窗体"模板,窗体名为:WebForm2.aspx,单击"打开"按钮,创建新窗体WebForm2。

(12) 按照为WebForm1窗体增加控件、变量和方法的步骤,为WebForm2窗体增加控件和变量,只是在第6步中,Page_Load事件处理函数增加语句dataView1=(DataView1) Application["dw1"]修改为dataView1=(DataView1)Application["dw2"]。在第10步中,控件HyperLink的NavigateUrl属性为WebForm1.aspx,属性Text="选择水果"。

(13) 在浏览器中输入地址:http://Localhost/WebForm1.aspx,选中某种水果,转到第二个网页WebForm2.aspx,选中某种花卉,购物筐中应显示所选的所有商品。本例不尽合理,读者可以以此为基础修改,创建网上商店。

11.6  Server对象

Server对象提供对Web服务器资源进行访问的方法,主要包括:得到服务器的计算机名称,设置脚本程序失效时间,将HTML的特殊标记转变为ASCII字符,得到文件的真实路径等,本节将逐一介绍这些方法。使用Server 对象也可以从一个网页传递数据到另一个网页。

11.6.1       Server对象属性和方法

这里只介绍Server对象常用的方法和事件。

l  属性MachineName:该属性用来获取当前运行Web应用程序的Web服务器的计算机名称,使用方法如下:string s=Server.MachineName;这个计算机名称可以用如下办法查到:打开"控制面板",选中"系统"中的"计算机名",应和用Server对象的属性MachineName获得计算机名称一致。

l  属性ScriptTimeout:Web应用程序由于运行在计算机网络中,由于网络的原因,一些代码可能无法完成,一直在等待,这将极大消耗Web服务器的资源,为了避免这种情况,可以设置程序运行的最长时间,即设置属性ScriptTimeout,在脚本程序运行超过属性ScriptTimeout指定时间之后即作超时处理,也就停止程序运行。如以下代码指定服务器处理脚本程序在100秒后超时:Server.ScriptTimeout=100,其默认值为90秒。

l  方法HtmlEncode和HtmlDecode:HTML标记语言中,有些ASCII字符被作为标记,例如字符串:<br>中的<和>都是标记,如需要显示这些字符,必须作特殊处理,例如为了在浏览器中正确显示如下字符串:"<br>是换行标记",字符串必须写为如下形式:

<asp:Label id="label1" Text="%3cbr%3c是换行标记" runat=server></asp:Label>;

也可以用Server对象的属性HtmlEncode方法,用法如下:

<asp:Label id="label1" runat=server>Server.HtmlEncode(”<br>是换行标记”)</asp:Label>;

方法HtmlDecode对被HtmlEncode方法编码的字符串进行解码。例如:

string s=Server.HtmlDecode(label1.Text);

l  方法URLEncode和UrlDecode:在URL中,像?、&、/ 和空格这样的字符有特殊意义,因此这些字符在URL中不能作为普通字符使用,用HttpUtility.UrlEncode方法将对具有特殊含义字符做特殊处理。确保所有浏览器均正确地传输 URL 字符串中的文本,见例子e11_2_1C。方法UrlDecode对字符串进行URL解码并返回已解码的字符串,例如String s= Server.UrlDecode(已编码字符串);

l  方法MapPath:网页中网页文件的路径一般是以宿主目录为根目录,不同的系统中,宿主目录所在的实际目录并不相同,而且网页也可能在虚拟目录中。因此网页文件的路径并不是网页文件的实际路径。而在用File类处理文件时,则要求文件的地址必须是实际的全路径,Server对象的MapPath方法提供这两种路径的转换方法,例如,f1.aspx文件存在宿主目录下的Test目录下,用Server对象得到f1.aspx文件绝对路径方法如下:

string s=Serve.MapPath(\Test\f1.aspx);//这里\表示以宿主目录

也可以用如下语句:

string s=Serve.MapPath(Test\f1.aspx);//表示单前网页所在的目录的子目录Test

l  方法Transfer:终止当前网页,转向参数指定的URL路径的一个新页。例子见下节。

11.6.2使用Transfer在网页之间传递数据

11.2.1节介绍了用Request和Response对象在网页之间传递数据,本节介绍使用Transfer在网页之间传递数据。

例子e11_6_2A:创建一个Web网页,将数据发送到另一个Web网页,网页文件如下:

<%@ Page Language="C#" ClassName="FirstPageClass" %>

<html>

<script runat="server">

public string Data1

{  get

{ return  textBox1.Text;     }

}

void ButtonClicked(object sender, EventArgs e)

{ Server.Transfer("e11.6.2B.aspx");    }

</script>

<body>

<form runat="server">

输入数据:<asp:TextBox id="textBox1" runat="server"/> <br>

<asp:Button OnClick="ButtonClicked" Text="单击转向第2个网页" runat=server />

</form>

</body>

</html>

在网页的顶部的@Page指令中,ClassName属性设置本网页有效的类名,类名由程序员定义。然后为要传递到另一个网页的每个值都定义一个具有get访问器的属性,get访问器返回要传递的值,本例是Web窗体的文本框输入的值。必须在服务器端脚本中定义这些属性。当单击了按钮后,要将数据传递到另一个网页时,在按钮的事件处理行数中使用 Server.Transfer("e11_6_2B.aspx")语句,转向e11_6_2B.aspx,同时传递数据。

例子e11_6_2B:创建一个Web网页,接受另一个Web网页传递的数据,网页文件如下:

<%@ Page Language="C#" %>

<%@ Reference Page="e11.6.2A.aspx" %>

<html>

<script runat="server">

FirstPageClass fp;

void Page_Load()

{  if (!IsPostBack)

{  fp= (FirstPageClass)Context.Handler;

Label1.Text=fp.Data1;

}

}

</script>

<body>

<form runat="server">

你好:<asp:Label id="Label1" runat=server>

</form>

</body>

</html>

在网页的顶部增加指令:<%@ Reference Page="e11.6.2A.aspx" %>,其中Page属性值为e11.6.2A.aspx网页。在服务器端脚本中声明变量:FirstPageClass fp,fp将引用发送信息的网页中定义的类的实例。Page_Load 事件处理程序中,用语句fp=(FirstPageClass) Context.Handler引用这个对象。用fp.Data1得到对象中的属性值。

例子e11_6_2C:用VS.Net实现上述功能,具体步骤如下:

(1)    创建一个Web应用项目,项目名为e11_6_2C。

(2)    在WebForm1窗体中放置TextBox控件,Name属性textBox1。

(3)    在WebForm1类中增加属性Data1

public string Data1

{  get

{  return textBox1.Text;   }

}

(4)    在WebForm1窗体中放置Button控件,单击事件处理函数如下:

void button1_Click(object sender, EventArgs e)

{  Server.Transfer("WebForm2.aspx");      }

(5)    单击VS.Net菜单"文件"|"添加新项(w)…"菜单项,出现"添加新项"对话框,在右侧选中"Web窗体"模板,窗体名为:WebForm2.aspx,单击"打开"按钮,创建新窗体WebForm2。

(6)    在WebForm2窗体中放置Label控件,Name属性Label。

(7)    为WebForm2类增加变量public WebForm1 fp;这里WebForm1是在文件WebForm1.aspx定义的类名。

(8)    在Page_Load()增加语句如下:

void Page_Load()

{  if (!IsPostBack)

{ fp = (WebForm1)Context.Handler;

label1.Text=fp.Data1;

}

}

(9)    在WebForm2.aspx 文件Page语句后增加语句:<%@ Reference Page="WebForm1.aspx" %>

(10) 运行,打开WebForm1.aspx,在textBox1输入数据,单击按钮,打开WebForm2.aspx,在其label1中显示输入的数据。

11.7  Cache对象

Cache对象生存期和Application对象生存期一样长,因此,也可以在Cache对象中建立一些网站中所有网页都可使用的公用变量。例如,在Cache对象中增加一个DataSet类变量用语句:Cache["myDataSet"]=DataSet1; 取出DataSet类变量用语句:DataSet dataSet1=(DataSet)Cache["myDataSet"];

和Application对象不同,在Web服务器内存比较紧张时,为了提高Web服务器的性能,Cache对象采用最近最少使用(LRU)方法自动清除不常用的变量。因此每次取出Cache对象中的变量,要检查一下是否为NULL,如果是NULL,则要重新建立DataSet对象。

11.8  Config.web配置文件

ASP.Net的配置文件是基于XML格式的纯文本文件,保存在Web应用目录下,统一命名为"config.web"。它决定了所在目录及其子目录的配置信息。在子目录中可以增加Config.web配置文件,并且子目录下的配置覆盖其父目录的配置。在操作系统安装目录\Microsoft.Net\Framework\版本号\下的config.web为整个机器的根配置文件,它定义了整个环境下的缺省配置。缺省情况下,浏览器是不能够直接访问目录下的config.web文件。在运行状态下,ASP.Net会根据远程URL请求,把访问路径下的各个config.web配置文件叠加,产生一个唯一的配置集合。举例来说,一个对URL: http://localhost\webapp\owndir\test.aspx的访问,ASP.Net会根据以下顺序来决定最终的配置情况:

1..\Microsoft.Net\Framework\v.1.00\config.web (缺省配置文件)

2..\webapp\config.web (应用的配置)

3..\webapp\owndir\config.web (自己的配置)

ASP.Net提供了一个丰富而可行的配置系统,以帮助管理人员轻松快速的建立自己的WEB应用环境。ASP.Net提供的是一个层次配置架构,可以帮助WEB应用、站点、机器分别配置自己的扩展配置数据。ASP.Net的配置系统具有以下优点:

l  ASP.Net允许配置内容可以和静态内容、动态页面和商业对象放置在同一应用的目录结构下。当管理人员需要安装新的ASP.Net应用时,只需要将应用目录拷贝到新的机器上即可。

l  ASP.Net的配置内容以纯文本方式保存,可以以任意标准的文本编辑器、XML解析器和脚本语言解释、修改配置内容。

l  ASP.Net 提供了扩展配置内容的架构,以支持第三方开发者配置自己的内容。

l  ASP.Net配置文件的更新被系统自动监控,无须管理人员手工干预。

VS.Net为每一个Web应用项目自动建立了一个config.web文件如下,它是一个XML文件,这里只对XML文件的注解做了修改,请读者仔细研究该文件,理解其意义。

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<system.web>

<!--  动态调试编译:defaultLanguage="c#"表示本网页使用的默认语言。设置debug="true"启用 ASPX调试,这样设置将一些调试符号插入到编译页中,这将创建执行起来较慢的大文件,因此应该只在调试时将此值设置为true;设置为debug="false"将没有调试功能,但将提高应用程序的运行时性能。调试完成后,应设置debug="false"。有关更多信息,请参考有关调试 ASP.Net 文件的文档。 -->

<compilation  defaultLanguage="c#"  debug="true"   />

<!--  自定义错误信息(customErrors):网页被浏览时可能发生错误,系统可用默认错误网页显示发生错误的详细信息。在创建网页时这些信息对改正错误很有帮助,但在网页被用户浏览时显示默认错误网页是不合适的,此时可用defaultRedirect属性指定显示错误信息的网页的URL(本文件未设置该属性),这个网页被称为:自定义默认错误网页。属性mode="On"将仅用自定义默认错误网页显示错误信息;属性mode= "Off"将仅用默认错误网页显示错误信息;属性mode="RemoteOnly"对于和Web 服务器不在同一台计算机的用户,用自定义默认错误网页显示错误信息,对于和Web 服务器在同一台计算机的用户,用默认错误网页显示错误信息。出于安全目的,建议使用此设置,以便不向远程客户端显示应用程序的详细信息。 -->

<customErrors   mode="RemoteOnly"    />

<!--  身份验证:此节设置应用程序的身份验证策略。可能的模式是 "Windows"、"Forms"、"Passport" 和 "None"。"None" 不执行身份验证。"Windows" IIS 根据应用程序的设置执行身份验证(基本、简要或集成 Windows)。在 IIS 中必须禁用匿名访问。"Forms" 您为用户提供一个输入凭据的自定义窗体(Web 页),然后在您的应用程序中验证他们的身份。用户凭据标记存储在 Cookie 中。"Passport" 身份验证是通过 Microsoft 的集中身份验证服务执行的,它为成员站点提供单独登录和核心配置文件服务。-->

<authentication mode="Windows" />

<!--  授权:此节设置应用程序的授权策略。可以允许或拒绝不同的用户或角色访问应用程序资源。通配符: "*" 表示任何人,"?" 表示匿名(未经身份验证的)用户。 -->

<authorization>

<allow users="*" /> <!-- 允许所有用户 -->

<!--  <allow     users="[逗号分隔的用户列表]"   roles="[逗号分隔的角色列表]"/>

<deny      users="[逗号分隔的用户列表]"   roles="[逗号分隔的角色列表]"/>

-->

</authorization>

<!--  应用程序级别跟踪记录:应用程序级别跟踪为应用程序中的每一页启用跟踪日志输出。设置 trace enabled="true" 可以启用应用程序跟踪记录。如果 pageOutput="true",则在每一页的底部显示跟踪信息。否则,可以通过浏览 Web 应用程序根目录中的 "trace.axd" 页来查看应用程序跟踪日志。 -->

<trace   enabled="false"  requestLimit="10"  pageOutput="false"

traceMode="SortByTime"  localOnly="true"   />

<!--  会话状态设置:默认情况下,ASP.Net使用Cookie来标识哪些请求属于特定的会话。如果 Cookie不可用,则可以通过将会话标识符添加到 URL 来跟踪会话。若要禁用 Cookie,请设置essionState cookieless="true"。  -->

<sessionState    mode="InProc"   stateConnectionString="tcpip=127.0.0.1:42424"

sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"

cookieless="false"     timeout="20"   />

<!--  全球化:此节设置应用程序的全球化设置。  -->

<globalization     requestEncoding="utf-8"   responseEncoding="utf-8"   />

</system.web>

</configuration>

在config.web文件中除了以上设置外,还可以增加自定义标记,用来存储一些在运行中不必修改的数据,例如数据库连接字符串,当把数据库位置移动时,只需修改config.web文件中相关设置。在网页文件中可以用第12章介绍的读写XML文件的方法将有关的设置读出。下边是一个例子:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<system.web>

<appSettings>

<add key=="数据库路径" value=="Provider=Microsoft.Jet.OLEDB.4.0;Data

Source=D:\\vc#\\studentI.mdb"/>

</appSettings>

</system.web>

</configuration>

习题

(1)   如何实现记录访问网站的在线人数。(提示:在Session_End事件函数中计数器减1。)

(2)   用Application对象建立一个2人聊天室。如果是多人聊天室,又如何实现。

(3)   如何防止用户不经过主页或企图不经过登录直接访问其它网页。

(4)   例子e11_3_2中从一个网页转到另一网页时,购物筐显示不正确,请修改。

(5)   例子e11_3_2和e11_5_2完全可使用一个网页,请问如何实现。

(6)   例子e11_5_2中,也可以把DataSet类变量存到Application中,请问如何实现。

(7)   例子e11_3_1中,单击刷新按钮,访问次数也加1,这不合理,如何禁止。

(8)   修改例子e11_5_2,不使用Application对象,改用Cache对象。

(9)   总结一下,从一个网页向另一个网页传递数据的方法。

(10)   创建一个网上书店,具有登录、注册功能,能查询指定书籍,用网页返回,具有购物筐功能,选中商品放入后,显示的商品数量减少,购物后,原数据商品数量也要减少。

第二章        可扩展标记语言

本章介绍XML可扩展标记语言的基本概念和使用,包括使用XML的必要性、XML定义,以及如何建立、显示和处理XML文档数据,XML数据和数据库数据之间的转换等。

12.1  XML可扩展标记语言的基本概念

XML是基于文本的标记语言,它通过有意义的标签以结构化的格式存储数据,这种格式可以被任何一种计算机系统所解释。本节介绍XML的基本概念。

12.1.1       HTML及其缺点

Internet提供了全球范围的网络互连与通信功能,Web技术的发展更是一日千里,其丰富的信息资源给人们的学习和生活带来了极大的便利。特别是应运而生的HTML(超文本标记语言),以简单易学、灵活通用的特性,使人们发布、检索、交流信息都变得非常简单,从而使Web成了最大的环球信息资源库。然而,电子商务、电子出版、远程教育等基于Web的新兴领域的全面兴起,使得传统的Web资源更加复杂化、多样化,人们对Web服务功能的需求也达到更高的标准。而传统的HTML由于自身特点的限制,不能满足这些要求。HTML主要有如下不足:

l  HTML的标记都是预先定义的,用户不能自定义有意义的标记,可扩展性差。

l  HTML的显示方式内嵌在数据中,这样在创建文本时,要同时考虑显示格式,如果因为需求不同而需要对同样的内容进行不同风格的显示时,要从头创建一个全新的文档,重复工作量很大。不能对数据按照不同的需求进行多样化显示。

l  HTML缺乏对数据结构的描述,对于用程序理解文档内容、抽取语义信息都有诸多不便。不能进行智能化的语义搜索。不能对不同平台、不同格式的数据源进行数据集成和数据转化等。

l  HTML语言不能描述矢量图形、数学公式、化学符号等特殊对象。

12.1.2       SGML(标准通用标记语言)

SGML(Standard Generalized Markup Language)是一种通用的文档结构描述标记语言,为文档数据的标记提供了异常强大的工具,同时具有极好的扩展性,因此在数据分类和索引中非常有用。但SGML复杂度太高,不适合网络的日常应用,加上开发成本高、不被主流浏览器所支持等原因,使得SGML在Web上的推广受到阻碍。

12.1.3       XML(可扩展标记语言)

XML(eXtensible Markup Language)是由W3C于1998年2月发布的一种标准。它是SGML的一个简化子集,它将SGML的丰富功能与HTML的易用性结合到Web的应用中。XML的优点如下:

l  XML简单易用,功能强大。

l  XML允许各个组织、个人建立适合自己需要的标记集合,并且这些标记可以用通用的工具显示。例如定义数学、化学、音乐等专用标记。

l  XML的最大优点在于它的数据存储格式不受显示格式的制约。一般来说,一篇文档包括三个要素:数据、结构以及显示方式。XML把文档的显示格式从数据内容中独立出来,保存在样式表文件(Style Sheet)中,这样如果需要改变文档的显示方式,只要修改样式表文件就行了。

l  通过有意义的标签以结构化的格式存储数据,用一种开放的自我描述方式定义数据结构,在描述数据内容的同时突出对结构的描述,从而体现出数据之间的关系,XML的自我描述性质能够很好地表现许多复杂的数据关系,使得基于XML的应用程序可以在XML文件中准确高效地搜索相关的数据内容,忽略其它不相关部分。

l  XML还有其他许多优点,比如它有利于不同系统之间的信息交流,完全可以充当网际语言,并有希望成为数据和文档交换的标准机制。

由于以上优点,XML必将在商务的自动化处理,信息发布,智能化的Web应用程序和数据集成等领域被广泛使用。

12.1.4       XML的文档格式

首先介绍XML文档内容的基本单元——元素,它的语法格式如下:

〈标签〉文本内容〈/标签〉

元素是由起始标签、元素内容和结束标签组成。用户把要描述的数据对象放在起始标签和结束标签之间。例如:<姓名>王平</姓名>。无论文本内容有多长或者多么复杂,XML元素中可以再嵌套别的元素,这样使相关信息构成等级结构。用这样的方法定义XML文档和数据结构。

例子e12_1_4下面的例子是一个描述学生情况的XML文档,在<学生>元素中包括了所有学生的信息,每个学生都由<学生>元素来描述,而<学生>元素中又嵌套了<编号>、<姓名>、<性别>和<年龄>元素。完整XML文件e12_1_4.xml内容如下:

<?xml version="1.0" encoding="GB2312"?>

<?xml-stylesheet type="text/xsl" href="e12_2_1.xsl"?>

<学生>

<编号>001</编号>

<姓名>张三</姓名>

<性别>男</性别>

<年龄>20</年龄>

</学生>

除了元素,XML文档中出现的有效对象是:声明、注释、根元素、子元素和属性。

l  声明:声明给XML解析器提供信息,使其能够正确解释文档内容,它的起始标识是"<?",结束标识是"?>"。例如XML声明:<?xml version="1.0" encoding="GB2312"?>,该声明指明使用的XML版本号和文档使用的字符集是中文字符集"GB2312"。又如显示样式表文件声明:<?xml-stylesheet type="text/xsl" href=" e12_2_1.xsl"?>,指明使用e12_2_1.xsl样式表文件显示本XML文档。

l  注释:注释是XML文件中用作解释的字符数据,XML处理器不对它们进行任何处理。注释文本被"<!--"和" -->"标记,注释可以出现在XML元素间的任何地方,但是不可以嵌套。下边是一个注释的例子:<!--这是一个注释-->。

l  根元素和子元素:如果一个元素从文件头的序言部分之后开始,一直到文件尾,包含了文件中所有的数据信息,我们称之为根元素。XML元素是可以嵌套的,那么被嵌套在内的元素称为子元素。在前面的例子中,<学生>就是根元素,<编号>就是<学生>的子元素。一个XML文档中有且仅有一个根元素,其他所有的元素都是它的子元素。

l  属性:属性给元素提供进一步的说明信息,它必须出现在起始标签中。属性以名称/值成对出现,属性名不能重复,名称与取值之间用等号分隔,取值用引号括起来。例如:<工资 currency="US$"> 25000 </工资>,上例中的属性说明了薪水的货币单位是美元。

l  XML文档的基本结构:XML文档的基本结构由序言部分和一个根元素组成。序言包括了XML声明和DTD或XSD声明,DTD(Document Type Define,文档定义类型)和XSD(XML Schema,XML架构)都是用来描述XML文档的数据结构的。例如,在例子e12_1_4的文档前面加上如下的序言部分,就构成了一个完整的XML文档:

<?xml version="1.0" encoding="GB2312"?>

<?xml-stylesheet type="text/xsl" href="student1.xsl"?>

<!DOCTYPE employees SYSTEM"employees.dtd">

l  格式良好的(Well-Formed)XML文档:一个XML文档首先应当是格式良好的,格式良好XML文档的正式定义位于:http://www.w3.org/TR/REC-xml。格式良好的XML文档除了要满足根元素唯一的特性之外,还包括:

(1) 起始标签和结束标签应当匹配,结束标签是必不可少的。

(2) 大小写应一致,XML对字母的大小写是敏感的,<employee>和<Employee>是完全不同的两个标签,所以结束标签在匹配时一定要注意大小写一致。

(3) 元素应当正确嵌套,子元素应当完全包括在父辈元素中,下面的例子就是错误嵌套:<A> <B> </A> </B>,正确的嵌套方式如下:<A> <B> </B> </A> 。

(4) 属性值必须包括在引号中,元素中的属性名是不允许重复的。

12.1.5       用DTD和XML Schema定义XML架构

DTD(Document Type Definition 文档类型定义)是SGML语言的组成部分,可以用来定义XML文档的数据结构和组成结构的元素类型,可以看作一个或多个XML文档的模板。使用DTD可以对一个XML文档的结构进行校验。它可以是一个独立文件,也可以直接放在XML文档中。例如,例子e12_1_4的DTD文件如下:

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE学生[

<!ELEMENT学生 (编号, 姓名, 性别, 年龄)>

<!ELEMENT编号 (#PCDATA)>

<!ELEMENT姓名 (#PCDATA)>

<!ELEMENT性别 (#PCDATA)>

<!ELEMENT年龄 (#PCDATA)>

]>

由于DTD采用了非XML的语法规则,不支持多种多样的数据类型,扩展性较差等原因,W3C提出了XML Schema(XML架构,XSD),在保留了并扩充了DTD原有的文档结构说明能力的同时,克服了DTD的缺点。XML Schema使用的例子见12.3.5节。

12.1.6       较复杂的XML文档

例子e12_1_6为了说明属性的用法,以及为显示XML文档提供一个例子,这里建立一个较复杂的XML文档,有较多的数据。本例是一个描述书店中所有书籍的XML文档,显示了XML文档的各种元素用法,用记事本程序输入以下内容,网页文件如下:

<?xml version="1.0" encoding="GB2312" ?>

<!--这是一个注释-->

<bookstore>

<book 出版社="电子工业出版社">

<书名>SQL实用全书</书名>

<作者>Rafe Colburn</作者>

<出版日期>2001年6月</出版日期>

<价格>34.00</价格>

</book>

<book 出版社="清华大学出版社">

<书名>C#高级编程</书名>

<作者>Simon Robinson</作者>

<出版日期>2002年6月</出版日期>

<价格>128.00</价格>

</book>

<book 出版社="人民邮电出版社">

<书名>ASP.Net从入门到精通</书名>

<作者>Chris Payne</作者>

<出版日期>2002年1月</出版日期>

<价格>41.00</价格>

</book>

<book 出版社="中国青年出版社">

<书名>精通C#与ASP.Net程序设计</书名>

<作者>孙三才</作者>

<出版日期>2003年6月</出版日期>

<价格>39.00</价格>

</book>

<book 出版社="电子工业出版社">

<书名>ASP.Net实用全书</书名>

<作者>张三</作者>

<出版日期>2004年6月</出版日期>

<价格>55.00</价格>

</book>

</bookstore>

用IE浏览器(5.0以上版本)浏览e12_1_6.xml文件,效果如上图。单击标记前的减号(或加号),看一下效果。上图显示的数据,已用单击减号方法,将最后几本书的数据隐藏。

12.2  XML文档显示

由于XML文档只是定义数据及其数据结构,并不包含显示的格式。如要按指定格式显示这些数据,必须采用其它方法定义显示格式。本节介绍显示XML文档的一些方法。

12.2.1       用XSL文件显示XML文档

使用CSS文件或XSL文件可以定义XML文档的显示格式。这里使用两个XSL文件按不同显示格式显示同一个XML文件。

子e12_2_1:首先定义第一个xsl文件e12_2_1.xsl显示e12_1_4.xml内容。文件如下:

<?xml version="1.0" encoding="GB2312"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">

<xsl:for-each select="学生">

<xsl:value-of select="编号"/>,

<xsl:value-of select="姓名"/>,

<xsl:value-of select="性别"/>,

<xsl:value-of select="年龄"/>

</xsl:for-each>

</xsl:template>

</xsl:stylesheet>

将文件e12_2_1.xsl和e12_1_4.xml存到同一文件夹中。请注意e12_1_4.xml文件中的语句<?xml-stylesheet type="text/xsl" href="e12_2_1.xsl"?>,表示使用e12_2_1.xsl样式表文件显示e12_1_4.xml文件。用IE打开e12_1_4.xml文件,显示效果如上图。

例子e12_2_1B定义第二个xsl文件e12_2_1B.xsl,以不同的显示方式显示e12_1_4.xml文件。文件如下:

<?xml version="1.0" encoding="GB2312"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">

<html>

<body>

<xsl:for-each select="学生">

<table border="1" cellpadding="0" cellspacing="0" bordercolor="#111111"

style="border-collapse:collapse" width="100%" id="AutoNumber1">

<tr>

<td width="50%">编号</td>

<td width="50%"> <xsl:value-of select="编号"/> </td>

</tr>

<tr>

<td width="50%">姓名</td>

<td width="50%"> <xsl:value-of select="姓名"/> </td>

</tr>

<tr>

<td width="50%">性别</td>

<td width="50%"> <xsl:value-of select="性别"/> </td>

</tr>

<tr>

<td width="50%">年龄</td>

<td width="50%"> <xsl:value-of select="年龄"/> </td>

</tr>

</table>

</xsl:for-each>

</body>

</html>

</xsl:template>

</xsl:stylesheet>

将文件e12_2_1B.xsl和e12_1_4.xml存到同一文件夹,修改e12_1_4.xml文件的第2条语句为:<?xml-stylesheet type="text/xsl" href="e12_2_1B.xsl"?>。用IE打开e12_1_4.xml文件,显示效果如上图。

12.2.2       使用XML控件显示XML文档

例子e12_2_2用XML控件也可以显示XML文档,XML控件属性DocumentSource是要显示的XML文件,属性TransformSource是指定显示格式的XSL文件。下边是使用XML控件显示e12_1_4.xml文件的例子。显示效果和例子e12_2_1B相同。

<%@ Page Language="C#" %>

<HTML>

<body>

<h3>使用Xml控件示例</h3>

<form runat="server" ID="Form1">

<asp:Xml id="xml1" DocumentSource="e12_1_4.xml"

TransformSource="e12_2_1B.xsl" runat="server" />

</form>

</body>

</HTML>

12.2.3       使用数据绑定方法显示XML文档

DataSet 类提供了若干方法处理XML文件,主要有:

l GetXml():将DataSet中数据转换为XML格式,以字符串类型返回。

l GetXmlSchema():将DataSet中数据转换为XML格式,以字符串类型返回其XSD架构。

l ReadXml():将包括XML架构和数据的XML文件读入DataSet。

l ReadXmlSchema():将XML架构(XSD)文件读入DataSet。

l WriteXml():将DataSet中数据转换为XML格式写入XML文件,可包含或不包含架构。

l WriteXmlSchema():将DataSet中数据转换为XML格式,将XML架构写入XML文件。

本节仅介绍将XML文档读入DataSet,其它例子见12.4节。

子e12_2_3:XML文档也可以作为控件的数据源,本例使用e12_1_6.xml作为DataGrid控件的数据源,用DataGrid控件把XML文档显示出来。网页文件如下:

<%@ Import Namespace="System.Xml" %>

<%@ Import Namespace="System.Data" %>

<html>

<script runat=server Language="C#">

public void Page_Load(Object sender, EventArgs e)

{  DataSet ds = new DataSet();

ds.ReadXml(Server.MapPath("e12_1_6.xml"));

DataGrid1.DataSource=ds;

DataGrid1.DataMember="book";//将本XML数据看作数据库表book

DataGrid1.DataBind();

}

</script>

<body>

<h2>用数据绑定方法显示XML文档</h2>

<form runat=server>

<asp:DataGrid id="DataGrid1" runat="server"/>

</form>

</body>

</html>

网页的显示效果如上图。将XML文件读入DataSet后,就可以将此文件的内容看作一个数据库表,例如本例为"book",该表也可以用ds.Tables[0]表示,表名为:ds.Tables[0].TableName。

12.2.4       使用VS.Net建立和显示XML文档

例子e12_2_4:本例使用VS.Net建立网页文件,用来显示e12_1_6.xml文档

(1) 创建一个Web应用程序框架,项目名为e12_2_4。

(2)    在窗体中放置控件DataGrid,其属性Name=DataGrid1。

(3)    单击VS.Net菜单"项目"|"添加新项"菜单项,弹出标题为"添加新项"的窗口,在窗口中选中XML文件,文件名为MyXMLFile.xml,单击"打开"按钮,增加一个XML文件。

(4)    将XML文件e12_1_6内容拷贝到MyXMLFile.xml中。使用VS.Net创建XML文档,其XML文档编辑器为编写XML文档提供了一些支持,例如,当加入XML开始标记,将自动增加XML结束标记,并能以网格的形式显示数据。

(5)    单击MyXMLFile.xml窗口下的"数据"标签,可以看到用表格显示的XML文件。

(6) 为Page_Load事件函数增加语句:

private void Page_Load(object sender, System.EventArgs e)

{   DataSet ds = new DataSet();

ds.ReadXml(Server.MapPath("MyXMLFile.xml"));

DataGrid1.DataSource=ds;

DataGrid1.DataMember="book";

DataGrid1.DataBind();

}

(7)    运行,可以看到用表格显示的XML文件,显示效果和例子e12_2_3相同。

12.2.5       将XML文件转换为HTML文件

例子e12_2_5本例将e12_1_4.xml文件,按照e12_2_1B.xsl定义的显示格式生成Html文件e12_2_5.htm。用浏览器IE显示效果和例子e12_2_1B相同。网页文件如下:

<%@ import namespace="System.Xml" %>

<%@ import namespace="System.Xml.XPath" %>

<%@ import namespace="System.Xml.Xsl" %>

<html>

<script language="c#" runat=server>

void Btn_Click(Object src,EventArgs e)

{  XmlDocument doc=new XmlDocument();//创建XmlDocument类的实例

doc.Load(Server.MapPath("e12_1_4.xml"));//读XML文件到内存,形成DOM结构

XPathNavigator nav=doc.CreateNavigator();//nav可以随机查询doc文档的节点

XslTransform Xslt=new XslTransform();//Xslt负责将XML文件转换为HTML文件

Xslt.Load(Server.MapPath("e12_2_1B.xsl"));//装入转换的格式文件

XmlTextWriter writer=new XmlTextWriter(Server.MapPath("e12_2_5.htm"),null);

Xslt.Transform(nav,null,writer);

writer.Close();//上句执行转换,nav查找文档节点,转换后由writer写入html文件

}

</script>

<body>

<form  runat=server>

<asp:button text="转换XML文件为Html文件" Onclick="Btn_Click" runat=server/>

</form>

</body>

</html>

可用浏览器IE显示这个Html文件。转换后的Html文件,有时不能正确显示中文,可在标记<html>后加入如下语句,表示使用中文。

<head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"></head>

12.3  对XML文档的处理

对XML文档的处理是指读取或查找XML文档中指定数据或标记、用程序生成XML文档,修改XML文档等。当前处理XML文档的方法主要有两种:DOM (Document Object Model)和SAX(Simple API for XML)。在.Net框架的System.XML命名空间为处理XML文档提供了若干类。.Net框架支持DOM,提供了XmlDocument类可以按节点读出或查找数据及元素,还可以增加节点,修改数据。.Net框架不支持SAX,但可以使用XmlTextReader类对XML数据流进行快速顺序只读访问,按节点读出或查找指定数据或标记。提供了XmlTextWriter类可用语句快速顺序生成XML文件。本节介绍这些方法。

12.3.1       使用XmlTextReader类读XML文件

XmlTextReader类可以读取XML文件,但只提供非缓存的只进、只读访问。这意味着使用XmlTextReader无法编辑属性值或元素内容,也无法插入和移除节点。

例子e12_3_1本例用来读出e12_1_6.xml文件中每本书的书名、作者、出版日期、价格等数据。使用XmlTextReader类读XML文档各种元素只能顺序读出。运行效果如下图。

<%@ Import Namespace="System.Xml" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{     XmlTextReader dr= new XmlTextReader(Server.MapPath("e12_1_6.xml"));

while(dr.Read())//顺序读出每一个节点

if(dr.NodeType==XmlNodeType.Text)//如果是文本节点,读出数据

ListBox1.Items.Add(dr.Value);//dr.Value是本节点的值

}

</script>

<body>

<form runat=server>

读XML文件数据<br>

<asp:ListBox id="ListBox1" runat="server"/>

</form>

</body>

</html>

在XML文档结构中,把XML文档的基本组成单元叫做节点,例如例子e12_1_4中的XML文档中,<学生>、<编号>、001、</编号>、<姓名>…都是节点。XmlTextReader类的方法Read()读Xml文档时,按节点在XML文档中的顺序逐一读出每一个节点。

XML文档的节点分为两大类,第一类是文本节点,即XML文档的数据。在两个标记之间的文本被称为一个文本节点,例如,<书名>SQL实用全书</书名>中的"SQL实用全书"是一个文本节点。文本节点的类型是XmlNodeType.Text,dr.Value为数据(dr意义见上例)。

第二类是非数据节点,它又可以分为以下几大类:注释节点、声明节点、开始标记节点,结束标记节点,统称为Xml文档的非数据节点。例如,<!--这是一个注释-->是注释节点,节点类型为:XmlNodeType.Comment,dr.Value为注释的内容,这里为"这是一个注释"。<?xml version="1.0" encoding="GB2312" ?>是声明节点,声明节点的节点类型为:XmlNodeType.XmlDeclartion。本声明节点包括两个声明:xml version="1.0"和encoding= "GB2312"。dr.Name为声明的名称,这里为xml version和encoding;dr.Value为声明的值,这里为"1.0"和"GB2312"。<book 出版社="电子工业出版社">是开始标记节点,其节点类型为:XmlNodeType.Element。dr.Name 为标记名称,这里为book。出版社被称为属性名字(Name),"电子工业出版社"被称为属性的值(Value),可以有多个属性,dr.AttributeCount 表示属性的个数,用方法dr.GetAttribute(i)得到第i个属性的值,如果要同时得到属性名字和属性的值,可以使用方法dr.MoveToFirstAttribute()移到第1个属性,方法dr.MoveToNextAttribute()移到下1个属性,然后用dr.Name得到属性名字,用dr.Value得到属性的值。</book>是结束标记,节点类型为:XmlNodeType.EndElement。dr.Name 得到标记名称,这里为book。

本网页的Page_Load方法中,用dr.Read()读Xml文档,每次读出一个节点的数据,用语句if(dr.NodeType==XmlNodeType.Text)判断是否是文本节点,如果是文本节点,则把文本内容加到ListBox1。

12.3.2       使用XmlTextReader类读XML文档标记

例子e12_3_2A本例用来读出e12_1_6.xml文件book标记的属性。具体内容如下:

<%@ Import Namespace="System.Xml" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{     XmlTextReader dr= new XmlTextReader(Server.MapPath("e12_1_6.xml"));

while(dr.Read())

if(dr.NodeType==XmlNodeType.Element)//判断是否为开始标记

for(int i=0;i<dr.AttributeCount;i++)

ListBox1.Items.Add(dr.GetAttribute(i));

}

</script>

<body>

<form runat=server>

读XML文件开始标记的属性<br>

<asp:ListBox id="ListBox1" runat="server"/>

</form>

</body>

</html>

例子e12_3_2B如果显示e12_1_6.xml文档注释,修改上例Page_Load方法如下:

public void Page_Load(Object sender, EventArgs e)

{     XmlTextReader dr= new XmlTextReader(Server.MapPath("e12_1_6.xml"));

while(dr.Read())

if(dr.NodeType==XmlNodeType.Comment)

ListBox1.Items.Add(dr.Value);

}

子e12_3_2C:如果显示e12_1_6.xml文档声明,修改上例Page_Load方法如下:

public void Page_Load(Object sender, EventArgs e)

{     XmlTextReader dr= new XmlTextReader(Server.MapPath("e12_1_6.xml"));

while(dr.Read())

if(dr.NodeType==XmlNodeType.XmlDeclaration)

ListBox1.Items.Add(dr.Name+" "+dr.Value);

}

12.3.3       使用XmlTextWriter类写XML文档

XmlTextWriter类提供了快速、非缓存、只进方法生成XML文档的方法,可以生成包含XML数据的流或文件。该类属性Formatting为Formatting.None,表示不使用特殊的格式设置XML文档,这是默认选项;如果为Formatting.Indented,表示使子元素根据Indentation和IndentChar设置缩进。

<%@ Import Namespace="System.Xml" %>

<html>

<script language="c#" runat=server>

void Btn_Click(Object src,EventArgs e)

{  string s="D:\\asp\\WriteBook\\e12_3_3.xml";

System.IO.FileStream myFileStream=//创建一个写入XML数据的文件流

new System.IO.FileStream(s,System.IO.FileMode.Create);

XmlTextWriter writer=//使用文件流对象创建一个XmlTextWriter对象

new XmlTextWriter(myFileStream, System.Text.Encoding.Unicode);

writer.Formatting = Formatting.Indented;

writer.WriteStartElement("学生");//写开始标记<学生>

writer.WriteAttributeString("编号", "001");//增加属性,标记为<学生 编号="001">

writer.WriteElementString("姓名", "张三");//写:<姓名>张三</姓名>

writer.WriteElementString("性别", "男");

writer.WriteElementString("年龄", "20");

writer.WriteEndElement();//写结束标记<学生/>

writer.Close();

}

</script>

<body>

<form  runat=server>

<asp:button text="用程序写XML文件" Onclick="Btn_Click" runat=server/>

</form>

</body>

</html>

用程序写出的XML文档的书局格式如下:

<学生 编号="001">

<姓名>张三</姓名>

<性别>男</性别>

<年龄>20</年龄>

</学生>

12.3.4       文档对象模型(DOM)使用

文档对象模型(DOM)类是XML文档在内存中表示形式。DOM使程序员能够以编程方式读取、操作和修改XML文档。DOM的节点的概念和12.3.1节中叙述的概念完全相同,因此也可以使用类似XmlTextReader类的方法读出XML文档的数据和非数据节点。下边仅给出读出XML文档的数据的例子,读XML文档中的非数据节点请读者完成。

子e12_3_4A:使用XML文档对象模型(DOM)读出e12_1_6.xml文件中每本书的书名、作者、出版日期、价格等数据。

<%@ Import Namespace="System.Xml" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{  XmlDocument doc = new XmlDocument();//创建XmlDocument类的实例

doc.Load(Server.MapPath("e12_1_6.xml") );//读XML文件到内存,形成DOM结构

XmlNodeReader dr=new XmlNodeReader(doc);

while(dr.Read())

if(dr.NodeType==XmlNodeType.Text)

ListBox1.Items.Add(dr.Value);

}

</script>

<body>

<h2>使用Xml文档对象模型</h2>

<form runat=server>

<asp:ListBox id="ListBox1" runat="server"/>

</form>

</body>

</html>

子e12_3_4B:用文档对象模型(DOM)创建一个XML文档。

<%@ Import Namespace="System.Xml" %>

<%@ Import Namespace="System.IO" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{  XmlDocument doc = new XmlDocument();

doc.LoadXml("<book  ISBN='1-861001-57-5'>" +

"<title>Pride</title>" + "</book>");

string s="D:\\asp\\WriteBook\\e12_3_4B.xml";

doc.Save(s);

}

</script>

<body>

<h2>运行此网页创建一个XML文档,请用IE察看</h2>

</body>

</html>

例子e12_3_4C为例子e12_3_4B中创建的XML文件e12_3_4B.xml增加一个新节点。

<%@ Import Namespace="System.Xml" %>

<%@ Import Namespace="System.IO" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{  XmlDocument doc = new XmlDocument();

doc.Load(Server.MapPath("e12_3_4B.xml"));

XmlNode root = doc.DocumentElement;//获得根节点,既book节点

XmlElement elem = doc.CreateElement("price");//建立新节点,节点名称为price

elem.InnerText="19.95";//新节点的值为19.95

root.AppendChild(elem);//为根节点(book节点)增加一个子节点

doc.Save(Server.MapPath("e12_3_4C.xml"));//存XML文件

}

</script>

<body>

<h2>运行此网页增加一个新节点,请用IE察看</h2>

</body>

</html>

也可以用方法InsertAfter在参数2指定的节点后插入参数1指定的节点,用方法InsertBefore在参数2指定的节点前插入参数1指定的节点,方法的参数1是要插入的节点,例如上例的elem, 参数2指定1个节点,是插入的参考位置,例如:root.FirstChild。

例子e12_3_4D为例子e12_3_4B中创建的XML文件e12_3_4B.xml增加一个新属性。

<%@ Import Namespace="System.Xml" %>

<%@ Import Namespace="System.IO" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{  XmlDocument doc = new XmlDocument();

doc.Load(Server.MapPath("e12_3_4B.xml"));

XmlAttribute newAttr=doc.CreateAttribute("genre");//创建新属性,Name="genre"

newAttr.Value = "novel";//属性值为"novel"

XmlAttributeCollection attrColl=doc.DocumentElement.Attributes;//得到根节点属性

attrColl.Append(newAttr);//为根节点增加一个新属性

doc.Save(Server.MapPath("e12_3_4D.xml"));

}

</script>

<body>

<h2>运行此网页增加一个属性,请用IE察看</h2>

</body>

</html>

例子中的语句doc.DocumentElement为XML文档的根节点,本例为book节点。doc.DocumentElement.Attributes语句得到根节点的所有属性。任何XML文档节点都可用XmlNode节点类对象来代表,XmlNode类属性Attributes表示开始标记节点中的所有属性,该属性是XmlAttributeCollection类对象,可以象操作一个普通数组那样修改该节点的属性。例中用Append方法为根节点增加了一个属性。attrColl.InsertAfter(newAttr, attrColl.ItemOf(0))则表示在第1个属性之后增加新属性,attrColl.InsertBefore(newAttr, attrColl[0]) 则表示在第1个属性之前增加新属性。其它方法,例如Remove、RemoveAll等方法意义可以察看XmlAttributeCollection类帮助文档。请注意对这个数组的操作,就是对节点属性的操作。

子e12_3_4E:查找e12_1_6.xml文档指定节点,修改该节点数据。网页文件如下,第1条语句中的Debug="true"表示允许调试,当发现错误时,在IE浏览器中显示错误信息。

<%@ Page Language="C#" Debug="true" %>

<%@ Import Namespace="System.Xml" %>

<%@ Import Namespace="System.IO" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{  XmlDocument doc = new XmlDocument();

doc.Load(Server.MapPath("e12_1_6.xml"));//e12_1_6.xml和本网页在同一目录

XmlNode book;

XmlNode root = doc.DocumentElement;//得到根节点

//下句查找满足条件的标记名为book的第1个节点,条件为其子节点书名="SQL实用全书"

book=root.SelectSingleNode("descendant::book[书名='SQL实用全书']");

//book.LastChild.InnerText="19.00";//修改最后一个子节点文本,此句也正确

book["价格"].InnerText="19.00";//修改价格节点数据

doc.Save(Server.MapPath("e12_3_4E.xml"));

}

</script>

<body>

<h2>运行此网页修改指定节点数据,请用IE察看</h2>

</body>

</html>

例子e12_3_4F本例显示XML文件e12_1_6.xml的所有书名。

<%@ Page Language="C#" Debug="true" %>

<%@ Import Namespace="System.Xml" %>

<%@ Import Namespace="System.IO" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{  XmlDocument doc = new XmlDocument();

doc.Load(Server.MapPath("e12_1_6.xml"));

//下句得到XML文档中"书名"节点放到节点数组中

XmlNodeList elemList = doc.GetElementsByTagName("书名");

for(int i=0; i < elemList.Count; i++)

{   ListBox1.Items.Add(elemList[i].InnerXml);//显示书名节点的内部数据即书名

}

}

</script>

<body>

<form runat=server>

<asp:ListBox id="ListBox1" runat="server"/>

</form>

</body>

</html>

使用Xml文档对象模型要把整个XML文件到内存,形成DOM结构,但可以修改XML文档。例子e12_2_5也是使用Xml文档对象模型的例子。

12.3.5       用XML Schema验证XML架构

一个XML文档首先应当是格式良好的,为了验证格式的正确性,可以使用XML Schema (XML架构,XSD)对一个XML文档进行验证,下例介绍验证的具体方法。

例子e12_3_5首先按12.4.3节方法为文件e12_1_4.xml建立XSD文件e12_1_4.xsd,然后用下边的网页文件用e12_1_4.xsd对e12_1_4.xml的架构进行验证。

<%@ import namespace="System.Xml" %>

<%@ import namespace="System.IO" %>

<%@ import namespace="System.Xml.Schema" %>

<html>

<script language="c#" runat=server>

void Page_Load(object sender, System.EventArgs e)

{  FileStream fs=new FileStream(Server.MapPath("e12_1_4.xml"), FileMode.Open);

//创建XmlValidatingReader类的对象

XmlValidatingReader vr=new XmlValidatingReader(fs,XmlNodeType.Element,null);

vr.Schemas.Add(null, Server.MapPath("e12_1_4.xsd"));//加载XML架构文档

vr.ValidationType = ValidationType.Schema;//说明是根据XML架构验证

vr.ValidationEventHandler +=new ValidationEventHandler(ValidationHandler);

while(vr.Read());//对文档进行验证,验证不通过,产生事件验证失败事件

label1.Text="架构正确";

fs.Close();

}

private void ValidationHandler(object sender,ValidationEventArgs args)//验证失败事件函数

{  label1.Text="架构不正确";//显示验证失败的消息

}

</script>

<body>

<form  runat=server>

<asp:Label id="label1" runat=server/>

</form>

</body>

</html>

12.4  数据库和XML

XML提供了异构数据库之间交换数据的一种方法。本节介绍这种方法。

12.4.1       数据库数据存为XML文档

察看e12_1_6.xml文件和数据库的表的对应关系,标记<bookstore>之间的内容可以看作一个数据库的表,标记<book>之间的内容可以看作一个数据库的表的一个记录,标记<书名>、<作者>、<出版日期>、<价格>可以看作一个数据库的表的字段,这些标记之间的文本可以看作这些字段的数据。因此,可以用XML文档来表示一个数据库表。由于XML文档可以被任何一种计算机系统所解释,因此XML提供了异构数据库之间交换数据的一种方法。

数据库表的字段还有一些其它属性,例如,字段的数据类型,为了表示这些属性及其数据库表结构,可以使用DTD(Document Type Define,文档定义类型)或XML Schema来描述XML文档的数据结构和组成结构的元素类型。微软的.Net系统支持用XML Schema来描述XML文档的数据结构。VS.Net提供了将数据库表存为带XML架构和不带XML架构XML文件的方法,下边例子介绍实现的具体步骤。

例子e12_4_1将数据库studentI.mdb中的studnt表存为带XML架构或不带XML架构的XML文件。使用VS.Net建立这个ASP.Net网页的具体步骤如下:

(1)  创建Web应用程序项目,项目名为e12_4_1。

(2)  按照8.12节的例子e8_12中的第(4)步到第(8)步创建OleDbConnection对象、OleDbDataAdapter对象和数据集DataSet对象。

(3)  在窗体中放置控件DataGrid,其属性Name=dataGrid1,属性DataSource为dataSet11,属性DataMember为Student。

(4)  在Page_Load函数中增加语句如下:

oleDbDataAdapter1.Fill(dataSet11);

DataGrid1.DataBind();

(5)  运行,应能在DataGrid控件中看到数据库studentI.mdb中的studnt表的数据。

(6)  增加一个按钮,属性ID为Button1,属性Text为"将数据库表存为带XML架构XML文件",为其增加事件处理函数如下:

private void Button1_Click(object sender, System.EventArgs e)

{   string s="D:\\asp\\e12_4_1\\e12_4_1A.xml";

dataSet11.WriteXml(s,XmlWriteMode.WriteSchema);

}

(7)  增加一个按钮,属性ID为Button2,属性Text为"将数据库表存为不带XML架构XML文件",为其增加事件处理函数如下:

private void Button1_Click(object sender, System.EventArgs e)

{   string s="D:\\asp\\e12_4_1\\e12_4_1B.xml";

dataSet11.WriteXml(s,XmlWriteMode.IgnoreSchema);

}

(8)  运行,单击两个按钮,可以创建带XML架构和不带XML架构XML文件,文件名为"e12_4_1A.xml"和"e12_4_1B.xml"。用浏览器察看这两个XML文件,可以看到它们的区别。不使用VS.Net,使用记事本建立这个ASP.Net网页不能完成此功能。

12.4.2       读XML文档到DataSet对象

例子e12_4_2把12.4.1节生成的带XML架构或不带XML架构XML文件用控件DataGrid显示。用记事本生成的网页文件如下:

<%@ Import Namespace="System.Xml" %>

<%@ Import Namespace="System.Data" %>

<html>

<script Language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{  string FileNameString=Server.MapPath("e12_4_1B.xml");//或e12_4_1A.xml

DataSet ds = new DataSet();

ds.ReadXml(FileNameString);

DataGrid1.DataSource=ds.Tables[0].DefaultView;

DataGrid1.DataBind();

}

</script>

<body>

<h2>读带XML架构和不带XML架构XML文件</h2>

<form runat=server>

<asp:DataGrid id="DataGrid1" runat="server"/>

</form>

</body>

</html>

12.4.3       为自建的XML文档建立XSD文件

自己创建的XML文档,如果用手工创建XML Schema是比较困难的,下例介绍如何使用VS.Net为一个XML文档创建XML Schema。

例子e12_4_3为自建的XML文档e12_1_4.xml建立XSD文件。

(1)    运行VS.Net,打开e12_1_4.xml文件。

(2)    单击VS.Net菜单"XML"|"创建架构"菜单项,将创建e12_1_4.xsd文件,打开此文件,可以修改每个字段的数据类型。然后存盘。

(3)    e12_1_4.xsd也是一个XML文档,请用IE浏览器打开e12_1_4.xsd,察看e12_1_4.xsd的内容。

习题

(1)    创建一个记录某专业学生学习科目的XML文档。

(2)    读出e12_1_6.xml文件中所有开始和结束标记,用ListBox控件显示。

(3)    用Xml文档对象模型(DOM)读e12_1_6.xml文件开始和结束标记,用ListBox控件显示。

(4)    编写一个程序,读e12_1_4.xml文件,在网页中按照如下格式输出:

<学生>

<编号>001</编号>

<姓名>张三</姓名>

<性别>男</性别>

<年龄>20</年龄>

</学生>

(5)    编写一个程序,读e12_1_6.xml文件到DataSet中,用TextBox控件显示每一个记录。(提示:将XML文件读入DataSet后,就可以将此文件的内容看作一个数据库表。)

(6)    例子e12_3_1中如果希望只显示书名,如何实现?(提示:先找开始标记为”书名”的节点,如果找到,读下一节点,用dr.Value得到书名。)

(7)    修改例子e12_3_2A,使其能够读出节点属性的名称和和属性的值。

(8)    修改例子e12_3_4E,查找例子e12_1_6.xml中作者名字为张三的书的价格为20.00元。

(9)    对e12_1_6.xml的架构进行验证。

(10) 用记事本程序编制的网页文件,可以将SQL2000数据库系统的例子数据库NorthWind的表Employees存为带XML架构和不带XML架构XML文件。请写出网页文件。

(11) 在8.13节,用xsd文件记录了两个表的主从关系,如果要把两个表以及两个表的主从关系转换为XML文档,则xsd文件要单独存为一个文件,可以使用方法WriteXmlSchema()将DataSet中架构写入XML文档。请把8.13节例子中数据库数据用XML文档保存。