摘要:了解 Microsoft ASP.NET Web 服务方法 (WebMethod) 如何为生成 Web 服务提供一种高效方法。WebMethod 可以将传统的 Microsoft .NET 方法公开为支持 HTTP、XML、XML 架构、SOAP 和 WSDL 的 Web 服务操作。WebMethod (.asmx) 处理程序自动将传入的 SOAP 消息调度到相应的方法,并自动将传入的 XML 元素序列化为相应的 .NET 对象。(20 页打印页)
Aaron Skonnard DevelopMentor
2003 年 5 月
适用于:
Microsoft? ASP.NET Web 服务方法 SOAP 消息处理 XML 架构 基于 HTTP 的 Web 服务
本页内容
简介
WebMethod 框架
消息调度
将 XML 映射到对象
自动生成 WSDL
小结
资源
简介
在 Microsoft? .NET 中,目前有两种截然不同的方法来实现基于 HTTP 的 Web 服务。第一种同时也是最低级的方法是编写一个自定义 IHttpHandler 类并将它插入到 .NET HTTP 管道中。这种方法要求您使用 System.Web API 来处理传入的 HTTP 消息,使用 System.Xml API 来处理在 HTTP 主体中找到的 SOAP 封装。编写自定义处理程序还要求您手动编写一个精确描述您的实现的 WSDL 文档。要正确地做好这一切,需要对 XML、XSD、SOAP 和 WSDL 规范有深入的了解,但这个先决条件使大多数人望而却步。
实现 Web 服务的一种更高效的方法是使用 Microsoft ASP.NET WebMethod 框架。ASP.NET 为 .asmx 终结点附带了一个特殊的 IHttpHandler 类(名为 WebServiceHandler),该类提供您所需的可反复套用的 XML、XSD、SOAP 和 WSDL 功能。由于 WebMethod 框架使您从复杂的基础 XML 技术中解脱出来,因此您可以将精力迅速集中到手头的业务问题上。
在实现技术之间进行选择就会形成如图 1 中所示的灵活性和工作效率之间常见的折衷。编写自定义的 IHttpHandler 会为您提供极大的灵活性,但却要花大量的时间来编写、测试和调试代码。WebMethod 框架使迅速生成和运行 Web 服务变得非常轻松,但是您无疑将受到该框架边界的限制。但是,如果 WebMethod 框架不能完全满足您的需要,也可以通过添加自己的额外功能来扩展该框架。
通常,除非您已经掌握了 XML、XSD、SOAP 和 WSDL,并且愿意承担直接处理它们所带来的负担,否则,最好还是使用 WebMethod 框架来满足您的 Web 服务需要。它提供了大多数 Web 服务终结点所需的基本服务,以及一些使框架更符合您确切需要的有趣的扩展性特点。基于上述假设,本文的其余部分将讨论 WebMethod 的内部工作机制。如果您对 XML 架构和 SOAP 不熟悉,则可能需要在继续阅读本文之前参阅 Understanding XML Schema 和 Understanding SOAP。
WebMethod 框架
WebMethod 框架围绕将 SOAP 消息映射到 .NET 类上的方法来设计。这通过首先用在 System.Web.Services \命名空间中找到的 [WebMethod] 属性批注您的方法来完成。例如,下面的 .NET 类包含四种方法,其中的两种方法用 [WebMethod] 属性进行批注:
using System.Web.Services; public class MathService { [WebMethod] public double Add(double x, double y) { return x + y; } [WebMethod] public double Subtract(double x, double y) { return x - y; } public double Multiply(double x, double y) { return x * y; } public double Divide(double x, double y) { return x / y; } }
要在 WebMethod 框架中使用该类,需要将该类编译成程序集,然后将它复制到虚拟目录的 bin 目录中。在本例中,Add 和 Subtract 方法随后可被公开为 Web 服务操作,而 Multiply 和 Divide 却不能(因为它们没有被标记为 [WebMethod])。
通过 .asmx 终结点将 Add 和 Subtract 公开为 Web 服务操作。为此,要新建一个名为 Math.asmx 且包含以下简单声明的文本文件,然后将它放到包含该程序集的同一虚拟目录中(注:这是放在虚拟目录本身中,而不是它的 bin 子目录):
<%@ WebService class="MathService"%>
上面的声明通知 .asmx 处理程序要在哪个类中查找 WebMethod,该处理程序就会神奇地处理其余所有事情。例如,假设虚拟目录的名称为 'math',它包含 Math.asmx 以及一个包含该程序集的 bin 子目录,浏览到 http://localhost/math/math.asmx 会导致 .asmx 处理程序生成如图 2 所示的文档页(后面会对此进行详述)。
关于 .asmx 处理程序如何工作有一个很大的变化。.asmx 文件通常只包含 WebService 声明,该声明根据名称引用 Web 服务类(这与上面显示的声明相似)。因此,在本例中,程序集必须已经被编译和部署到虚拟目录的 bin 目录中。对于在 .asmx 文件中找到的源代码,.asmx 处理程序还提供实时编译。例如,下面的文件(名为 Mathjit.asmx)包含 WebService 声明以及被引用类的源代码。
<@% WebService class="MathServiceJit" language="C#"%> using System.Web.Services; public class MathServiceJit { [WebMethod] public double Add(double x, double y) { return x + y; } [WebMethod] public double Subtract(double x, double y) { return x - y; } public double Multiply(double x, double y) { return x * y; } public double Divide(double x, double y) { return x / y; } }
当通过 HTTP 首次访问此文件时,.asmx 处理程序会编译源代码并将程序集部署到正确的位置。请注意,WebService 声明还必须提供语言,以便 .asmx 处理程序在运行时能够选择正确的编译器。这种方法的明显缺点是,只有在首次访问该文件之后,才会发现编译错误。
当您在 Visual Studio? .NET 中新建一个 Web 服务项目时,总是使用“双文件”技术,即类的源文件与引用它的 .asmx 文件是分开的。集成开发环境 (IDE) 尽量地对文件进行了隐藏,但是如果您在 Solution Explorer 工具栏上单击 Show All Files,将会注意到该项目中的每个 Web 服务类都有两个文件。实际上,Visual Studio .NET 并不支持 .asmx 文件的语法突出显示或 IntelliSense?,所以,如果您朝着这个方向设计,则必须依靠自己。对于 Web 项目,Visual Studio .NET 也负责自动创建一个虚拟目录并将程序集编译到该虚拟目录的 bin 目录中。
在详细讨论 .asmx 处理程序如何工作之前,让我们先简单讨论一下消息如何从 Internet Information Server (IIS) 传递到 .asmx 处理程序。当传入的 HTTP 消息到达端口 80 时,IIS 使用在 IIS 元数据库中找到的信息来确定应当使用哪个 ISAPI DLL 来处理消息。.NET 安装程序将 .asmx 扩展名映射到 Aspnet_isapi.dll,如图 3 所示。
Aspnet_isapi.dll 是 .NET 框架提供的标准的 ISAPI 扩展名,它只是将 HTTP 请求转发到一个名为 Aspnet_wp.exe 的单独的辅助进程。Aspnet_wp.exe 宿主公共语言运行库和 .NET HTTP 管道。当消息进入 .NET HTTP 管道之后,该管道会在配置文件中进行查找,看对于给定的扩展名应当使用哪个 IHttpHandler 类。如果您在 Machine.config 文件中进行查找,则会发现它包含 .asmx 文件的 httpHandler 映射,如下所示:
<configuration> <system.web> <httpHandlers> <add verb="*" path="*.asmx" type="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" validate="false"/> ...
因此,当消息进入面向 .asmx 文件的 .NET HTTP 管道时,该管道会调用 WebServiceHandlerFactory 类,以便实例化新的 WebServiceHandler 对象,该对象可用于处理请求(通过调用 IHttpHandlerProcessRequest 方法)。WebServiceHandler 对象随后打开物理 .asmx 文件,以便确定包含 WebMethod 的类的名称。有关 .NET HTTP 管道如何工作的详细信息,请查看 HTTP Pipelines:Securely Implement Request Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET。
当 .asmx 处理程序由 .NET HTTP 管道调用之后,它会开始神奇地处理 XML、XSD、SOAP 和 WSDL。由 .asmx 处理程序提供的其余功能可分成三大类:1) 消息调度;2) 将 XML 映射到对象;3) 自动生成 WSDL 和文档。让我们更详细地了解这些方面。
消息调度
当 .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" <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" > <soap:Body> <Add xmlns="http://tempuri.org/"> <x>33</x> <y>66</y> </Add> </soap:Body> </soap:Envelope>
在传入的 HTTP 消息中,确实有两段信息可用来确定要在该类中调用哪种方法:SOAPAction 头或请求元素的名称(例如,soap:Body 元素中元素的名称)。在本例中,任何一个都指出了发送方想调用的方法的名称。
在默认情况下,.asmx 处理程序使用 SOAPAction 头的值来执行消息调度。因此,.asmx 处理程序查看消息中的 SOAPAction 头,使用 .NET 反射检查被引用类中的方法。它只考虑标记了 [WebMethod] 属性的方法,但是它可以通过查看每种方法的 SOAPAction 值来正确地确定要调用哪种方法。由于我们没有对类中的方法显式指定 SOAPAction 值,因此,.asmx 处理程序假设 SOAPAction 值将由 Web 服务的命名空间及其后面的方法名称组成。而且我们也没有指定命名空间,因此该处理程序会将 http://tempuri.org 作为默认的命名空间。这样,Add 方法的默认 SOAPAction 值将是 http://tempuri.org/Add。
您可以自定义 Web 服务的命名空间,方法是用 [SoapDocumentMethod] 属性批注自己的 WebMethod,从而使用 [WebService] 属性以及正确的 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 方法的 SOAPAction 值是 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" <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" > <soap:Body> <Subtract xmlns="http://example.org/math"> <x>33</x> <y>66</y> </Subtract> </soap:Body> </soap:Envelope>
如果 .asmx 处理程序未找到与传入的 HTTP 消息相匹配的 SOAPAction,则它只是引发一个异常(以后会详细介绍如何处理异常)。如果您不想依赖 SOAPAction 头来进行方法调度,则可以用 [SoapDocumentService] 属性的 RoutingStyle 属性来批注该类,以便指示 .asmx 处理程序使用请求元素的名称。如果这样做的话,可能还应该指出 WebMethod 不需要 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 值,而是使用请求元素的名称。例如,对于 Add 方法,该处理程序希望请求元素的名称是 Add(来自 http://example.org/math 命名空间),如下面的 HTTP 请求消息中所示:
POST /math/math.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: "" <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" > <soap:Body> <Add xmlns="http://example.org/math"> <x>33</x> <y>66</y> </Add> </soap:Body> </soap:Envelope>
因此,当 .asmx 处理程序收到传入的 HTTP 消息时,它做的第一件重要事情就是确定如何将该消息调度到相应的 WebMethod。但是,在能够实际调用该方法之前,它必须将传入的 XML 映射到 .NET 对象。
将 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 元素。
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 消息。有关如何实现这种混合方法的更多详细信息,请查看 Accessing Raw SOAP Messages in ASP.NET Web Services。
自动生成 WSDL
在您写好并部署了 WebMethod 之后,客户端需要明确知道为了与它成功通讯而必须使 SOAP 消息具有什么样的外观。提供 Web 服务说明的标准方法是通过 WSDL(以及嵌入的 XSD 定义)进行的。为了帮助适应这种情况,.asmx 处理程序自动生成可读的文档页,以及能准确反映 WebMethod 接口的 WSDL 定义。如果您对 WebMethod 应用了许多映射属性,则它们都会反映在生成的文档中。
如果您浏览 .asmx 文件,将会看到一个如图 2 所示的可供人工读取的文档页。此文档页是由一个名为 DefaultWsdlHelpGenerator.aspx(位于 C:\windows\Microsoft.NET\Framework\ v1.0.3705\config)的 .aspx 页生成的。如果您打开这个文件,将会发现这仅仅是一个标准的 ASP.NET 页,该页使用 .NET 反射生成文档。此功能允许您的文档总是与代码保持同步。您只需修改此文件即可自定义所生成的文档。
还可以通过在 Web.config 文件中指定一个不同的文档文件来避免在虚拟目录中生成文档:
<configuration> <system.web> <webServices> <wsdlHelpGenerator href="MyDocumentation.aspx"/> </webServices> ...
如果客户端对 .asmx 终结点发出 GET 请求,而且查询字符串中有 “?wsdl”,那么,.asmx 处理程序会生成 WSDL 定义,而不生成可供人工读取的文档。客户端可以使用 WSDL 定义来生成代理类,这些类可自动了解如何与 Web 服务通讯(例如,使用 .NET 中的 Wsdl.exe)。
要自定义 WSDL 生成过程,可以编写一个 SoapExtensionReflector 类,并在 Web.config 文件中向 WebMethod 框架注册该类。然后,当 .asmx 处理程序生成 WSDL 定义时,它将调用反射器类,并使您有机会自定义向客户端提供的最终定义。有关如何编写 SoapExtensionReflector 类的更多信息,请查看 SoapExtensionReflectors in ASP.NET Web Services。
您还可以使用两种不同的方法来完全跳过 WSDL 生成过程。第一种方法是,在虚拟目录中提供一个可供客户端访问的静态 WSDL 文档,然后通过将文档生成器从 Web.config 文件中删除来禁用它,如下所示:
<configuration> <system.web> <webServices> <protocols> <remove name="Documentation"/> </protocols> ...
另一种自动化程度较之稍高的方法是,使用 [WebServicesBinding] 属性来指定由 WebMethod 类实现的静态 WSDL 文档在虚拟目录中的位置。您还必须使用 [SoapDocumentMethod] 属性为每个 WebMethod 实现的 WSDL 绑定指定名称。这样做之后,WSDL 的自动生成过程将导入静态 WSDL 文件,并在它周围包装一个新的服务说明。有关此方法的更多信息,请查看标题为 Place XML Message Design Ahead of Schema Planning to Improve Web Service Interoperability 的文章。
目前,因为仍没有太多可用的 WSDL 编辑器,所以手工编写 WSDL 是极其困难的。因此,文档/WSDL 的自动生成是 WebMethod 框架中很有价值的一部分,没有它,许多开发人员的日子会很难过。
小结
ASP.NET WebMethod 框架为生成 Web 服务提供了一种高效的方法。WebMethod 框架使得传统的 .NET 方法公开为支持 HTTP、XML、XML 架构、SOAP 和 WSDL 的 Web 服务操作。WebMethod (.asmx) 处理程序自动确定如何将传入的 SOAP 消息调度到相应的方法,以及何时将传入的 XML 元素自动序列化为相应的 .NET 对象。而且,为了简化与客户端的集成,.asmx 处理程序还为生成人可读取的 (HTML) 和机器可读取的 (WSDL) 文档提供自动支持。
尽管与自定义的 IHttpHandler 相比,WebMethod 框架稍微有些限制,但是它还是提供了一个功能强大的扩展性模型,即所谓的 SOAP 扩展框架。SOAP 扩展允许您引入我们上面没有讨论到的额外功能来满足您的具体需要。例如,Microsoft 发布了 Web Services Enhancements 1.0 for Microsoft .NET (WSE),WSE 只提供一个 SoapExtension 类,该类为 WebMethod 框架引入了对几个 GXA 规范的支持。有关编写 SOAP 扩展名的更多信息,请查看 Fun with SOAP Extensions。