下载 Web Services Enhancements 1.0 for Microsoft .NET(英文)。
简介Web Services Enhancements 1.0 for Microsoft .NET (WSE) 是一个类库,用于实现高级 Web 服务协议。WSE 的体系结构模型基于处理入站和出站 SOAP 消息的过滤器管道。过滤器可以与 ASP.NET Web 服务基础结构集成在一起,也可以单独使用。本文深入探讨了 WSE 管道技术的工作原理,介绍了有关单独过滤器和过滤器管道的工作原理、配置默认管道的方法、创建自定义过滤器的方法以及 DIME 适合图片的特点。
以过滤器为中心的模型WSE 是一种将高级 Web 服务协议应用到 SOAP 消息的引擎。它要求向出站 SOAP 消息写入标头,从入站 SOAP 消息读取标头;它还要求转换 SOAP 消息正文。例如,按照 WS-Security 规范中定义的那样加密出站消息正文和解密入站消息正文。在 WSE 中,这种功能通过过滤器来实现。输出过滤器向消息写入标头,输入过滤器从消息读取标头并检查标头的有效性。此外输出和输入过滤器都可以转换消息的内容。图 1 说明了 WSE 过滤器模型。
图 1:Web Services Enhancements 的过滤器模型
使用单独的过滤器了解 WSE 如何使用过滤器的最好方法是从一个简单的例子开始。WSE 提供了一对过滤器,用于读写时间戳标头。时间戳标头包含了一些元素,用来表示消息的创建时间和过期时间,指明消息的周期以及何时可以认为该消息失效。时间戳过滤器在 Microsoft.Web.Services.Timestamp 命名空间中定义。顾名思义,TimestampOutputFilter 是一个输出过滤器,用于向 SOAP 消息写入时间戳标头;而 TimestampInputFilter 是一个输入过滤器,用于从 SOAP 消息读取时间戳标头。以下是它们各自的定义:
public class TimestampOutputFilter : SoapOutputFilter public class TimestampInputFilter : SoapInputFilter |
两个类都有一个 ProcessMessage 方法,该方法带有一个 SoapEnvelope 类型的参数。Microsoft.Web.Services.SoapEnvelope 类是标准 .NET XML DOM API System.Xml.XmlDocument 的扩展。它具有验证逻辑,可以验证包含有效 SOAP 消息的文档内容;它还具有一些快捷方法和属性,可以创建和访问消息的特定部分,即 Envelope、Header 和 Body 元素。
以下是一个使用时间戳过滤器的简单应用程序。
static void Main(string[] args) // 打印原始消息 // 创建时间戳输出过滤器 // 打印输出过滤后的消息 // 创建时间戳输入过滤器 // 处理消息,读取时间戳标头 // 打印输入过滤的消息 |
本程序首先创建一个 SoapEnvelope 对象,并添加空白的消息正文。然后,创建一个 TimestampOutputFilter 并使用它来处理 SoapEnvelope,向消息写入时间戳标头。最后,该程序创建一个 TimestampInputFilter 并使用它来处理 SoapEnvelope,从消息读取时间戳标头。程序执行的每一步都将消息内容打印到控制台。以下是输出结果(已经过格式编排)。
原始消息:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body/> </soap:Envelope> |
输出过滤后的消息:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <wsu:Timestamp xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <wsu:Created>2002-11-14T19:03:27Z</wsu:Created> <wsu:Expires>2002-11-14T19:08:27Z</wsu:Expires> </wsu:Timestamp> </soap:Header> <soap:Body /> </soap:Envelope> |
输入过滤后的消息:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> </soap:Header> <soap:Body /> </soap:Envelope> |
原始的消息只包含一个空白的 Body 元素,没有其他内容。当 TimestampOutputFilter 处理消息时,它用表示消息创建时间和过期时间的元素写入时间戳标头。而当 TimestampInputFilter 处理消息时,则读取时间戳标头。如果到了过期时间,TimestampInputFilter 将抛出一个异常。
使用 SoapContext 与过滤器通信
输出过滤器通过 Microsoft.Web.Services.SoapContext 类来管理。每个 SoapContext 对象使用简单的对象模型来记录特定的协议选项,例如是否存在用户名标记或数字证书、创建和过期时间戳、路由路径等。SoapEnvelope 类具有 Context 属性,该属性是 SoapContext 类的一个实例。当 SoapEnvelope 由输出过滤器处理时,是由 SoapContext 对象中的数据来告诉输出过滤器进行什么操作。
例如,TimestampOutputFilter 实际通过检查 SoapEnvelope 的 SoapContext 对象的属性(尤其是 SoapEnvelope.Context.Timestamps.Ttl)来设置消息的过期时间。如果您希望将消息的过期时间设置为 10 分钟,只需要按照如下所示设置该值:
// 创建封装 SoapEnvelope env = new SoapEnvelope(); ... // 以毫秒为单位,将过期时间设置为 10 分钟 env.Context.Timestamps.Ttl = 600000; |
WSE 输入过滤器也依赖于 SoapContext 类,但是其用途不同。当输入过滤器处理 SoapEnvelope 对象时,会更新它的 SoapContext 对象,以反映封装包含的协议标头。例如,TimestampInputFilter 处理消息之后,SoapEnvelope.Context.Timestamps.Created 属性会反映消息的创建时间。
在管道中使用多个过滤器
前面展示的时间戳输入和输出过滤器仅仅是 WSE 所带的 10 种内置过滤器中的两种。表 1 列出了所有内置过滤器,包括它们所在的命名空间、输入和输出过滤器类型名以及简单的功能描述。
表 1:Web Services Enhancements 中的 WSE 内置过滤器
命名空间 | 输入过滤器 | 输出过滤器 | 用途 |
---|---|---|---|
Microsoft.Web.Services.Diagnostics | TraceInputFilter | TraceOutputFilter | 向日志文件写入信息,以帮助调试 |
Microsoft.Web.Services.Security | SecurityInputFilter | SecurityOutputFilter | 身份验证、签名和加密支持 (WS-Security(英文)) |
Microsoft.Web.Services.Timestamp | TimestampInputFilter | TimestampOutputFilter | 时间戳支持 (WS-Security(英文)) |
Microsoft.Web.Services.Referral | ReferralInputFilter | ReferralOutputFilter | 动态更新路由路径 (WS-Referral(英文)) |
Microsoft.Web.Services.Routing | RoutingInputFilter | RoutingOutputFilter | 消息路由 (WS-Routing(英文)) |
表 1 中最右边的列清楚地说明了每对 WSE 过滤器的功能。一般来说,您可能希望将多个输入或输出过滤器结合起来,应用到给定的 SOAP 消息 en masse。为此,WSE 提供了 Microsoft.Web.Services.Pipeline 类,它的定义如下。
public class Pipeline public SoapInputFilterCollection inputFilters { get; } public void ProcessInputMessage(SoapEnvelope envelope); |
每个 Pipeline 对象封装一个输入过滤器集合和一个输出过滤器集合,它们分别由 Microsoft.Web.Services.SoapInputFilterCollection 和 Microsoft.Web.Services.SoapOutputFilterCollection 类的实例来表示。这些集合通过构造函数进行初始化,并作为属性提供。Pipeline.ProcessInputMessage 和 Pipeline.ProcessOutputMessage 方法在相应的过滤器集合中进行简单的迭代,依次传递提供的 SoapEnvelope 对象。
下面是一个使用 Pipeline 类的简单程序,它使用多个输出过滤器来处理消息。特别是使用了 WSE 中内置的 TraceOutputFilter 和 TimestampOutputFilter 类。
static void Main(string[] args) // 创建输出过滤器集合 // 添加所需的输出过滤器 // 创建空白的 SOAP 消息 // 打印原始消息 // 创建管道,封装过滤器集合 // 使用管道的集合中的所有输出过滤器 // 打印输出过滤后的消息 |
此应用程序首先创建一个空的 SoapInputFilterCollection 对象。然后创建一个 SoapOutputFilterCollection 对象,并向其添加一个 TraceOutputFilter 对象和一个 TimestampOutputFilter 对象。接着使用这两个集合来初始化一个新的 Pipeline 对象。然后使用 Pipeline 来处理空的 SoapEnvelope。程序的输出结果如下。
原始消息:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body/> </soap:Envelope> |
输出过滤后的消息:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <wsu:Timestamp xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <wsu:Created>2002-11-14T23:17:48Z</wsu:Created> <wsu:Expires>2002-11-14T23:22:48Z</wsu:Expires> </wsu:Timestamp> </soap:Header> <soap:Body /> </soap:Envelope> |
在上例中,TimestampOutputFilter 向消息添加一个时间戳标头,TraceOutputFilter 将消息写入跟踪文件。问题是向跟踪文件写入的消息是否包括时间戳标头?答案是肯定的。乍看起来这似乎违反常理。SoapOutputFilterCollection.Add 方法将过滤器附加到集合的末尾,因此 TraceOutputFilter 比 TimestampOutputFilter 早出现,这意味着在添加时间戳之前,消息就已经被写入跟踪文件中。然而,结果却是输出过滤器逆序执行,也就是说,集合中最后的输出过滤器最先处理消息。我给大家解释一下原因。
考虑两个会变更输出消息内容的过滤器 TimetampOutputFilter 和 SecurityOutputFilter。如果 TimetampOutputFilter 要向出站消息添加时间戳标头,就需要在 SecurityOutputFilter 加密消息之前进行。因此,输出过滤器处理的顺序非常重要。而且,当输入消息到达时,SecurityInputFilter 必须先解密消息,然后 TimestampInputFilter 才能检查消息是否过期。因此,输入过滤器处理的顺序也很重要,一般需要反转相应输出过滤器的顺序。
Pipeline 类考虑了这种情形,下面是它的工作原理。过滤器集合中的第一个过滤器最“接近线路”,最后一个过滤器最“接近代码”。对于输出过滤器和输入过滤器来说都是这样。输入过滤器始终按它们出现在集合中的顺序进行处理,而输出过滤器始终按相反的顺序进行处理。所有这些都封装在 ProcessInputMessage 和 ProcessOutputMessage 方法中。图 2 说明了这种体系结构。
图 2:Pipeline 类中的过滤器处理顺序
Pipeline 类有充分的理由采用这种工作方式。如果输入和输出过滤器集合以相同的顺序移动的话,您就必须在代码中手动反转一组过滤器的顺序。通过在 ProcessOutputMessage 中使用逆序处理,Pipeline 类就将您从其中解放出来。您可以“按照相同的顺序”向各自的集合中添加“对应的”输入和输出过滤器,而把其余的工作留给 Pipeline 去做。
默认管道
在大多数应用程序中,您很可能希望 Pipeline 对象使用相同的输入和输出过滤器。为此,WSE 支持默认 Pipeline 配置。Microsoft.Web.Services.Configuration.WebServicesConfiguration 类提供了一个静态 FilterConfiguration 属性,它维护了一对过滤器集合,分别通过 InputFilters 和 OutputFilters 属性来提供。当使用默认构造函数来实例化 Pipeline 类时,对默认过滤器的引用被复制到新对象的过滤器集合中。您可以针对每个 AppDomain 来修改默认的 Pipeline 配置。
使用内置 WSE 过滤器来预先配置默认的输入和输出过滤器集合。表 2 显示了这些配置。
表 2:预先配置的默认 Pipeline 配置
索引 | 输入过滤器 | 输出过滤器 |
---|---|---|
0 | SecurityInputFilter | SecurityOutputFilter |
1 | TimestampInputFilter | TimestampOutputFilter |
2 | ReferralInputFilter | ReferralOutputFilter |
3 | RoutingInputFilter | RoutingOutputFilter |
请注意,索引较小的过滤器更“接近线路”,而索引较大的过滤器更“接近代码”。如果在 WSE 中启用跟踪功能,则 TraceInputFilter 和 TraceOutputFilter 将被插入到默认过滤器集合的最前面,即 0 位置。这样做很有意义,因为跟踪过滤器的目标就是记录消息出入线路的情况。
如果您愿意,可以修改 AppDomain 的默认过滤器集合。例如,如果您希望应用程序使用 WS-Security 而不是 WS-Routing 和 WS-Referral,则可以删除路由和参照过滤器。一般来说,删除不需要的过滤器可以减少必须检查每个 SOAP 消息的对象数,进而提高应用程序的性能。
以下方法说明如何从默认的输入和输出过滤器中删除路由和参照标头。
public static void ReconfigureDefaultPipeline() // 删除路由和参照输入过滤器 // 检索默认的输出过滤器集合 // 删除路由和参照输出过滤器 |
值得注意的是,修改默认过滤器集合对现有的 Pipeline 对象没有影响。只有在更改默认集合后创建的新 Pipeline 对象才会受到此修改的影响。例如,在以下代码中,两个 Pipeline 对象使用不同的过滤器。
static void Main(string[] args) // 第二个管道具有安全性和 ... // 使用管道 |
如果您结合使用 WSE 和 ASP.NET Web 服务,可以使用 Global.asax 文件来修改服务在应用程序启动时的默认过滤器集合。下面是一个示例:
<%@ import namespace="Microsoft.Web.Services" %> <script runat="server" language="C#" > public void Application_OnStart() SoapOutputFilterCollection defaultOutputFilters = </script> |
WSE 过滤器体系结构可以扩展。您或许已经注意到了,TimestampOutputFilter 类扩展了 Microsoft.Web.Services.SoapOutputFilter,而 TimestampInputFilter 类扩展了 Microsoft.Web.Services.SoapInputFilter。SoapOutputFilter 和 SoapInputFilter 都是抽象基类。如果您希望自己编写过滤器,只需要从相应的基类中派生出一个子类并重写抽象 ProcessMessage 方法。
自定义过滤器有许多用途。以下是一个自定义过滤器,它检查 Web 服务生成的输出消息是否包含 SOAP Fault 元素。如果包含,则将错误内容写到 Windows 事件日志中。此输出过滤器不会修改消息。
public class SoapFaultOutputFilter : SoapOutputFilter // 如果有错误... // 将错误的内容格式化为字符串 // 将原始的错误正文写入字节数组 sw.Close(); private void WriteToEventLog(string msg, string src, byte[] buf) |
SoapFaultOutputFilter.ProcessMessage 方法使用 XPath 来探测作为参数传递的 SoapEnvelope。它查找 SOAP Fault 元素。如果找到,则提取错误信息,将其格式化为字符串,然后传递给 WriteToEventLog 方法。WriteToEventLog 使用 System.Diagnostics.EventLog 类将错误信息写入名为“WSEFaultLog”的自定义事件日志中,并在必要时创建该文件。值得注意的是,此过滤器仅查看服务本身生成的 SOAP Faults。如果由于消息签名不正确,SecurityInputFilter 拒绝客户端发送的消息,则此过滤器不会查看该消息。
SoapFaultOutputFilter 用于需要记录向客户端发送 SOAP Fault 情况的任意 Web 服务。它可以很容易地实现类似的输入过滤器,以记录客户收到来自服务的 SOAP Fault 的情况,但是由于不需要这样的实现,因此就没有什么意义。WSE 不要求所有的输出过滤器都有匹配的输入过滤器,也不要求所有的输入过滤器都有匹配的输出过滤器。许多任务(例如记录错误或按照架构验证消息正文的内容)都可以采用非对称的方式完成,即仅针对输入或输出消息,而不是同时针对两者。
使用 SoapContext 与自定义过滤器通信
内置 WSE 过滤器与应用程序其他部分的通信是通过附加到 SoapEnvelope 的 SoapContext 对象属性来实现的。对于自定义过滤器,可以使用相同的技术。SoapContext 类包含一个 System.Collections.Hashtable 对象,用于存储任意数据,因此以下方法间接公开了 Hashtable。
public class SoapContext { public void Add(string key, object value); public void Clear(); public bool Contains(string key); public IDictionaryEnumerator GetEnumerator(); public void Remove(string key); ... // 省略其他属性和方法 } |
可以通过修改 SoapFaultOutputFilter 来使用 SoapContext,从而允许应用程序指定记录错误时所使用的源字符串。在上面的 SoapFaultOutputFilter.ProcessMessage 实现中,源字符串被“硬编码”为“WSE”:
// 将错误数据写入事件日志
WriteToEventLog(msg, "WSE", buf);
下面是修改过的代码,如果 SoapContext 存在,则从中提取源字符串,字符串的值由 FaultSource 指定:
// 将错误数据写入事件日志 string src = "WSE"; if (envelope.Context.Contains("FaultSource")) src = envelope.Context["FaultSource"]; WriteToEventLog(msg, src, buf); |
对于其他更复杂的自定义过滤器,您可以使用此技术来研究 SoapContext 的所有附加对象模型。
配置自定义过滤器
实现了自定义过滤器之后,必须配置 Pipeline 才能使用该过滤器。可以通过向应用程序的 .config 文件中添加一行来进行配置,如下所示。
<configuration> <microsoft.web.services> <filters> <!-- 请注意,您可以不对称地添加自定义过滤器, 即仅添加输出过滤器或仅添加输入过滤器 --> <output> <add type="SoapFaultOutputFilter,WSEFilters" /> </output> </filters> </microsoft.web.services> </configuration> |
采用这种方法配置的自定义过滤器被自动添加到默认过滤器集合的末尾,因而它始终比内置的 WSE 过滤器更“接近代码”。您也可以通过修改 AppDomain 默认过滤器集合,手动配置自定义过滤器,如下所示:
WebServicesConfiguration.FilterConfiguration.OutputFilters.Add( new SoapFaultOutputFilter(); |
如果您不希望将自定义过滤器附加到默认过滤器集合的末尾,则需要使用后一种方法。
关于 DIME
WSE 唯一不是通过输入和输出过滤器实现的重要功能是支持 DIME 和 WS-Attachments。DIME 规范定义了消息的二进制格式。WS-Attachments 规范说明了如何使用 DIME 将二进制数据附加到 SOAP 消息中。Pipeline 类使用的过滤器链将各个 SoapEnvelope 对象“就地”转换。SOAP 消息与 DIME 记录之间的映射要求进行从 SOAP 消息到二进制流或相反方向的不同格式转换。这反映了一个事实:DIME 与其他 Web 服务协议不同,DIME 实际上不是基于 SOAP 的。简而言之,当您使用 DIME 时,已超出了 SOAP 的范围,WSE 过滤器模型不支持这种转换,因此需要独立的机制。
Microsoft.Web.Services.Dime 命名空间中的类实现了 WSE DIME 功能。DimeWriter 和 DimeReader 类为 DIME 消息提供了基于流的 IO。两个类均使用 DimeRecord 对象来表示 DIME 消息中的各个记录。调用 Pipeline.ProcessOutputMessage 之后,可以使用 DimeWriter 将出站 SOAP 消息转换为 DIME 消息。与此类似,调用 Pipeline.ProcessInputMessage 之前,可以使用 DimeReader 将入站 DIME 消息转换为 SOAP 消息。
大多数 Web 服务开发人员认为 DIME(与 WS-Attachments 一起)的功能是将二进制数据与 SOAP 消息一起发送,实际上 DIME 还有另一个更有趣的用途。如果您打算使用基于流的协议来发送一个 SOAP 消息,则需要某种方法来指定消息的大小,以便接收者了解消息何时结束以及下一个消息何时开始。当通过 HTTP 发送 SOAP 消息时,可以使用内容长度标头来指定消息的大小。如果使用其他方法发送 SOAP 消息,则可以使用 DIME。以下是一个用一条记录将 SOAP 消息转换为 DIME 消息的函数。
public void EnvelopeToDIMEStream(SoapEnvelope env, Stream stm) // 生成新的记录 id // 创建新记录,指定内容将 // 将 SOAP 消息写入 DIME 记录 // 清除 |
而下面是与之对应的将 DIME 消息转换为 SOAP 消息的函数。
public DIMEStreamToEnvelope(Stream stm, SoapEnvelope env) // 读取包含 SOAP 消息的记录 // 从 DIME 记录读取 SOAP 消息 // 确认没有其他记录 |
如果您使用 DIME 向 SOAP 消息附加任意数据,进行读写操作的代码将变得更加复杂。WSE 的关键之处是它为作为 SoapEnvelope 对象中 SoapContext 的一部分的附件数据提供了保存场所。尤其是,DimeAttachmentCollection 类型的 SoapContext.Attachments 属性可以为您保存 DimeAttachment 对象。每个 DimeAttachment 与一个 DIME 记录相对应,包括 id、类型标识符、区块大小以及二进制数据流。
小结
Web Services Enhancements (WSE) 功能(主要)是通过使用处理入站和出站消息的过滤器来实现的。您可以单独使用过滤器或者在管道中使用过滤器,也可以控制进程中管道的默认配置。还可以创建自定义过滤器,添加您需要的功能。总之,WSE 提供了无比灵活且可扩展的体系结构。不管您是结合使用 WSE 与 ASP.NET Web 服务还是单独使用 WSE,深入理解过滤器和管道的工作原理将有助于充分利用 WSE 提供的强大功能。