XML 是标准扩展语言的简称,是未来Web编程的标准。在这一章中,我们将讲述XML在ASP.NET中的应用
在这个程序中,我们通过XML语言实现每次访问网页时,将显示不同的广告条。在本例中,我们只调用了两条广告
源文件:advanceapp\intro.aspx
Intro.aspx的代码如下:
<html>
<center><title>广告条演示</title></center>
<head>
<link rel="stylesheet"href="intro.css">
</head>
<body>
<center>
<form action="intro.aspx" method="post" runat="server">
<h2>广告条演示</h2>
<asp:adrotator AdvertisementFile="intro.xml" BorderColor="black" BorderWidth=1 runat="server"/>
</form>
</center>
</body>
</html>
intro.xml的 代码如下:
<Advertisements>
<Ad>
<ImageUrl>./hp1.gif</ImageUrl>
<NavigateUrl>http://www.yesky.com</NavigateUrl>
<AlternateText>欢迎访问!</AlternateText>
<Keyword>Computers</Keyword>
<Impressions>80</Impressions>
</Ad>
<Ad>
<ImageUrl>./hp2.gif</ImageUrl>
<NavigateUrl>http://www.yesky.com</NavigateUrl>
<AlternateText>欢迎访问</AlternateText>
<Keyword>Computers</Keyword>
<Impressions>80</Impressions>
</Ad>
</Advertisements>
在intro.aspx中,我们使用了<asp:adrotator AdvertisementFile="intro.xml" BorderColor="black" BorderWidth=1 runat="server"/>这条语句来嵌入intro.xml文件。运行效果如图:

当我们点刷新按狃或者按F5键,我们将看到另一条广告条。

在本例中,我们用到了AdRotator服务器控件,在xml文件中,我们我们可以定义其属性:如表所示:
|
属性 |
描述 |
|
ImageUrl |
图象文件的绝对或相对地址 |
|
NavigateUrl |
当图象被点击时,可访问相应的网页 |
|
AlternateText |
当鼠标移动到图片上时,将显示提示信息 |
|
Keyword |
指定广告条的分类,我们可以利用此属性来对广告条进行分类 |
|
Impressions |
指定图片在表格中的大小 |
数据访问是一个应用系统的核心。公用语言运行环境(Common Language Runtime)提供了管理数据访问应用程序接口(API)的方法。而这些API将不论它的的数据源是什么,如SQL Server, OLEDB, XML等,都能提取出我们所需要的数据。我们在具体编程的时候,有3个对象将常常用到:Connections, Commands, and DataSets。
|
对象 |
描述
|
|
Connection |
与数据源进行连接。如SQL Server 或者一个XML 文件。
|
|
Command |
对数据进行操纵。如进行删除(delete),提取(select),更新(update)
|
|
Dataset |
显示出所需的数据
|
为了使我们能使用 SQL语句,我们要先导入System.Data 和System.Data.SQL这两个名字空间。
语句如下:
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SQL" %>
下面的表格是对几个典型的SQL 语句的说明:
|
SQL 语句 |
示例
|
|
Select(对单个表的操作) |
SELECT * from Student WHERE stuname = ‘小李’;
|
|
Select(对多个表的操作) |
SELECT * from Student S, Dept D WHERE S.dept= D.dept;
|
|
Insert |
INSERT into Student VALUES ('小王',21,’男’);
|
|
Delete |
DELETE from Student WHERE name=’小王’ ;
|
|
Update |
UPDATE Student SET age = 21 WHERE name=’小李’;
|
在执行查询之前,我们要先构件一个SQLDataSetCommand对象,在执行查询以后,我们需要把数据转移到DataSet 中,我们可以使用下面的代码:
我们假设有一个test数据库,在这个数据库中有一个student表。
Dim myConnection As New SQLConnection("server=localhost;uid=sa;pwd=;database=test")
Dim myCommand As New SQLDataSetCommand("select * from student", myConnection)
Dim ds As New DataSet()
myCommand.FillDataSet(ds, "student")
可能读者不禁要问:为什么要用XML文件存储数据吗?为什么不使用数据库?
这是因为:对很多目的用途来说,用数据库太过浪费了。.要使用一个数据库,你必须安装和支持一个分离的服务器处理进程(a separate server process),它常要求有安装和支持它的管理员(administrator)。你必须学习SQL语句, 并用SQL语句写查询,然后转换数据,再返回。而如果你用XML文件存储数据,将可减少额外的服务器的负荷。还有,你还找到了一个编辑数据的简单方法。你只要使用文本编辑器,而不必使用复杂的数据库工具。XML文件很容易备份,和朋友共享,或下载到你的客户端。同样的,你可以方便地通过ftp上载新的数据到你的站点。
XML还有一个更抽象的优点,即作为层次型的格式比关系型的更好。 它可以用一种很直接的方式来设计数据结构来符合你的需要。你不需要使用一个实体-关系编辑器,也不需要使你的图表(schema)标准化。 如果你有一个元素(element)包含了另一个元素,你可以直接在格式中表示它,而不需要使用表的关联。
注意,在很多应用中,依靠文件系统是不够充分的。如果更新很多,文件系统会因为同时写入而受到破坏。数据库则通常支持事务处理,可以应付所发生的请求而不至于损坏。对于复杂的查询统计要有反复、及时的更新,此时数据库表现都很优秀。当然,关系型数据库还有很多优点,包括丰富的查询语言,图表化工具,可伸缩性,存取控制等等。
在下面这样的案例中,正如大多数中小规模的、基于发布信息的站点一样,你可能涉及的大多数数据存取都是读,而不是写,数据虽然可能很大,但相对来说并没有经常的更新变化,你也不需要做很复杂的查询,即使你需要做,也将用一个独立的查询工具,那么成熟的rdbms的优点消失了,而面向对象型的数据模型的优点则可以得到体现。
最后,为你的数据库提供一个查询器外壳来进行SQL查询并将他们转化进入xml stream也是完全有可能的。
所以你可以选择这二种方式之一。XML正变成一种非常健壮的,便于编程的工具,作为某个成熟的数据库的前端工具来进行存储和查询。(oracle的xsql servlet即是这种技术的一个很好的例子。)
现在我们来介绍一下XML Schemas:
随着XML Schema规范的逐渐普及,它成为W3C推荐标准的那一天已经不远了。我们来看一下这个新的定义文档类型的方法。
一、定义元素
文档类型定义的工作现在大都是由DTDs来完成的,现在的DTD在功能上有一些限制,随着XML应用的越来越广泛,这些限制逐渐阻碍了XML的发展。
DTD的语法不同于XML的语法,因而需要文档编写者另外的学习一套符号语言。而软件也需要另外的一个解析器来对DTD文件进行解析。
在DTD中没有办法来定义能够从程序语言变量直接映射到XML数据的数据类型和数据格式。 没有一组被人所熟知的基本的元素以供文档编写者选择。DTDs是XML从其前辈SGML那儿继承过来的(事实上,XML本身就是一种SGML的简化版本),这个相对比较成熟的技术能够很快的让XML运行起来,并且能够让熟悉SGML的人对XML有更多的感觉。然而,很快的人们就发现,XML需要一种更具表达能力的解决方案,而不仅仅是DTD。
定义元素需要指定元素的名称、元素的内容模式、元素的属性、以及内嵌的子元素。在XML Schemas中,元素的内容模式是通过类型来定义的。一个服从于某个特定的schema的XML文档只能按照schema中定义的元素模式来编写,这同DTD的规则是一样的。元素可以是简单类型的,也可以使复杂类型的。在Schemas规范中定义了很多的简单类型,例如: string,integer和decimal。简单类型的元素不能在包含子元素和属性,而复杂类型的元素则能够嵌套子元素,并能够包含有属性。
我们来看看一个简单的定义元素的例子:
<element name="quantity" type="positive-integer"/>
<element name="amount" type="decimal"/>
我们知道在面向对象的观点中,有聚集和继承的概念,可以在已有的类中衍生出新类。在这儿Schema借用了这些观点,用户也可以通过聚集和继承来在老元素的基础上定义新的元素。聚集能够把一组已存在的元素组合成一个新的元素。继承则通过扩展已存在的元素来定义一个新的元素,并且这个新的元素能够代表被继承的那个元素。
比如,如果我们要从decimal类型中派生一个新的value元素,并让它有这样的内容模式:<value unit="Celsius">42</value>,我们可以这样的定义:
<element name="value">
<complexType base=&single;decimal&single; derivedBy=&single;extension&single;>
<attribute name=&single;unit&single; type=&single;string&single;/>
</complexType>
</element>
下面我们把time和value元素聚集到一个measurement元素中,形成这样的内容模式:
<measurement>
<time>2000-10-08 12:00:00 GMT<time/>
<value unit="Celsius">42</value>
那么最终我们的schema元素定义结果就应该是这样的:
<element name=&single;measurement&single; type=&single;measurement&single;/>
<complexType name=&single;measurement&single;>
<element name=&single;time&single; type=&single;time&single;/>
<element name=&single;value&single; type=&single;value&single;/>
</complexType>
和上面的schema等同DTD是:
<!ELEMENT measurement (time, value)>
<!ELEMENT time (#PCDATA)>
<!ELEMENT value (#PCDATA)>
<!ATTLIST value (unit)>
显然,它所能表达的意思就少了很多。
从Java等面向对象语言中引入的继承的特性,还包括了可以定义抽象元素来迫使实现子类元素,或者定义一个终结元素来禁止元素再被其它元素所继承。这使得从元素到Java或者C++语言的类的一一映射变得更为直观可行。
二、基数表达
XML Schema比起DTD来能够更方便的表达元素基数的概念。所谓基数是指一个元素在文档中出现的次数。在DTD中,使用的是正则表达式来表示基数的,而正则表达式的表达能力在所有的形式化文法中是最低的,这使得你在定义文档的时候会遇到很多的困难。你只能够指定一次(1),零次或者更多 (*),一次或者更多 (+) 这三种基数,并用这些基数序列来构成DTD。XML Schema中则是使用minOccurs和maxOccurs这两个属性来定义元素基数,分别用来指定元素出现的最少次数和最多次数:
<element ref="optionalElement" minOccurs="0"/>
<element ref="twoOrMoreElements" minOccurs="2" maxOccurs="unbounded"/>
<element ref="exactlyOneElement" />
minOccurs和maxOccurs的缺省值都是1,也就是说,当没有指定这两个属性的时候,元素只允许在文档中出现一次。除了这两个属性,你还可以用choice和all这两个元素。元素choice只允许它的所有子元素中的一个出现在文档中。而元素all则最为宽松,能够让其所有的子元素在文档中以任意的顺序出现任意的次数。而这些概念在DTD中都是难以表达的。
<xsd:choice>
<element ref="EitherThis"/>
<element ref="OrThat"/>
</xsd:choice>
<xsd:all>
<element ref="Ying"/>
<element ref="Yang"/>
</xsd:all/>
三、名域
XML Schema中还支持名域。一个Schema除了可以定义XML文档词汇表外,还可以通过名域来定义目标名域,和其它可能会使用到的词汇名域。例如:
<xsd:schema targetNamespace=&single;http://www.physics.com/measurements&single; xmlns:xsd=&single;http://www.w3.org/1999/XMLSchema&single; xmlns:units= &single;http://www.physics.com/units&single;>
<xsd:element name=&single;units&single; type=&single;units:Units&single;/>
<xsd:element name=&single;measurement&single; type=&single;measurement&single;/>
<complexType name=&single;measurement&single;>
<element name=&single;time&single; type=&single;time&single;/>
<element name=&single;value&single; type=&single;value&single;/>
</complexType>
这种机制为建立复杂的名域体系提供了一种模块化而又易于扩展的方法。使得名域的作用能够真正的被体现出来。
XML Schema提供了一个丰富而更具弹性的机制来定义XML文档词汇表。它使用XML语言本身来定义关于一个XML文档的元信息(meta-information),这使得XML的协同工作能力大大的增强了。而许多对于DTD功能上的改进和增强,也使得它最终必定会终结DTD,作为XML的一个标准出现。在Apache Project和IBM alphaworks的主页上现在已经有很多的Schema的工具出现了,你如果有兴趣的话,不妨去看看。
好了,有上面的介绍,我们来看如何从xml文件中读取数据。DataSet控件提供了ReadXml方法。同时在XML文件中,必须存在schema和我们所需要的数据(Data)。
好了,有了上面的介绍,我们先看一下data1.aspx文件:
源文件:advanceapp\data1.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>
<html>
<head>
<title>
XML演示
</title>
</head>
<script language="VB" runat="server">
Sub Page_Load(Src As Object, E As EventArgs)
Dim DS As New DataSet
Dim FS As FileStream
Dim Reader As StreamReader
FS = New FileStream(Server.MapPath("data1.xml"),FileMode.Open,FileAccess.Read)
Reader = New StreamReader(FS)
DS.ReadXml(Reader)
FS.Close()
Dim Source As DataView
Source = new DataView(ds.Tables(0))
MySpan.InnerHtml = Source.Table.TableName
MyDataGrid.DataSource = Source
MyDataGrid.DataBind()
End Sub
</script>
<body>
<center>
<h3><font face="Verdana">XML 演示 <span runat="server" id="MySpan"/></font></h3>
<ASP:DataGrid id="MyDataGrid" runat="server"/>
</center>
</body>
</html>
我们再来看看 xml文件(advanceapp\data1.aspx)的内容:
data1.xml的文件如下:
<center>
<root>
<schema id="DocumentElement" targetNamespace="" xmlns="http://www.w3.org/1999/XMLSchema" xmlns:xdo="urn:schemas-microsoft-com:xml-xdo" xdo:DataSetName="DocumentElement">
<element name="student">
<complexType content="elementOnly">
<all>
<element name="name" type="string"></element>
<element name="age" type="int"></element>
<element name="sex" type="string"></element>
<element name="grade" type="string"></element>
</all>
</complexType>
</element>
</schema>
<DocumentElement>
<student>
<name>jimmy</name>
<age>20</age>
<sex>boy</sex>
<grade>freshman</grade>
</student>
<student>
<name>Mary</name>
<age>20</age>
<sex>girl</sex>
<grade>sophomore</grade>
</student>
<student>
<name>Tom</name>
<age>19</age>
<sex>boy</sex>
<grade>freshman</grade>
</student>
<student>
<name>Susan</name>
<age>20</age>
<sex>girl</sex>
<grade>freshman</grade>
</student>
</DocumentElement>
</root>
</center>
程序运行后演示如下:

当然我们也可以把schema和数据分开成对立的文件。在主文件中,我们要分别使用the ReadXmlData 和 ReadXmlSchema方法。
如:
读取schema.xml的内容:
FS = New FileStream(Server.MapPath("schema.xml"),FileMode.Open,FileAccess.Read) Schema = new StreamReader(FS)
DS.ReadXmlSchema(Schema)
FS.Close()
读取data.xml的内容:
FS = New FileStream(Server.MapPath("data.xml"),FileMode.Open,FileAccess.Read)
Reader = New StreamReader(FS)
DS.ReadXmlData(Reader)
FS.Close()
我们上面提到的data1.xml分成两部分:
第一部分:生成schema.Xml文件:
<schema id="DocumentElement" targetNamespace="" xmlns="http://www.w3.org/1999/XMLSchema" xmlns:xdo="urn:schemas-microsoft-com:xml-xdo" xdo:DataSetName="DocumentElement">
<element name="student">
<complexType content="elementOnly">
<all>
<element name="name" type="string"></element>
<element name="age" type="int"></element>
<element name="sex" type="string"></element>
<element name="grade" type="string"></element>
</all>
</complexType>
</element>
</schema>
第二部分生成data.xml:
<DocumentElement>
<student>
<name>jimmy</name>
<age>20</age>
<sex>boy</sex>
<grade>freshman</grade>
</student>
<student>
<name>Mary</name>
<age>20</age>
<sex>girl</sex>
<grade>sophomore</grade>
</student>
<student>
<name>Tom</name>
<age>19</age>
<sex>boy</sex>
<grade>freshman</grade>
</student>
<student>
<name>Susan</name>
<age>20</age>
<sex>girl</sex>
<grade>freshman</grade>
</student>
</DocumentElement>
通过上面的介绍,使我们对XML在ASP.NET中有了一定的认识。在下面的章节中,我们将对XML进行更深入的讲解。
ASP.NET中的三层结果开发方法,其实其思想跟Java的一样。Java中的三层架构为前端的html、Jsp、Servlet,中间层为JavaBean、EJB,后面为数据库服务器。而在ASP.NET中,前段为html、asp、aspx等,中间层为有.vb、.cs等文件编译而成的.dll控件,后面为数据库服务器。
在我们的三层架构中,我们的数据库层通过中间层来连接以及操作,前端给中间层传递参数,并接受中间层的参数。在我们的ASP.NET中,我们主要关注的是我们的中间层与前端的数据交互。
我们一般统称中间层为组件,组件可以用.vb编译而成,也可以用.cs文件编译而成。中间层一般为.dll文件。微软的.NET技术在这个方面比他的以前的任何版本都要来的简单,这也是它的一打好处之一。以前我们要注册一个.dll文件,有是注册有是重启动,而在.net上,我们的.dll文件拿来就用,不用再考虑注册的问题。
在没有Visual stutio.net之前,我们用写成的.bat文件来把.vb和.cs文件编译成.dll文件,在.bat文件里,我们写入编译的文件名称、相关联的名字空间、要编译成的文件名以及对应的命令名称,然后运行就行了。听起来很复杂,这也是很多初学者在编译第一个.dll文件时所害怕的事情。但是做起来很简单的。下面我们举一个例子来说明.bat文件的写法,假设我们有一个文件名为:saidy.vb的文件,我们要把它编译成saidy.dll的文件,其中用到System、System.Data、System.Data.SQL名字空间,我们可以创建一个dblink.bat文件,内容如下:
vbc /out:..\bin\saidy.dll /t:library /r:system.dll /r:system.data.dll / r:system.data.sql.dll
dblink.vb
这是编译.vb程序的命令,如果是编译.cs文件,则命令会是不一样,我们假定有一个saidy.cs的文件,按照上面的要求,我们编译如下:
cs /out:..\bin\saidy.dll /t:library /r:system.dll /r:system.data.dll / r:system.data.sql.dll
dblink.cs
我们可以看出来,大部分是一样的。
当然,如果我们有微软公司的vs.net编程环境,则我们不用这么麻烦,我们可以象编译vb或者vc程序一样方便的编译.dll文件。微软公司的vs.net是一个集大成者,把各种语言整合起来,在这个环境下都可以写出不同语言的程序。具体的应用我们会在专门的章节上介绍的。
我们通过具体的例子来说明三层架构的应用,我们建一个小项目来说明这个问题。有时为了安全性,我们通常把与数据库的连接用一个动态连接库文件封装起来,这样我们就要把写数据库连接的.vb或者.cs文件编译成动态连接库.dll文件。甚至我们把对数据库的相关操作页编译成.dll文件。
下面是我们的与数据库连接以及操作的文件dblink.vb的主要部分,对数据库的连接:
Dim dbl As SQLConnection
对数据库的操作,我们把它写在一个方法里面,在返回相应值:
Function getdata() as DataView
Dim sComm as SQLDataSetCommand
Dim sDS as DataSet
Dim sStr as String
dbl = New SQLConnection("server=localhost;uid=sa;password=;database=howff")
sStr = "select * from color"
sComm = new SQLDataSetCommand(sStr,dbl)
sDS = new DataSet()
sComm.FillDataSet(sDS,"color")
Return sDS.Table["color"].DefaultView
End Function
我们第六个语句就用到上面的与数据库的连接变量,我们这个函数的功能是从表“color“中选出所有的元素,并返回表结构的形式。完整的代码如下:
Imports System
Imports System.Data
Imports System.Data.SQL
'创建名字空间
Namespace db
'创建一个类
Public Class dblink
'建立数据库的连接
Dim dbl As SQLConnection
'方法
Public Function getdata() As DataView
Dim sComm As SQLDataSetCommand
Dim sDS As DataSet
dbl = New SQLConnection("server=localhost;uid=sa;password=;database=howff")
Dim sStr As String
sStr = "select * from color"
sComm = New SQLDataSetCommand(sStr, dbl)
'填充数据
sDS = New DataSet()
sComm.FillDataSet(sDS, "color")
'返回
Return sDS.Tables("color").DefaultView
End Function
End Class
End Namespace
我们再写一个前端掉用页面saidy.aspx,我们首先要引入我们创建的名字空间:
<%@ Import Namespace="db" %>
在页面装入的时候,我们用此方法:
Sub Page_Load(Sender As Object, E As EventArgs)
'建立一个新的对象
Dim newdb As dblink
newdb = new dblink()
'数据来源
Products.DataSource = newdb.getdata()
'数据绑定
Products.DataBind()
End Sub
下面看看我们完整的代码(advanceapp\dblink.aspx):
<%@ Import Namespace="db" %>
<html>
<script language="VB" runat="server">
Sub Page_Load(Sender As Object, E As EventArgs)
'建立一个新的对象
Dim newdb As dblink
newdb = new dblink()
'数据来源
Products.DataSource = newdb.getdata()
'数据绑定
Products.DataBind()
End Sub
</script>
<body style="font: 10pt verdana" bgcolor="CCCCFF">
<BR><BR><BR>
<CENTER>
<h3>.NET->三层架构!</h3>
</CENTER>
<BR><BR>
<CENTER>
<ASP:DataList id="Products" ShowHeader=false ShowFooter=false RepeatColumns="2" RepeatDirection="horizontal" BorderWidth=0 runat="server">
<template name="itemtemplate">
<table>
<tr>
<td width="150" style="text-align:center; font-size:8pt; vertical-align:top;
height:50">
<p>
<%# DataBinder.Eval(Container.DataItem, "id") %> <br>
<%# DataBinder.Eval(Container.DataItem, "name", "{0:C}").ToString() %>
</td>
</tr>
</table>
</template>
</ASP:DataList>
</CENTER>
</body>
</html>
我们看到,在这个页面当中,没有出现与数据库交互的语句,这样我们就很好的把数据操作封装起来了,我们的运行效果如下:

在本章中,我们讲解一个基于三层架构的例子,这只是一个非常简单的例子,我们知道.NET在这方面的功能是非常强大的,你可以用它来写非常复杂的组件。
MSMQ(MicroSoft Message Queue,微软消息队列)是在多个不同的应用之间实现相互通信的一种异步传输模式,相互通信的应用可以分布于同一台机器上,也可以分布于相连的网络空间中的任一位置。它的实现原理是:消息的发送者把自己想要发送的信息放入一个容器中(我们称之为Message),然后把它保存至一个系统公用空间的消息队列(Message Queue)中;本地或者是异地的消息接收程序再从该队列中取出发给它的消息进行处理。
在消息传递机制中,有两个比较重要的概念。一个是消息,一个是队列。消息是由通信的双方所需要传递的信息,它可以是各式各样的媒体,如文本、声音、图象等等。消息最终的理解方式,为消息传递的双方事先商定,这样做的好处是,一是相当于对数据进行了简单的加密,二则采用自己定义的格式可以节省通信的传递量。消息可以含有发送和接收者的标识,这样只有指定的用户才能看到只传递给他的信息和返回是否操作成功的回执。消息也可以含有时间戳,以便于接收方对某些与时间相关的应用进行处理。消息还可以含有到期时间,它表明如果在指定时间内消息还未到达则作废,这主要应用与时间性关联较为紧密的应用。
消息队列是发送和接收消息的公用存储空间,它可以存在于内存中或者是物理文件中。消息可以以两种方式发送,即快递方式(express)和可恢复模式(recoverable),它们的区别在于,快递方式为了消息的快速传递,把消息放置于内存中,而不放于物理磁盘上,以获取较高的处理能力;可恢复模式在传送过程的每一步骤中,都把消息写入物理磁盘中,以得到较好的故障恢复能力。消息队列可以放置在发送方、接收方所在的机器上,也可以单独放置在另外一台机器上。正是由于消息队列在放置方式上的灵活性,形成了消息传送机制的可靠性。当保存消息队列的机器发生故障而重新启动以后,以可恢复模式发送的消息可以恢复到故障发生之前的状态,而以快递方式发送的消息则丢失了。另一方面,采用消息传递机制,发送方必要再担心接收方是否启动、是否发生故障等等非必要因素,只要消息成功发送出去,就可以认为处理完成,而实际上对方可能甚至未曾开机,或者实际完成交易时可能已经是第二天了。
采用MSMQ带来的好处是:由于是异步通信,无论是发送方还是接收方都不用等待对方返回成功消息,就可以执行余下的代码,因而大大地提高了事物处理的能力;当信息传送过程中,信息发送机制具有一定功能的故障恢复能力;MSMQ的消息传递机制使得消息通信的双方具有不同的物理平台成为可能。
在微软的.net平台上利用其提供的MSMQ功能,可以轻松创建或者删除消息队列、发送或者接收消息、甚至于对消息队列进行管理。
在.NET产品中,提供了一个MSMQ类库“System.Messaging.dll”。它提供了两个类分别对消息对象和消息队列对象进行操作。在能够使用MSMQ功能之前,你必须确定你的机器上安装了MSMQ消息队列组件,并确保服务正在运行中。在使用ASP.NET编程时,应在头部使用:
<%@ Assembly Name=”System.Messaging”%>
<%@ Import NameSpace=”System.Messsaging”%>
将MSMQ类库引入ASP.NET文件
1. 对消息队列的创建
dim MsgQue as MessageQueue
MsgQue=New MessageQueue(MsgPath)
其中:MsgPath可以为本地私有队列,如“.\MyQueue”,也可以为其他机器的公有队列,如“Saidy\777$\MyQueue”,Saidy为另一机器名。
2. 消息的发送
dim MsgQue as MessageQueue
MsgQue.Send(Msg)
其中:Msg为任一对象。
3. 消息的接收
消息的接收又分成同步和异步方式两种,同步接收在规定时间内从消息队列中取出收到的第一条消息,当消息队列中没有消息时,程序处于等待状态;异步接收方式则是定义了一个事件处理函数,当消息队列中第一个消息到达时立即触发该函数。
1) 同步方式
dim Msg as Message
dim Fmt As XmlMessageFormatter
Fmt= CType(MsgQue.Formatter,XmlMessageFormatter)
Fmt.TargetTypeNames = new String(){"System.String"}
Msg=MsgQue.receive(New TimeSpan(0,0,3))
首先定义收到消息应转换成的格式,然后在指定时间内去接收消息
2) 异步方式
dim Fmt As XmlMessageFormatter
‘定义接收消息类型
Fmt = CType(MsgQue.Formatter,XmlMessageFormatter)
Fmt.TargetTypeNames = new String(){"System.String"}
‘定义消息处理函数入口
AddHandler MsgQue.ReceiveCompleted, New ReceiveCompletedEventHandler
(AddressOf OnReceiveCompleted)
‘定义消息处理函数
Public Shared Sub OnReceiveCompleted(s As Object, asyncResult As ReceiveAsyncEventArgs)
Dim MsgQue As MessageQueue = CType(s,MessageQueue)
Dim Msg As Message = MsgQue.EndReceive(asyncResult.AsyncResult)
‘此时Msg.Body即为所取消息对象
MsgQue.BeginReceive()
‘重新定义异步接收方式
End sub
‘启动异步接收方式
MsgQue.BeginReceive
如图所示:
消息队列常用操作:
。用create方法创建你指定路径的消息队列,使用delete方法删除一个已经存在的消息队列。
。使用exists方法判别是否存在一个消息队列。
。使用GetPublicQueues方法获取消息队列网络中的一个消息队列
。使用Peek或者是BeginPeek方法查看消息队列中的消息,而不会删除它们
。使用Receive或者上BeginReceive方法从消息队列中取出一个消息,同时在消息队列中删除它。
。使用Send方法,送一个消息到指定的消息队列中。
1. 创建消息队列
。创建公共消息队列
MessageQueue.Create(“MyMachine\MyQueue”)
。创建私有消息队列
MessageQueue.Create(“MyMachine\Private$\MyPrivateQueue”)
说明:标识Private$表示创建的是私有消息队列
2. 队列引用说明
当你创建了一个MessageQueue部件的一个实例以后,就应指明和哪个队列进行通信。在。Net 中有3种访问指定消息队列的方法:
。使用路径,消息队列的路径被机器名和队列名唯一确定,因而可以用消息队列路径来指明使用的消息队列。
。使用格式名(format name),它是由MSMQ在消息队列创建时或者应用程序在队列创建以后生成的唯一标识。
。使用标识名(label),它是消息队列创建时由队列管理者指定的带由描述意义的名字。
它可能并不唯一。
采用路径(path)方式引用队列
|
消息队列类型 |
路径使用格式 |
|
Public queue
|
MachineName\QueueName |
|
Private queue
|
MachineName\Private$\QueueName |
|
Journal queue
|
MachineName\QueueName\Journal$ |
|
Machine journal queue
|
MachineName\Journal$ |
|
Machine dead letter queue
|
MachineName\Deadletter$ |
|
Machine transactional dead letter queue
|
MachineName\XactDeadletter$ |
。因为消息队列服务器接收到一个使用路径方式使用消息队列的操作请求时,会去解析出路径和格式名(format name),因此它的效率上不如格式名方式使用队列。
。消息队列未连接时,只能使用格式名方式对它发送消息。
路径名的引用除了path属性以外,还可以由MachineName和QueueName两个属性得到。
路径引用的例子:
MessageQueue1.path=”.\MyQueue”
采用格式名(format name)方式引用队列
格式名由公有私有标识串加上队列产生的GUID,以及其他必需的标识构成。
|
消息队列类型 |
格式名的构成规则 |
|
Public queue |
PUBLIC=QueueGUID
|
|
Private queue |
PRIVATE=MachineGUID\QueueNumber
|
|
Journal queue
|
PUBLIC=QueueGUID;JOURNAL 或者 PRIVATE=MachineGUID\QueueNumber;JOURNAL |
|
Foreign queues
|
DIRECT=AddressSpecification\QueueName
|
格式名由不由用户指定,而是在队列创建时由队列管理者自动产生。
。当你的部件作为一个WEB sevice或者是WEB调用的一部分的时候,最好采用格式名方式引用队列,因为它速度较快。
。当向一个非连接的队列发送消息时,应使用格式名方式,因为当队列不可连接时,路径解析会导致失败。
。网络拓扑结构发生变化或者消息队列重建以后,格式名会变化。
可以由消息队列对象的FormatName属性得到格式名。
例如:采用格式名方式引用消息队列的例子
MessageQueue1.Path = "FORMATNAME:PUBLIC=3d3dc813-c555-4fd3-8ce0-79d5b45e0d75"
采用标识方式引用消息队列
标识是消息队列创建时,由消息队列创建者指定由于描述队列的文本属性。采用标识的好处在于屏蔽了低层的具体位置,对于移植和程序修改时,应用的修改很小。它的缺点也是显然的,就是不能保证标识的唯一性,当标识有冲突时,向该标识发送消息会导致一个错误的发生。标识可以由访问消息队列对象的Label属性得到。
总结,我们对消息队列引用的步骤大致为:
首先,产生一个MessageQueue对象的实例。
然后,根据引用消息队列的方式设置不同的属性。
如果为路径方式,设置它的path属性
如果为格式名方式,设置其FormatName属性
如果为标识方式,设置它的Label属性
3. 删除消息队列
删除一个队列使用Delete方法。当删除一个队列时,队列中含有的所有消息将首先被删除,然后删除该队列,它不会把消息队列中的信息发往死信队列中。删除一个队列最主要的问题是用户是否有删除该队列的足够的权限。
使用Delete方法的例子如下:
MessageQueue.Delete(“MyMachine\MyQueue”)
4.清除消息队列中的内容
有时我们需要把送入消息队列中而尚未发出的消息清除,或者定期需对消息发送日志队列进行清除,可以使用消息队列对象提供的Purge方法。它把指定的消息队列中的消息全部清空,并不再发送。
使用的例子如下:
MessageQueue1.Path="MyMachine\MyQueue"
MessageQueue1.Purge()
5.创建消息队列对象的实例
创建一个消息队列实例的步骤如下:
1. 创建一个MessageQueue类的实例
例如:
dim MyQue as New MessageQueue
2. 对MessageQueue类实例的path属性进行设置
例如:
MyQue.path=”.\MyQueue”
3.设置你需要其他MessageQueue类的属性
6.消息队列配置属性
关于队列的属性:
path属性:它可以决定引用队列的三种方式,路径引用、格式名引用、标识引用
category属性:标识当前使用的队列的类型。Category是队列所有者定义的GUID值。该GUID值可以有GUID生成工具产生或者是用户自定义的数字值。GUID值不会唯一,这样才可以根据相同的GUID值,把多个消息队列划分为不同的类别(category)。
跟发送数据类型相关的属性
Formatter属性:决定在一个队列中如何发送和接收消息的顺序,以及可以在一个消息中发送什么样的内容。
和队列交互相关的属性
DenyShareReceive属性:决定同一时间内只有一个部件能够访问消息队列中的消息。
CanRead和CanWrite属性:决定队列是否可以被读取或者是写入。
MaximumQueueSize和MaximumJournalSize属性:以千字节为单位设置一个队列(日志队列)的消息最大容纳量。一旦接收的消息到达这个容量,新的消息将不再被接收。
一般情况下,消息队列的最大值为消息队列管理员所设置,如果这个值没有控制的话,那么缺省的消息队列最大容量将是无限制的。
UseJournalQueue属性::设置是否将收到的消息拷贝到日志消息队列中去。
7.消息发送
MSMQ消息队列中定义的消息由一个主体(body)和若干属性构成。消息的主体可以由文本、二进制构成,根据需要还可以被加密。你可以在属性窗口或者是直接对消息对象的属性进行赋值。但是,在MSMQ中消息的大小不能够超过4MB。
消息可以被送往公用、私有、日志、死信、交易队列。
。简单消息的发送
简单消息发送的步骤大致为:
1.建立与要发送消息的队列的连接
2.指定你要发送的消息的格式
3.提供要发送的数据
4.使用Send方法将消息发送出去
采用简单消息发送方式发送的数据类型可以是:对象、原数据类型、数据流或其他简单的数据类型。
例子:以发送一个整型数和字符串为例
首先创建一个连接:
Dim MessageQueue1 as new MessageQueue ("MyMachine\MyQueue")
或者
Dim MessageQueue1 as New MessageQueue
MessageQueue1.path=”MyMachine\MyQueue”)
由于是标准数据类型,消息格式可以不指定,使用缺省的
然后发送数值1
MessageQueue1.Send(1)
再发送字符串“hello world”
MessageQueue1.Send("Hello world")
。发送复杂的消息
相对于简单的消息发送,发送较为复杂的消息对象能够给你带来对消息更多的控制的控制能力。对于复杂对象的发送,我们是通过创建消息的对象的实例,然后对它的属性实行相应设置来实现的。
发送复杂消息的步骤大致为:
1. 创建一个MessageQueue的实例,然后对它的path属性设置,以指明操作的消息队列。
2. 创建一个消息对象实例。
3. 设置消息的主体(body), 然后修改不希望使用缺省值的属性。
4. 同样使用send方法把消息对象发送至相应的队列中。
例子:
‘创建MessageQueue实例,指明连接的队列
dim MessageQueue1 as New MessageQueue(“.\MyQueue”)
‘创建消息对象实例
dim MyMessage as New Message(“Hello world”)
‘设置消息队列属性
MyMessage.Label=”MyLabel”
‘发送消息
MessageQueue1.Send(MyMessage)
8.消息接收
消息的接收分为两种,即同步和异步。
同步方式,我们使用receive方法。当调用receive方法时,它会从指定的消息队列中选取出第一个适合要求的消息对象返回给用户,然后把它从消息队列中删除。如果调用receive方法的时候,消息队列中没有一条记录存在,那么方法将导致程序挂起,直到有消息到达消息队列中。为了不使这个等待过程太长,你可以在调用Receive方法时指定time-out值(毫秒为单位),以指定在相应时间到达后退出等待过程。在使用Receive方式时,还可以指定消息队列的DenyShareReceive属性,防止其他用户对队列进行操作。试想这样一种情况,当消息队列中剩一条消息的时候,2个用户同时对它调用peek方法,发现都有消息,于是都放心的使用Receive方式去获取消息,结果必然导致其中一个用户被挂起。如果,在调用receive方法以前使用了DenyShareReceive属性拒绝其他用户的对该队列的Receive方法,就会避免上述现象的发生。
同步接收的例子:
dim MyMessageQue as MessageQueue
MyMessageQue=New MessageQueue(“MyMachine\MyQueue”)
‘指定要连接的消息队列
dim MyMessage as New Message
MyMessage=MyMessageQue.Receive(1000)
‘同步收取一条消息,超时时间为1秒
Peek方法和Receive方法比较类似,只是它并不把取得的消息对象从消息队列中删除。Peek方法只取出消息队列中的第一条消息,若要取得所有的消息可以使用