将 XML映射到对象
在 WebMehod 处理程序确定了要调用的方法之后,它需要将 XML 消息反序列化为可在方法调用过程中提供的 .NET 对象。如同消息调度一样,该处理程序通过以下方法来实现上述目标:通过反射来检查该类,以便确定如何处理传入的 XML 消息。XmlSerializer 类在 System.Xml.Serialization 命名空间中自动完成 XML 和对象之间的映射。
XmlSerializer 使将任何公共的 .NET 类型映射到 XML 架构类型成为可能,在建立了这样的映射之后,它可以在 .NET 对象和 XML 实例文档之间自动映射(请参阅图 4)。目前,XmlSerializer 被限制于 XML 架构所支持的模型中,因此无法处理当今所有复杂的现代对象模型,例如,复杂的非树型对象图、双重指针等。不过,XmlSerializer 能够处理开发人员倾向使用的大多数复杂类型。
对于上面说明的 Add 示例,XmlSerializer 会将 x 和 y 元素映射为 .NET 双精度值,这些值随后会在调用 Add 时提供。Add 方法向调用方返回一个双精度值,该值随后将需要重新序列化为 SOAP 响应中的一个 XML 元素。
图 4. 将 XML映射到对象
XmlSerializer 还可以自动处理复杂的类型(除了上面描述的限制)。例如,下面的 WebMethod 计算两个 Point 结构之间的距离:
- 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 子元素,如下所示:
- < soap:Envelope
- xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
- >
- < soap:Body>
- < Distance xmlns="urn:geometry">
- < orig>
- < x>0< /x>
- < y>0< /y>
- < /orig>
- < dest>
- < x>3< /x>
- < y>4< /y>
- < /dest>
- < /Distance>
- < /soap:Body>
- < /soap:Envelope>
在本例中,SOAP 响应消息将包含一个 DistanceResponse 元素,该元素包含一个双精度类型的 DistanceResult 元素:
- < soap:Envelope
- xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
- >
- < soap:Body>
- < DistanceResponse
- xmlns="urn:geometry">
- < DistanceResult>5< /DistanceResult>
- < /DistanceResponse>
- < /soap:Body>
- < /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));
- }
- }
这个版本的 Distance 希望传入具有如下外观的 SOAP 消息:
- < soap:Envelope
- xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
- >
- < soap:Body>
- < CalcDistance xmlns="urn:geometry">
- < o x="0" y="0" />
- < d x="3" y="4" />
- < /CalcDistance>
- < /soap:Body>
- < /soap:Envelope>
而且,它将生成一个如下所示的 SOAP 响应消息:
- < soap:Envelope
- xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
- >
- < soap:Body>
- < CalculatedDistance xmlns="urn:geometry">
- < result>5< /result>
- < /CalculatedDistance>
- < /soap:Body>
- < /soap:Envelope>
.asmx 处理程序使用 SOAP document/literal 样式来实现和描述上面显示的默认映射。这意味着该 WSDL 定义将包含用来描述 SOAP 消息中所使用的请求和响应元素的字面上的 XML 架构定义(例如,不使用 SOAP 编码规则)。
.asmx 处理程序还可以使用 SOAP rpc/encoded 样式。这意味着 SOAP 正文中包含一个 RPC 调用的 XML 表示形式,而且参数都使用 SOAP 编码规则(例如,不需要 XML 架构)进行了序列化。为了实现这个目标,可以使用 [SoapRpcService] 和 [SoapRpcMethod] 属性,而不使用 [SoapDocumentService] 和 [SoapDocumentMethod] 属性。有关这些样式之间的区别的更多信息,请查看 Understanding SOAP。
正如您所看到的一样,可以完全自定义给定方法映射到 SOAP 消息的方式。XmlSerializer 提供一个功能强大的序列化引擎,以及许多我们在本文中没有时间进行讨论的功能。有关 XmlSerializer 如何工作的更多信息,请查看 Moving to .NET and Web Services。在我的每月 MSDN Magazine 的 XML Files 专栏(可在联机存档中查看专栏列表)中,我还介绍了 XmlSerializer 的许多不易察觉的细微差别。
除了对参数的反序列化进行处理以外,.asmx 处理程序还能够对 SOAP 头进行反序列化/序列化。SOAP 头的处理方法与参数不同,因为它们通常被视为带外信息,并未直接关联到某个特定的方法。因此,SOAP 头的处理通常是通过侦听层完成的,从而使得 WebMethod 完全无须对 SOAP 头进行处理。
但是,如果您希望亲自处理 WebMethod 中的头信息,则必须提供一个从 SoapHeader 派生的 .NET 类,此类代表该头的 XML 架构类型(遵循上面描述的同一映射准则)。然后定义该类型的成员变量,以便让其充当头实例的占位符。***,批注每个需要访问该头的 WebMethod,以便指定您想要到达的字段的名称。
例如,考虑下面的 SOAP 请求,其中包含有一个用于进行身份验证的 UsernameToken 头:
- < soap:Envelope
- xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
- >
- < soap:Header>
- < x:UsernameToken xmlns:x="http://example.org/security">
- < username>Mary< /username>
- < password>yraM< /password>
- < /x:UsernameToken>
- < /soap:Header>
- < soap:Body>
- < CalcDistance xmlns="urn:geometry">
- ...
为了使 .asmx 处理程序能够反序列化该头,首先需要定义一个表示隐含的 XML 架构类型的 .NET 类(注:如果您实际上已经知道了该头的 XML 架构,则可以使用 xsd.exe /c 来生成该类)。在本例中,相应类的外观如下所示:
- [XmlType(Namespace="http://example.org/security")]
- [XmlRoot(Namespace="http://example.org/security")]
- public class UsernameToken : SoapHeader {
- public string username;
- public string password;
- }
接着,只需在 WebMethod 类中定义一个用来保存头类的实例的成员变量,并用 [SoapHeader] 属性批注 WebMethod,如下所示:
- using System;
- using System.Web.Services;
- using System.Web.Services.Protocols;
- [WebService(Namespace="urn:geometry")]
- public class Geometry {
- public UsernameToken Token;
- [WebMethod]
- [SoapHeader("Token")]
- public double Distance(Point orig, Point dest) {
- if (!Token.username.Equals(Reverse(Token.password)))
- throw new Exception("access denied");
- return Math.Sqrt(Math.Pow(orig.x-dest.x, 2) +
- Math.Pow(orig.y-dest.y, 2));
- }
- }
然后,您可以在 WebMethod 中访问 Token 字段并提取在该头中提供的信息。您也可以使用同样的方法将头重新发送到客户端 — 您只需在 [SoapHeader] 属性中指定头的方向。有关在 WebMethod 框架中处理 SOAP 头的更多信息,请查看 Digging into SOAP Headers with the .NET Framework。
.asmx 处理程序也提供了 .NET 异常的自动序列化。由 .asmx 处理程序捕获的任何未经处理的异常都自动序列化为响应中的 SOAP Fault 元素。例如,在上例中,如果用户名与反转密码不匹配,代码将引发一个 .NET 异常。.asmx 处理程序随后将捕获该异常,并将它序列化为 SOAP 响应,如下所示:
- < soap:Envelope
- xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
- >
- < soap:Body>
- < soap:Fault>
- < faultcode>soap:Server< /faultcode>
- < faultstring>Server was unable to process request. --> access denied< /faultstring>
- < detail />
- < /soap:Fault>
- < /soap:Body>
- < /soap:Envelope>
如果您希望对 SOAP Fault 元素进行更多的控制,则还可以显式引发 SoapException 对象,以便指定所有的 SOAP Fault 元素细节,例如,faultcode、faulstring、faultactor 和 detail 元素。有关更多信息,请查看 Using SOAP Faults。
正如您所看到的一样,要知晓 WebMethod 如何工作必须了解基础序列化引擎及其各种选项。序列化引擎的好处在于,它隐藏了所有的基础 XML API 代码,而在自定义处理程序中,您通常必须编写这些代码。尽管多数开发人员发现这很好,但是,有一些开发人员却认为它是一个缺陷,因为他们仍希望亲自处理 WebMethod 实现中的原始 SOAP 消息。
【编辑推荐】