[转]Indigo 简介
摘要:概要介绍了 Microsoft 用于构建面向服务的应用程序的统一编程模型“Indigo”的体系结构。本文涵盖了 Indigo 与 .NET Framework 中现有的分布式应用程序技术之间的关系、有关创建和使用 Indigo 服务的基本知识以及对 Indigo 功能的概述,包括安全性、可靠消息传输和事务支持等。(本文包含一些指向英文站点的链接。)
注意: 本文是根据 Indigo 的第一个社区技术预览 (CTP) 的预发布版本撰写的。请注意,功能和实现细节会随着 Indigo 产品周期的不断推进而改变。
本页内容
什么是 Indigo?
Indigo 有哪些特性
创建 Indigo 服务
创建 Indigo 客户端
Indigo 的其他特性
共存和移植
结论
什么是 Indigo?
为构建软件选择最佳的抽象方式是一个仍在不断演变的过程。对象是目前构建应用程序业务逻辑的主流方法,但使用对象构建应用程序与应用程序之间的通信模型却没有达到同样的成功。一种较好的方法是将软件的各个独立块之间的交互显式地构建为“服务”。对于构建面向对象的应用程序,已经存在大量的支持,但将服务作为基本软件构造块来考虑,则还是一种比较新的观念。因此,专门用于创建面向服务的应用程序的技术还没有得到广泛应用。
Microsoft 用于构建面向服务应用程序的代号为 Indigo 的框架,正好改变了这一状况。Indigo 允许目前创建面向对象应用程序的开发人员采用 .NET Framework 以相似的方式来创建面向服务的应用程序。同时为了让这些应用程序能够与运行在 Windows 和其他平台上的软件有效地进行交互,Indigo 还实现了 SOAP 和其他 Web 服务技术,这样开发人员就可以创建可靠、安全且能够与运行在任何系统上的软件实现互操作的事务型服务。
上图显示了 Indigo 客户端和服务的简单视图。Indigo 提供了创建由客户端访问的服务的基础,这一基础主要由一组运行于公共语言运行库 (CLR) 上的类来实现。客户端与服务通过 Indigo 的内置协议 SOAP 进行交互,因此虽然图中显示两个部分均基于 Indigo 构建,但这显然并不是必需的。
Indigo 基于 .NET Framework 2.0 并对其进行了扩展,该版本计划于 2005 年发布。Indigo 本身会包含在计划于 2006 年发布的代号为 Longhorn 的 Windows 版本,同时也会在 Windows XP 和 Windows Server 2003 上提供。本文的介绍基于 Indigo 的第一个社区技术预览的预发布版本。请注意,在最终版本发布之前,可能会(实际上几乎肯定会)有一些变化。
Indigo 有哪些特性
在 Microsoft,有许多人已投入数年时间来创建 Indigo。如果其解决的问题很简单,或者其解决方案很明显的话,就不会需要这样的努力程度。因此,Indigo 是一种极为重要的技术。然而,作为 Indigo 最重要的方面,Indigo 有三项突出的特性:与多种现有 Microsoft 技术的统一性,对跨供应商互操作性的支持,以及显式的面向服务特性。本节对逐一探讨这三项特性。
与 Microsoft 分布式计算技术的统一性
.NET Framework 的最初版本中包含多种不同的技术,用于创建分布式应用程序。下图列出了每种技术与开发人员采用该技术的主要理由。例如,要构建基本的可互操作的 Web 服务,最佳选择是 ASP.NET Web 服务,即通常所说的 ASMX。要连接两个基于 .NET Framework 的应用程序,有时应当选择 .NET Remoting。如果应用程序需要分布式事务和其他更高级的服务,其创建者一般会使用企业服务,即 .NET Framework 推出的 COM+ 的继任者。要了解最新的 Web 服务规范,如 WS-Addressing 和 WS-Security,开发人员可以构建采用 Web 服务增强(缩写为 WSE,是 Microsoft 对这些新兴规范的初始实现)的应用程序。而要创建基于消息的排队式应用程序,基于 Windows 的开发人员则应使用 Microsoft 消息队列 (MSMQ)。
ASMX |
.NET Remoting |
企业服务 |
WSE |
MSMQ |
Indigo |
|
---|---|---|---|---|---|---|
可互操作 Web 服务 |
X |
X |
||||
.NET - .NET 通信 |
X |
X |
||||
分布式事务等。 |
X |
X |
||||
WS-* 规范支持 |
X |
X |
||||
队列消息传输 |
X |
X |
所有这些选择都有其存在价值,但其多样性也会让开发人员感到迷惑。为什么要有这么多选择?好的解决方案应当是一种技术就能解决所有问题。随着 Indigo 的到来,这种技术出现了。Indigo 不再强迫开发人员从多种可能当中做出选择,而是允许他们创建能够解决所有问题的分布式应用程序,各种问题由 Indigo 所包含的技术负责解决。尽管 Microsoft 将继续支持这些早期的技术,但大多数以前可能会采用这些技术来实现的新应用程序都将基于 Indigo 来构建。
与非 Microsoft 应用程序的互操作性
通过统一各种独立技术使 Windows 开发人员的生活更加轻松是一件好事。但通过 Web 服务供应商之间的通用协议,长期存在的应用程序互操作性问题也可以解决。由于 Indigo 的基本通信机制是 SOAP,因此 Indigo 应用程序可与大量运行于各种上下文环境的其他软件进行通信。如下图所示,基于 Indigo 构建的应用程序可与下列所有程序进行交互:
-
运行于同一 Windows 计算机上不同进程中的 Indigo 应用程序。
-
运行于另一 Windows 计算机上的 Indigo 应用程序。
-
基于其他技术构建的应用程序,如基于 Java 2 企业版 (J2EE) 构建的、支持标准 Web 服务的应用程序服务器。这些应用程序可以运行在 Windows 计算机上,也可以运行在采用其他操作系统(如 Sun Solaris、IBM 的 z/OS 或 Linux)的计算机上。
Indigo 应用程序还可以与基于 Indigo 以前的 .NET Framework 技术(如后面讲到的 ASMX)构建的应用程序进行互操作。
为了实现基本通信以外的功能,Indigo 采用了一些更新的 Web 服务技术,这些技术统称为 WS-* 规范。这些文档定义了用于添加可靠消息传输、安全性、事务以及更多基于 SOAP 的 Web 服务的多供应商方式。所有这些规范最初均是由 Microsoft、IBM 及其他供应商共同制定的。随着它们日渐稳定,所有权通常会转移到一些标准机构,如结构化信息标准促进组织 (OASIS)。Indigo 第一版中支持的 Web 服务规范包括 WS-Addressing、WS-Policy、WS-MetadataExchange、WS-ReliableMessaging、WS-Security、WS-Trust、WS-SecureConversation、WS-Coordination、WS-AtomicTransaction 和 SOAP 消息传输优化机制 (MTOM)。
当一个 Indigo 应用程序与运行在非 Windows 系统上的应用程序进行通信时,采用的协议为标准 SOAP(可能具有某些 WS-* 扩展),传输时以基于普通文本的 XML 编码形式表示。然而,当一个基于 Indigo 的应用程序与另一个基于 Indigo 的应用程序进行通信时,就非常有必要优化这种通信。与前者相比,所有的功能都相同,包括可靠消息传输、安全性和事务,但采用的传输编码则是 SOAP 的一种优化二进制版本。消息仍遵循 SOAP 消息的数据结构,称为“信息集”,但其编码采用了信息集的二进制表示,而不是标准尖括号加文本的 XML 格式。
对面向服务开发的显式支持
把应用程序看作提供和使用服务并不是什么新观念。新颖之处在于明确将服务作为与对象截然不同的东西来看待。为此,Indigo 创建者们在设计此项技术的过程中始终牢记四项基本原则:
-
共享架构,而不是类:与旧的分布式对象技术不同,服务仅通过精心定义的 XML 接口与其客户端进行交互。跨越服务边界传递完整的类、方法及全部之类的行为被禁止。
-
服务具有自主性:服务及其客户端同意它们之间的接口,但相互独立。它们可以采用不同的语言编写,可以使用不同的运行时环境(如 CLR 和 Java 虚拟机),可以运行在不同操作系统上,还可以存在其他方面的不同。
-
边界是显式的:分布式对象技术 [如分布式 COM (DCOM)] 的目标之一是使远程对象尽可能看上去像本地对象一样。虽然这种方法通过提供一种通用编程模型在某些方面简化了开发,但也同时隐藏了本地对象与远程对象之间不可避免的区别。服务通过使服务与其客户端之间的交互更明显而避免了这一问题。隐藏分布式特性并非目的。
-
采用基于策略的兼容性:如果可能,决定在系统之间使用哪些选项应取决于基于 WS-Policy 的机制。
面向服务是一个广泛的领域,它包含面向服务应用程序和更广泛的面向服务体系结构 (SOA) 的概念。Indigo 将成为基于 Windows 的面向服务应用程序的基础,因而对许多组织的 SOA 工作都将非常重要。
创建 Indigo 服务
如下图所示,每个 Indigo 服务均由三个部分构成:
-
一个“服务类”,采用 C# 或 VB.NET 或其他基于 CLR 的语言编写,实现一个或多个方法;
-
一个“宿主”环境,这是一种应用程序域和进程,服务将在该环境中运行。
-
一个或多个“终结点”,由客户端用于访问服务。
与 Indigo 服务的所有通信均需通过该服务的终结点实现。每个终结点将指定一个“合同”,确定通过该终结点可以访问哪些方法;一个“绑定”,确定客户端如何与该终结点通信;以及一个“地址”,指示可在什么地方找到该终结点。
理解 Indigo 需要掌握所有这些概念。本节将从服务类开始逐一描述每个概念。
创建服务类
Indigo 服务类就像其他任何类一样,但它还有一些新特性。这些新特性允许该类的创建者定义该类实现的一个或多个“合同”。每个 Indigo 服务类至少应实现一个“服务合同”,用于定义该服务提供的操作。服务类还可以显式实现一个“数据合同”,用于定义那些操作传递的数据。本节将从服务合同开始对两者进行探讨。
定义服务合同
每个 Indigo 服务类均需实现一些方法,以供其客户端使用。服务类的创建者通过将这些方法包含在某个服务合同中,来决定将哪些方法公开为客户可调用的操作。定义服务合同,实际上通常就是显式使用服务,对 .NET 领域来说基本上是一个新观念。Indigo 的创建者需要找到一条途径,从 CLR 以及在其基础上构建的编程语言的角度来把握这一观念。幸运的是,CLR 的创建者们早就预见到了这种扩展需求,因而提供了对“属性”的支持。从开发人员的角度来看,属性就是一些字符串,可能具有关联的属性,它们可能出现在类定义、方法定义的前面,也可能出现在其他位置。只要有属性出现,它就会改变其所关联的事物的某些行为。
.NET Framework 从初始版本开始就对各种事物使用了属性。例如,在 .NET Framework 的 ASMX 技术中要使一个方法成为 SOAP 可调用的 Web 服务,该方法将被前置一个 WebMethod 属性。与此相似,企业服务使用 Transaction 属性来指示一个方法需要事务。Indigo 将这种观念用于服务,定义了大量属性来定义和控制服务。
Indigo 中最基本的属性是 ServiceContract。实际上,Indigo 服务类本身就是标记有 ServiceContract 属性的类或者是实现了标记有该属性的接口的类。以下是采用第一种方法的一个简单 C# 示例:
using System.ServiceModel; [ServiceContract] class Calculator { [OperationContract] private int Add(int a, int b) { return a + b; } [OperationContract] public int Subtract(int a, int b) { return a - b; } public int Multiply(int a, int b) { return a * b; } }
ServiceContract 属性以及 Indigo 使用的所有其他属性均在 System.ServiceModel 命名空间中定义,因此本例开头使用 using 语句来引用该命名空间。服务类中可被客户端调用的每个方法都必须使用名为 OperationContract 的另一个属性加以标记。服务类中带有前置 OperationContract 属性的所有方法都将自动被 Indigo 公开为 SOAP 可调用操作。在本例中,Add 和 Subtract 均标记有该属性,因此两者均对该服务的客户端公开。服务类中未标记有 OperationContract 的任何方法(如上例中的 Multiply)将不包含在服务合同中,因而不能被该 Indigo 服务的客户端调用。
服务和对象,这两个本来互不相干的抽象,在 Indigo 中却走到了一起。必须理解的是,两者均显式或隐式地依赖于合同来定义它们将向外界公开什么。通过类定义的对象有效地定义了一种合同,此合同决定了它的哪些方法可以被同一应用程序中的其他对象调用。对这些方法的访问由语言关键字如 public 和 private 控制。例如,在上面所示的类 Calculator 中,同一应用程序中的其他对象可以调用该类的两个公共方法 Subtract 和 Multiply。该类公开的对象合同中只包含这两个方法。
通过 Indigo 的属性,Calculator 还定义了一个服务合同,如前所述。此合同也拥有两个方法,但它们与对象合同中的那些方法不同。一个方法能否被此 Indigo 服务的客户端调用,由 OperationContract 属性控制,而不是由 public 和 private 关键字控制。由于此属性只出现在 Add 和 Subtract 上,因此只有这两个方法才能被客户端调用。对象合同与服务合同彼此完全独立,正因如此,同一方法(如 Add)才可以既是 private 同时又具有 OperationContract 属性。
刚才的示例展示了创建 Indigo 服务类的最简单方法:直接使用 ServiceContract 标记类。这样做之后,该类的服务合同将隐含定义为包含该类中所有标记有 OperationContract 的方法。还可以(并且大多数情况下这样做会更好)使用语言中的 interface 类型显式地指定服务合同。使用这种方法,Calculator 类可能会如下所示:
using System.ServiceModel; [ServiceContract] public interface ICalculator { [OperationContract] private int Add(int a, int b); [OperationContract] public int Subtract(int a, int b); } class Calculator :ICalculator { private int Add(int a, int b) { return a + b; } public int Subtract(int a, int b) { return a - b; } public int Multiply(int a, int b) { return a * b; } }
在本例中,ServiceContract 和 OperationContract 属性被指定到 ICalculator 接口及其包含的方法,而不是 Calculator 类本身。但结果是相同的,因此这一版本的服务公开的服务合同与前一版本相同。像这样使用显式接口稍微有点复杂,但同时也具有更大的灵活性。例如,一个类可以实现多个接口,这意味着它也可以实现多个服务合同。通过公开多个终结点,每个终结点拥有一个不同的服务合同,一个类就可以向不同的客户端提供不同的服务组。
最后一点:使用 ServiceContract 和 OperationContract 标记服务类还允许以 Web 服务描述语言 (WSDL) 自动生成服务合同定义。因此,可将每个 Indigo 服务合同的外部可见定义作为指定该合同中的操作的标准 WSDL 文档来访问。虽然本文不作讲解,但直接从 WSDL 文档创建 Indigo 服务类的做法同样是可行的,这一方法对于实现外部定义的 WSDL 接口十分有用。
定义数据合同
Indigo 服务类指定了一个服务合同,该服务合同定义了将向服务客户端公开哪些方法。这些操作中的每个操作一般都将传递一些数据,即一个服务合同还隐含有某种数据合同,该数据合同用以描述将被交换的信息。有些情况下,这种数据合同被作为服务合同的一部分来隐含定义。例如,在上面所示的 Calculator 类中,每个方法使用了两个整数型输入参数并返回一个整数。这些参数定义了此服务交换的所有数据,因此它们包含服务的数据合同。对于这种每个操作仅使用简单类型的服务,在服务合同内隐含定义该合同的数据特性较为恰当。无需再进行任何其他操作。
但服务还可能拥有更复杂类型的参数,如结构。在这种情况下,就需要使用显式数据合同。数据合同定义内存中的类型如何转换为适合通过线路传输的形式,即所谓的“序列化”过程。实际上,数据合同是控制数据如何序列化的一种机制。
在 Indigo 服务类中,数据合同使用 DataContract 属性来定义。标记有 DataContract 的类、结构或其他类型都可以拥有一个或多个带有前置 DataMember 属性的成员,指示该成员必须被包含在此类型的序列化值中。以下是一个简单示例:
[DataContract] struct Customer { [DataMember] public string Name; int public age; [DataMember] private int CreditRating; }
当将此 Customer 类型的实例作为一个参数在标记有 OperationContract 的方法中传递时,将只有那些标记有 DataMember 属性的字段(Name 和 CreditRating)被传递。
字段标记为 public 或 private 对该字段是否被序列化没有影响。就像方法的情况一样,public 和 private 关键字是定义此类型如何被同一应用程序中的其他对象访问的合同的一部分。DataMember 则像 OperationContract 一样,定义该类型如何被此类所实现的服务的客户端访问。两者再次完全独立。
关于 Indigo 合同需要强调的最后一点是,没有任何东西默认成为服务合同或数据合同的一部分。相反,开发人员必须显式地使用 ServiceContract 和 DataContract 属性指示哪些类型拥有 Indigo 定义的合同,然后使用 OperationContract 和 DataMember 属性显式地指定这些类型的哪些部分向此服务的客户端公开。其设计者的基本原则之一便是服务必须拥有显式边界,因此 Indigo 是一种明确选择技术。服务要向其客户端提供的任何事物均需在代码中明确指定。
合同以及用于定义它们的那些属性是 Indigo 的主要特性,此处的简短描述仅涵盖了最显著的部分。OperationContract 属性可用于定义“单向”操作,例如不需要应答的服务调用。还可通过创建“双工”合同定义双方均可充当客户端和服务的交互操作,每一方均可调用操作并公开对方调用的操作。DataContract 属性同样拥有多个选项,甚至可以使用一个称为 MessageContract 的属性直接与 SOAP 消息进行内部交互。Indigo 提供的大部分操作均通过合同来表示,因此合同是其最基本的概念。
选择宿主
实现 Indigo 服务的类通常编译到库中。按照定义,所有库都需要运行在宿主应用程序域和 Windows 进程中。Indigo 提供了两种实现服务的宿主库的方法。一种是使用宿主应用程序域和由 Windows 激活服务 (WAS) 提供的进程,而另一种是允许服务托管于运行在任意进程内的任何应用程序域中。本节将从 WAS 开始对两者进行介绍。
使用 Windows 激活服务托管服务
托管 Indigo 服务的最简单方式就是依靠 WAS。(请注意,在 Indigo 的第一个社区技术预览中不支持 WAS。相反,Indigo 服务可托管在 Windows Server 2003 和 Windows XP 上的 Internet 信息服务器中,虽然在该配置中只支持通过 HTTP 传递的 SOAP。)使用 WAS 与使用 IIS 提供的用于 ASMS 的托管机制很相似。其中,两者均依靠“虚拟目录”的概念,它是 Windows 文件系统中的实际目录路径的一个简短别名。
要查看 WAS 托管如何工作,假设前面所述的两个 Calculator 类中有一个类被编译到名为 calc.dll 的库中,然后放置到运行于 Windows Server 2003 上的系统的虚拟目录 calculator 中。要指示 calc.dll 中实现的 Indigo 服务需要使用 WAS 托管,开发人员应在 Calculator 虚拟目录中创建一个带有 .svc(当然是表示“服务”的意思)扩展名的文件。对于我们的简单示例,此文件可能称为 calc.svc,其整个内容可以是:
%@Service language=c# class="Calculator" %
一旦完成这一步,且如下一节所示定义了一个终结点,则客户端对 Calculator 服务的某个方法的请求将自动创建一个该类的实例,以执行指定的操作。该实例将运行在由 WAS 提供的标准进程中创建的应用程序域中。
在任意进程中托管服务
依靠 WAS 提供一个进程来托管 Indigo 服务无疑是最简单的选择。但应用程序经常需要从其自己的进程中公开服务,而不是依靠 Windows 提供的进程。幸运的是,这样做并不困难。下面的示例显示了如何创建托管前面定义的两种 Calculator 类的进程。
using System.ServiceModel; public class CalculatorHost { public static void Main() { ServiceHost<Calculator> s1 = new ServiceHost<Calculator>(); s1.Open(); Console.Writeline("Press ENTER to end service"); Console.Readline(); } }
由于类 CalculatorHost 包含一个 Main 方法,因此它将作为一个独立的进程运行。要托管示例 Calculator 服务,此方法必须创建一个新的 ServiceHost<T> 类的实例,在 Calculator 类中进行传递。(请注意,这种标准 Indigo 类属于“泛型”,通过括住其参数的 < 和 > 指示。泛型是版本 2.0 的 C#、Visual Basic .NET 及其他基于 .NET Framework 2.0 的语言中的一种新语言特性。)一旦创建此类的实例,则使服务可用所需的唯一操作就是对该实例调用 Open 方法。这时 Indigo 将自动将来自客户端的请求转到 Calculator 类中相应的方法。
要使 Indigo 服务能够处理来自其客户端的请求,托管它的进程必须始终在运行。对于 WAS 托管的服务这不是问题,因为 WAS 提供的标准进程可以确保这一点。然而宿主应用程序则必须自己解决这个问题。在这个简单的示例中,进程将按等待控制台用户输入的简单机制持续运行。
定义终结点
除了在 Indigo 服务类中定义操作并指定运行这些操作的宿主进程,Indigo 服务还必须公开一个或多个终结点。每个终结点将指定以下三项内容:
-
一个“合同”名称,指示该 Indigo 服务类通过该终结点公开哪个服务合同。一个标记有 ServiceContract 且未实现任何显式接口的类(例如前面第一个示例中所示的 Calculator)只能公开一个服务合同。在这种情况下,所有终结点将公开同一合同。但是,如果一个类显式实现了两个或多个标记有 ServiceContract 的接口,则不同的终结点就可以公开不同的合同。
-
一个“地址”,指示该终结点位于何处。地址为标识一台计算机以及该计算机上的一个特定终结点的 URL。
-
一个“绑定”,决定如何访问该终结点。绑定决定可以使用哪些协议组合来访问该终结点,还决定了其他一些内容,如通信是否可靠以及可采用哪些安全机制。例如,假设一个服务的创建者希望允许客户端使用通过 HTTP 或 TCP 传递的 SOAP 来访问该服务。它们每个都是一个不同的绑定,因此该服务需要公开两个终结点,一个拥有 SOAP-over-HTTP 绑定,另一个拥有 SOAP-over-TCP 绑定。
绑定是实现通信的关键部分。为了使它们更易于使用,Indigo 包含了一组预先定义好的绑定集合,其中每个绑定指定一个特定的选项组。这个集合包括:
-
BasicProfileHttpBinding:遵循 Web Services Interoperability Organization (WS-I) Basic Profile 1.0,该规范定义了通过 HTTP 传递的 SOAP。这是未显式指定时,终结点的默认绑定方式。
-
BasicProfileHttpsBinding:遵循 WS-I Basic Security Profile 1.0,该规范定义了通过 HTTPS 传递的 SOAP。
-
WsHttpBinding:支持采用 WS-ReliableMessaging 的可选消息传输、采用 WS-Security 的安全性以及采用 WS-AtomicTransaction 的事务。此绑定允许与同样支持这些规范的其他 Web 服务实现之间进行互操作。
-
WsDualHttpBinding:与 WsHttpBinding 类似,但还支持采用双工合同的交互。使用此绑定,服务和客户端均可接收和发送消息。
-
NetTcpBinding:直接通过 TCP 发送二进制编码的 SOAP,包括对可靠消息传输、安全性和事务的支持。此绑定只能用于 Indigo 对 Indigo 通信。
-
NetNamedPipeBinding:通过命名管道发送二进制编码的 SOAP。此绑定只能用于同一 Windows 计算机上两个进程之间的 Indigo 对 Indigo 通信。
-
NetMsmqBinding:通过后面将要介绍的 MSMQ 发送二进制编码的 SOAP。此绑定只能用于 Indigo 对 Indigo 通信。
上图显示了前面所示第一个 Calculator 服务的一个终结点的三个元素中每个元素的示例值。服务合同的名称为 Calculator,也就是实现该服务的类的名称,绑定则是 BasicProfileHttpBinding。假设此服务采用 WAS 托管,安装在前面所述的虚拟目录 calculator 中,并运行在一台名称为 qwickbank.com 的计算机上,则其地址可能为 http://www.qwickbank.com/calculator/calc.svc。
与合同不同,终结点不使用属性来定义。虽然可以通过编程方式创建终结点,但最通用的方式大概还是使用与该服务关联的配置文件来实现。WAS 托管的服务使用 web.config 文件,而那些独立托管的服务则使用与它们运行于其中的应用程序关联的配置文件(通常为 app.config,实际文件名可能会有变化)。如果仅用于前面所示的第一个 Calculator 服务类,此配置文件可能如下所示:
<configuration> <system.serviceModel> <services> <service serviceType="Calculator"> <endpoint contractType="Calculator" bindingType="basicProfileHttpBinding /> </service> </services> </system.serviceModel> </configuration>
一个 Indigo 应用程序实现的所有服务的配置信息均包含在 system.serviceModel 元素内。此元素包含一个 services 元素,而后者又包含一个或多个 service 元素。这个简单的示例仅有一个服务,因此只出现了一个 service。service 元素的 serviceType 属性标识了实现该配置所应用的服务的服务类,在本类中就是 Calculator。每个 service 元素可以包含一个或多个 endpoint 元素,其中每个元素指定一个可通过它访问此 Indigo 服务的特定终结点。在本例中,服务仅公开了一个终结点,因此仅出现了一个 endpoint 元素。终结点合同的名称为 Calculator,也就是实现它的类的名称。如果此配置文件用于前面所示的第二个 Calculator 服务,即使用显式接口定义其服务合同的服务,则 serviceType 属性的值将不变,但 contractType 的值将替换为 ICalculator,即该显式接口的名称。此处指定的绑定为 basicProfileHttpBinding,但由于它是默认设置,因此可以省略。假设 Calculator 是一个 WAS 托管的服务,则地址将自动生成,因此不需要在此配置文件中指定。
创建 Indigo 客户端
创建基本 Indigo 服务没有什么特别复杂的地方。创建 Indigo 客户端则更加简单。需要做的一切就是为服务创建一个称为“代理”的本地替身,它连接到目标服务的某个特定终结点,然后就是通过代理调用该服务的操作。下图显示了此原理。
创建代理需要准确知道目标终结点公开的合同,然后使用该合同的定义生成代理。在 Indigo 中,此过程是由一个称为 svcutil 的工具完成的。如果服务采用 Indigo 实现,svcutil 可以访问该服务的 DLL 以了解合同并生成代理。如果只有服务的 WSDL 定义可用,则 svcutil 可读取它以创建代理。如果只有服务本身可用,svcutil 可通过 WS-MetadataExchange 或一条简单的 HTTP GET 命令直接访问它,以获得该服务的 WSDL 接口定义,然后生成代理。
不管它是如何生成的,客户端均可创建该代理的一个新实例,然后通过该实例调用服务的方法。以下是 Calculator 类的一个客户端的简单示例:
using System.ServiceModel; using Indigo.Example; // 所生成的代理类的命名空间 public class CalculatorClient { public static void Main() { CalculatorProxy p = new CalculatorProxy(); Console.WriteLine("7 + 2 = {0}", p.Add(7, 2)); Console.WriteLine("7 - 2 = {0}", p.Subtract(7, 2)); p.Close(); } }
还有一项内容客户端需要定义:它要调用操作的具体终结点。与服务一样,客户端必须指定终结点的合同、其绑定以及其地址,这些通常是在配置文件中完成的。实际上,如果有足够的信息可用,svcutil 将自动为目标服务生成适当的客户端配置文件。
Indigo 的其他特性
服务和客户端的基础知识对每个 Indigo 应用程序都很重要。但这些应用程序中大部分还将用到此技术的其他方面。本节探讨 Indigo 为基于它建立的应用程序提供的一些其他特性。
控制本地行为
Indigo 的许多特性(如合同、绑定及其他)均跟服务与其客户端之间的通信有关。但也有部分服务行为本质上是本地行为。例如,一个服务实例的生存期是如何控制的,对该实例的并发访问是如何管理的?为了让开发人员控制此类行为,Indigo 定义了两个基本属性,其中每个都拥有大量属性。属性之一为 ServiceBehavior,可用于同样标记有 ServiceContract 属性的类。另一个属性为 OperationBehavior,可用于服务类中同样标记有 OperationContract 属性的方法。
ServiceBehavior 属性具有各种属性,共同影响服务的行为。例如,有一个属性名为 ConcurrencyMode,可用于控制对服务的并发访问。如果设置为 Single,Indigo 在任何时候都只处理对该服务的一个客户端请求,即服务是单线程的。如果设置为 Multiple,Indigo 在任何时候都可以处理对该服务的多个客户端请求,每个请求运行于一个不同的线程上。与此类似,ServiceBehavior 的 InstanceMode 属性可用于控制如何创建和销毁服务的实例。如果 InstanceMode 设置为 PerCall,将为处理每个客户端请求创建该服务的一个新实例,然后当该请求完成时将其销毁。而如果设置为 PrivateSession,则将使用服务的同一实例处理来自某个客户端的所有请求。
例如,假设其创建者决定 Calculator 类应当是多线程的,并且将使用同一实例处理来自某个客户端的每个调用。类的定义将如下所示:
using System.ServiceModel; [ServiceContract] [ServiceBehavior( ConcurrencyMode=Multiple, InstanceMode=PrivateSession)] class Calculator { ... }
与此类似,OperationBehavior 属性上的属性允许控制实现该操作的方法的模拟行为、其事务要求(将在后面讲述)以及其他内容。
消息传输选项
本文中所示的简单示例采用了同步远程过程调用 (PRC) 方法来实现客户端/服务交互。Indigo 支持这种选择,但它不是唯一的选择。SOAP 是一种面向消息的协议,这意味着它可以支持各种编程模型。实际上,Indigo 支持多种可能,包括以下选择:
-
传统 RPC,使用带有类型化参数的阻塞调用;
-
异步 RPC,使用带有类型化参数的非阻塞调用;
-
传统消息传输,使用带有一个消息参数的非阻塞调用;
-
基于消息的 RPC,使用带有一个消息参数的阻塞调用。
尽管绝大多数分布式应用程序需要,但 SOAP 规范未对可靠性进行任何规定。确保可靠性的一种通用方法就是只在点对点情况下使用 SOAP,依靠 TCP 来保证请求和响应的传送。在有些情况下,这样做就已经足够,使用 BasicProfileHttpBinding 时就是这样。
但仍有大量的情况,这样做还不够。例如,如果通过多个 SOAP 中间方访问服务会怎么样?由 TCP 提供的可靠性保证在这种情况下是无法确保端对端可靠性的。为了解决这个问题,Indigo 采用了 WS-ReliableMessaging 规范。通过选择一个使用 WS-ReliableMessaging 的绑定(如 WsHttpBinding),服务及其客户端可以在通过多重 SOAP 中间方的情况下也能保证可靠的端对端通信。
安全性
在网络上公开服务,即使是在内部网络上,一般也会需要某种程度的安全性。服务如何确定客户端的身份?如何防止发送到服务的消息和从服务接收的消息被恶意更改和窃取?如何使对服务的访问仅限于那些被授权使用它的客户端?如果没有解决这些问题的解决方案,公开大量服务就会非常危险。而构建安全的应用程序则会使事情复杂化。理想的情况,应当是采用简单直接的方式应对通用的安全情况,同时对有需要的应用程序采取更精细的控制。
为达到这一目的,Indigo 提供了身份验证、消息完整性、消息保密和授权等核心安全功能。Indigo 实现这些功能中前三个功能的方法主要依靠绑定,开发人员的选择有:
-
选择一种支持安全性的标准绑定。例如,只需要基于传输的安全性的应用程序可以采用 BasicProfileHttpsBinding 之类的绑定。这种方法对于那些不需经过任何中间方(如 HTTP 代理或其他 SOAP 节点)而直接从客户端到达服务的请求已经足够。需要确保经过多重 SOAP 中间方的消息的端对端安全性的应用程序,则可以采用支持 WS-Security 的绑定,如 WsHttpBinding。
-
选择一种支持安全性的标准绑定,然后通过改变一个或多个默认值对其进行自定义。例如,如果需要,可以更改一些绑定(如 WsHttpBinding)所采用的身份验证机制。
-
创建一个准确提供开发人员需要的安全特性的自定义绑定。这种方法不适合害怕繁琐者,但确实是一些高级情况的正确方法。
-
选择一种不支持安全性的标准绑定,如 BasicProfileHttpBinding。虽然采用不支持安全性的绑定通常是件危险的事,但在有些情况下这仍然是最佳选择。
Indigo 服务还可以控制授权哪些客户端使用该服务。大体而言,Indigo 只支持 .NET Framework 中已有的授权机制。例如,服务可以使用标准 PrincipalPermission 属性定义允许哪些客户端访问它。
让开发人员构建安全的应用程序而又避免使他们面对极大的复杂性,已被证明极具挑战性。通过为大多数通用情况提供简单直接的方法,同时为更为复杂的情况提供精细的控制,Indigo 正在以一种可行且有效的方式实现这一目标。
事务
处理事务是构建许多业务逻辑的一个重要方面。但在面向服务的世界中使用事务却很麻烦。分布式事务假设参与各方之间存在高级别的信任,因此一般不适合跨服务边界的事务。但仍然存在一些情况,将事务和服务绑定起来可以起到很好的作用,因此 Indigo 包含了对这一重要的应用程序设计特性的支持。
.NET Framework 2.0 中的事务
Indigo 中的事务支持构建在 .NET Framework 2.0 提供的机制上。这一即将发布的版本中包含 System.Transactions,这是一种新的命名空间,完全专注于控制事务性行为。开发人员将最常将 System.Transactions 与某个“执行上下文”配合使用,这是 .NET Framework 2.0 中的一种新结构。执行上下文允许指定适用于包含在一个定义范围内的所有代码的通用信息,如事务。以下是应用程序如何使用该方法将一组操作组合成一个事务的示例:
using System.Transactions; using (TransactionScope ts = new TransactionScope(Required)) { // 执行操作,例如更新不同的 DBMS ts.Complete(); }
位于 using 块内的所有操作将成为一个事务的一部分,因为它们共享其所定义的事务执行上下文。本例中的最后一行,调用 TransactionScope 的 Complete 方法,将导致退出该块时请求提交该事务。此方法还提供了内置的错误处理,出现异常时会终止事务。
如本例那样,为新 TransactionScope 指定 Required,意味着此代码将总是作为事务的一部分运行:若其调用方的事务存在,则加入之;若不存在,则创建一个新的。如同在企业服务中一样,还可以指定其他选项,包括 RequiresNew、Supported 和 NotSupported。
与企业服务及其前任 MTS 和 COM+ 不同,Systems.Transactions 完全专注于控制事务性行为。例如,事务与对象的内部状态之间不需要存在连接。企业服务要求一个对象在其结束事务时被停用,但 Systems.Transactions 则没有这种需要。由于 Indigo 建立在 Systems.Transaction 上,因此 Indigo 应用程序也是独立管理事务和对象状态的。
Indigo 中的事务
Indigo 应用程序可以显式使用 System.Transactions,也可以隐式使用依赖于 System.Transactions 的属性来控制事务。一种选择是,对位于标记有 ServiceContract 属性的类中的方法,使用前面所述的 TransactionScope 将其工作包装到一个事务中。例如,该方法可以包含一个 using 语句,建立一个事务范围,然后在该事务内更新两个独立的数据库。
服务的方法还可以使用属性来控制事务性行为。除了显式使用 System.Transactions 外,服务还可以使用前面所述的 OperationBehavior 属性。下面是一个示例:
using System.ServiceModel; [ServiceContract] class XactOperations { [OperationContract] public int Add(int value1, int value2) { return value1 + value2; } [OperationContract] [OperationBehavior(RequireTransaction=true, AutoCompleteTransaction=true)] int Update(int value1, int value2) { // 将 value1 和 value2 插入到 // 两个不同的数据库中 } }
本例中的第一个方法 Add 没有使用事务,因此其简单操作将和以前一样发生。但是第二个方法 Update 前置有 OperationBehavior 属性,同时 RequireTransaction 属性被设置为 true。因此,该方法中完成的所有工作将发生在一个事务内,就像其位于前面所示的 using 块事务范围内一样。同时由于还指定了 AutoCompleteTransaction 属性,因此如果不出现异常,事务将自动提交。
如果调用此方法的客户端不是运行在事务内,则 Update 方法将在其自己的事务内运行,没有其他选择。但这里假定客户端在调用 Update 时已经是某个现有事务的一部分。Update 方法所完成的工作是否会加入客户端的事务,或其是否仍运行在自己独立的事务中?答案取决于此服务能否接受由客户端传递的“事务上下文”,这是通过 OperationContract 属性的 TransactionFlowAllowed 属性控制的一个选项。如果服务中的一个方法未附加 TransactionFlowAllowed 属性,如上例所示,该方法内所完成的工作将永远不会加入现有事务中。而如果此属性存在,则方法将能够加入其客户端的事务中。
值得强调的还有,基于 Indigo 的应用程序可以参与包含运行于非 Indigo 平台上的应用程序的事务。例如,一个 Indigo 应用程序可以启动一个事务,更新本地 SQL Server 数据库中的记录,然后调用在一个 J2EE 应用程序服务器上实现的 Web 服务,更新另一个数据库中的记录。如果该服务是事务型的,且其运行的平台支持 WS-AtomicTransaction 规范,则两个数据库的更新可以是同一事务的一部分。与安全性和可靠消息传输相似,Indigo 事务工作在 Web 服务导致的异质环境中。
队列
使用绑定(如 WsHttpBinding),Indigo 应用程序可以与基于 Indigo 或任何其他实现了 WS-ReliableMessaging 的 Web 服务平台上的另一个应用程序进行可靠通信。但尽管此规范定义的技术确保了 SOAP 消息的可靠端对端传送,它却不能实现消息队列。使用队列,应用程序只需将消息发送到队列,而不是直接发送到另一个应用程序。当接收应用程序准备好时,它就可以从队列读取消息并进行处理。启用这种交互很有用,例如,当消息的发送方和接收方可能不是同时运行的时候。
因此,Indigo 提供了对消息队列的支持。这种支持建立在 MSMQ 之上,这意味着与 Indigo 的大多数其他特性(如可靠消息传输、安全性和事务等)不同,Indigo 队列并不支持跨供应商边界直接进行互操作(尽管可以使用 MSMQ-MQSeries 桥)。
要使用 Indigo 队列,开发人员需要创建一个标准的 Indigo 服务类,照常使用 ServiceContract 进行标记。但位于此类的服务合同中的操作具有一些限制。特别是,它们必须全部标记为单向,即不返回任何响应。这并不奇怪,因为调用排队的操作是将消息发送到一个队列中,而不是其最终接收方,因此等待立即响应没有任何意义。与其他任何服务类一样,队列 Indigo 应用程序也需要公开终结点。这些终结点使用了一些绑定,例如:NetMsmqBinding,允许与其他队列 Indigo 应用程序进行通信;或 MsmqIntegrationBinding,允许一个队列 Indigo 应用程序与不使用 Indigo 的标准 MSMQ 应用程序进行互操作。Indigo 队列还支持队列环境的其他传统特性,如“死信”队列和有毒消息的处理等。
队列对绝大多数分布式应用程序都是正确之选。Indigo 对这种通信方式的支持是开发人员无需了解完全独立的队列技术即可构建队列应用程序。
共存和移植
Indigo 代表在可靠、安全和事务型服务时代创建分布式应用程序的一种新型方法。然而,需要理解的一个关键在于,安装 Indigo 不会破坏任何现有的应用程序。运行于 ASMX、.NET Remoting 以及 Indigo 包含其功能的其他技术上的当前代码,均可以继续运行,因而不需要移植到 Indigo。但对于那些拥有对当前 Microsoft 技术的投资的机构,仍然存在一个明显的问题:采用 Indigo 之前的技术编写的现有代码会发生什么情况?
对于因 Indigo 的出现而前途深受影响的每一项当前技术,开发人员需要理解以下两件事:基于此技术的应用程序是否将与基于 Indigo 的应用程序进行互操作,将应用程序从此技术移植到 Indigo 环境需要完成的工作量有多大?以下是对每项技术如何解决这些问题的简短描述:
-
ASP.NET Web 服务 (ASMX):采用 ASMX 建立的 Web 服务会与 Indigo 应用程序进行互操作。由于 ASP.NET Web 服务和 Indigo 两者均支持标准 SOAP,因此这不应该有什么奇怪之处。将现有 ASP.NET Web 服务代码移植到 Indigo 需要进行一些机械性工作,但仍然简单直接。两种技术的基本结构十分相似,因此大体上只有属性和配置文件需要改变。但更高级的特性(如 SOAP 扩展等)将无法直接移植到 Indigo。相反,需要使用 Indigo 提供的扩展选项对它们进行重写。
-
.NET Remoting:基于 .NET Remoting 的应用程序不会与基于 Indigo 的应用程序进行互操作,它们的传输协议不兼容。将现有 .NET Remoting 代码移植到 Indigo 需要进行一些工作,但仍然是可能实现的。但是如果一个人建立了自定义的 .NET Remoting 扩展(如通道和接收),将会发现该代码无法映射到新环境。Indigo 中存在相似的扩展,但实现的接口与 .NET Remoting 中实现的接口不匹配。
-
企业服务:为了使现有的企业服务应用程序能够与 Indigo 客户端(或其他基于 Web 服务的软件)进行互操作,开发人员可以精确指定该应用程序中的哪些接口应当公开。使用 Indigo 提供的一个工具,可以自动为那些接口创建服务合同,并通过 Indigo 公开。对于那些不基于 .NET Framework 的企业服务应用程序的现有客户端(以及其他纯粹基于 COM 的客户端),提供了一个 Indigo 名字对象,以允许直接访问 Web 服务。将现有的企业服务应用程序移植为直接在 Indigo 上运行所需的工作与移植 ASMX 应用程序所需的工作类似。尽管不是全部,但大部分工作都是对属性和命名空间的直接机械的修改。
-
Web 服务增强 (WSE):WSE 是 Microsoft 为实现需要 WS-* 规范所提供的部分或全部功能的 Web 服务应用程序而采用的一种战术性解决方案。基于 WSE 1.0 和 WSE 2.0 的应用程序不会与基于 Indigo 的应用程序进行互操作。但基于将在 Indigo 发布之前交付的 WSE 3.0 的应用程序将与 Indigo 应用程序进行互操作。对于可移植性,情况与已经介绍的技术类似:将现有代码从 WSE 移植到 Indigo 需要进行一定的工作,但对于使用最后 WSE 版本编写的应用程序这一工作将大大减小。
-
MSMQ:由于 Indigo 的队列功能基于 MSMQ,因此基于 Indigo 的队列应用程序可以与直接基于 MSMQ 的队列应用程序进行互操作。将应用程序从初始 .NET Framework 提供的 System.Messaging 命名空间进行移植需要一些工作,因为这种早期接口与 Indigo 所提供的接口不同。一旦 Indigo 交付,开发人员就应使用它而不是 System.Messaging 来创建大多数基于 MSMQ 的队列应用程序。
在 Indigo 面世之前,对不需要队列的分布式 .NET 应用程序而言,最佳技术选择大概就是 ASMX。它使用简单,同时提供了移植到 Indigo 的最平滑的途径。企业服务对需要其提供的特性(如分布式事务)的应用程序也很重要,但 MSMQ 仍然是队列应用程序的正确选择。而 .NET Remoting 则应主要用于同一进程中的两个应用程序域之间的通信。对其他大部分情况下的直接应用程序对应用程序通信,ASMX 是一种较好的选择。
引入新软件总是会对已经存在的东西有影响。通过提供构建面向服务应用程序的通用基础,Indigo 为开发人员提供了一种更简单、更统一的平台。尽管这种改变会引起一些痛苦,但 Indigo 创建者们的目标是使这种转换尽可能平滑和简单。
结论
Indigo 代表了在开发人员创建软件的方式方面的一项重大进步。随着面向服务的应用程序越来越普遍,Indigo 将成为 Windows 软件开发人员的主流技术。其他 Microsoft 产品也将转而利用 Indigo 所带来的优点。例如 BizTalk Server,将在 BizTalk Server 2006 发布后的某个时候加入对 Indigo 作为一种通信选项的支持。由于 Indigo 为面向服务的软件提供了一种标准基础,因此它将成为大部分 Windows 通信的基础。
这一技术的影响势必不会小。任何人要在 Windows 上构建分布式应用程序,特别是那些必须与其他平台上的应用程序进行互操作的应用程序,都应当给予密切关注。Indigo 将极大地改变他们的世界。