HTTP 管道一旦调用了 .asmx 句柄,便开始了 XML 、 XSD 、 SOAP 和 WSDL 的处理。 .asmx 句柄提供的余下的功能被分为三个领域:
消息分派
当 .asmx 句柄被 HTTP 管道调用时,通过查看 .asmx 文件中的 WebService 声明,确定检查哪个 .NET 类。然后它观察到来的 HTTP 消息中的信息,确定调用引用类中的哪个方法。为了调用前面例子中的 Add 方法, HTTP 请求消息应像下面一样:
POST /math/math.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://tempuri.org/Add"
1<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
2
3
4 <soap:body>
5
6
7 <add xmlns="http://tempuri.org/">
8
9
10 <x>33</x>
11
12
13 <y>66</y>
14
15
16 </add>
17
18
19 </soap:body>
20</soap:envelope>
上面的 HTTP 请求消息中有两条信息可以用来确定调用类中的哪个方法: SOAPAction 头或 soap 体中请求元素的名字。在这个例子中,每种方法都指出了发送者想调用的方法名。
.asmx 句柄使用 SOAPAction 头的值来实现消息的分派。因此, .asmx 句柄查看消息中的 SOAPAction 头,使用 .NET 映射检查引用类中的方法。它只考虑标记了 [WebMethod] 属性的方法,但通过查看每种方法的 SOAPAction 值再具体确定调用哪个方法。因为我们在类中并没有明确的指定 SOAPAction 的值, .asmx 句柄认为 SOAPAction 的值是 Web 服务的名称空间加上方法名。而且我们也没有指定名称空间,所以句柄就把 http://tempuri.org 作为默认值。这样 Add 方法的默认 SOAPAction 值就是 http://tempuri.org/Add 。
可以按如下方法定制 Web 服务的名称空间。把类标记上 [WebService] 属性,用 [SoapDocumentMethod] 属性标记 WebMethods 来指定具体的 SOAPAction 值。示例如下:
using System.Web.Services;
using System.Web.Services.Protocols;
[WebService(Namespace="http://example.org/math")]
public class MathService
{
[WebMethod]
public double Add(double x, double y) {
return x + y;
}
[WebMethod]
[SoapDocumentMethod(Action="urn:math:subtract")]
public double Subtract(double x, double y) {
return x - y;
}
...
}
现在 .asmx 句柄认为 Add 方法的 SOAPAction 值是 http://example.org/math/Add ** , ** SubTract 的值是 urn:math:subtract (因为我们在类中明确定义了)。比如下面的 HTTP 请求消息调用 Subtract :
POST /math/math.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "urn:math:subtract"
1<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
2
3
4 <soap:body>
5
6
7 <subtract xmlns="http://example.org/math">
8
9
10 <x>33</x>
11
12
13 <y>66</y>
14
15
16 </subtract>
17
18
19 </soap:body>
20</soap:envelope>
如果 .asmx句柄没为HTTP请求消息找到一个SOAPAction匹配,将会抛出一个异常。如果你不想依赖SOAPAction头来分派消息,可以引导.asmx句柄使用请求元素名称。采用这种方法需要为类标记上[SoapDocumentService]属性的RoutingStyle特性,同时也应该指出WebMethods不需要SOAPAction值(在类中设定其值为空)。如下所示:
using System.Web.Services;
using System.Web.Services.Protocols;
[WebService(Namespace="http://example.org/math")]
[SoapDocumentService(
RoutingStyle=SoapServiceRoutingStyle.RequestElement)]
public class MathService
{
[WebMethod]
[SoapDocumentMethod(Action="")]
public double Add(double x, double y) {
return x + y;
}
[WebMethod]
[SoapDocumentMethod(Action="")]
public double Subtract(double x, double y) {
return x - y;
}
...
}
在这种情况下,句柄甚至不关心 SOAPAction 的值,它使用请求元素的名字确定调用方法。比如,在下面的 HTTP 请求消息中,它希望调用 Add 方法的请求元素的名字是 Add :
POST /math/math.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: ""
1<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
2
3
4 <soap:body>
5
6
7 <add xmlns="http://example.org/math">
8
9
10 <x>33</x>
11
12
13 <y>66</y>
14
15
16 </add>
17
18
19 </soap:body>
20</soap:envelope>
所以当 .asmx 句柄接收到 HTTP 消息时它要做的第一件事情就是决定如何分派消息到对应的 WebMethod 。在它真正调用方法之前,还需要将到来的 XML 映射到 .NET 对象。
将 XML 映射到对象
一旦 WebMethod 句柄决定了调用哪个方法,它就会将 XML 消息反串行化为 .NET 对象。随着消息分派,句柄通过 reflection 检查类,然后决定怎样处理 XML 消息。 XmlSerializer 类自动完成 XML 和 System.Xml.Serialization 名称空间中类的映射。
XmlSerializer 能实现任何 .NET 公共类型到 XML Schema 类型的映射,有了这个适当的映射,它能自动的实现 .NET 对象和 XML 实例文档的映射(见图 4 )。 XmlSerializer 受 XML Schema 所支持功能的限制,虽不能处理所有复杂的现代对象模型(如非树型的对象图),却能处理开发者常用的复杂类型。
再看前面 Add 的例子, XmlSerializer 将把 x 和 y 元素映射为 .NET 的 double 值(调用 Add 方法时必须提供的)。 Add 方法返回一个 double 类型值给调用者,这也需要被串行化为 SOAP 应答消息中的一个 XML 元素。
** Figure 4. Mapping XML to objects **
XmlSerializer 也能自动处理一些复杂类型(除了上面说到的一些限制)。比如,下面的 WebMethod 计算两个点结构之间的距离。
using System;
using System.Web.Services;
public class Point {
public double x;
public double y;
}
[WebService(Namespace="urn:geometry")]
public class Geometry {
[WebMethod]
public double Distance(Point orig, Point dest) {
return Math.Sqrt(Math.Pow(orig.x-dest.x, 2) +
Math.Pow(orig.y-dest.y, 2));
}
}
请求此操作的 SOAP 消息将包含一个 Distance 元素,它包含了两个子元素,一个称作 orig ,另一个是 dest ,每一个都包括了 x 和 y 元素,如下所示:
1<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
2
3
4 <soap:body>
5
6
7 <distance xmlns="urn:geometry">
8
9
10 <orig>
11
12
13 <x>0</x>
14
15
16 <y>0</y>
17
18
19 </orig>
20
21
22 <dest>
23
24
25 <x>3</x>
26
27
28 <y>4</y>
29
30
31 </dest>
32
33
34 </distance>
35
36
37 </soap:body>
38</soap:envelope>
这种情况下 SOAP 应答消息将包含一个 DistanceResponse 元素,它包含一个 double 类型的 DistanceResult 子元素。
1<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
2
3
4 <soap:body>
5
6
7 <distanceresponse xmlns="urn:geometry">
8
9
10 <distanceresult>5</distanceresult>
11
12
13 </distanceresponse>
14
15
16 </soap:body>
17</soap:envelope>
缺省的 XML 映射使用方法名作为请求元素名,参数名作为子元素名。每个参数的结构依赖于类型的结构。公共字段和属性的名字简单映射为子元素,如 Point 类中的 x 和 y 。应答元素的名字缺省为请求元素的名字后面附加上“ Response ” , 应答元素也包含一个子元素,是请求元素名字后面附加“ Result ”。也有可能使用一些固定的映射属性来打破标准的 XML 映射。比如,你可以使用 [XmlType] 属性来定制类型的名字和名称空间,使用 [XmlElement] 和 [XmlAttribute] 属性来控制如何将参数或类成员分别映射为元素或属性,也可以使用 [SoapDocumentMethod] 属性控制怎样把方法本身映射为请求 / 响应消息中的元素名。比如,检查下面重新定义的 Distance 。
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
public class Point {
[XmlAttribute]
public double x;
[XmlAttribute]
public double y;
}
[WebService(Namespace="urn:geometry")]
public class Geometry {
[WebMethod]
[SoapDocumentMethod(RequestElementName="CalcDistance",
ResponseElementName="CalculatedDistance")]
[return: XmlElement("result")]
public double Distance(
[XmlElement("o")]Point orig, [XmlElement("d")]Point dest) {
return Math.Sqrt(Math.Pow(orig.x-dest.x, 2) +
Math.Pow(orig.y-dest.y, 2));
}
}
它所期望的 SOAP 请求消息如下:
1<soap:envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
2
3
4 <SPAN lang=EN-U</soap:envelope>