服务与地址、绑定以及契约有关。其中,地址定义了服 务的位置,绑定定义了服务通信的方式,契约则定义了服务的内容。为便于记忆,我们可以将这种类似于“三权分立”一般管理服务的方式简称为服务的ABC。 WCF用终结点表示这样一种组成关系。终结点就是地址、契约与绑定的混成品(参见图1-5)。
图1-5:终结点
每一个终结点都包含了三个元素,而宿主则负责公开终 结点。从逻辑上讲,终结点相当于服务的接口,就像CLR或者COM接口一样。注意,图1-5使用了传统的“棒棒糖”形式展示了一个终结点的构成。
注意:从概念上讲,不管是C#还是VB,一个接口就 相当于一个终结点:地址就是类型虚拟表的内存地址,绑定则是CLR的JIT(Just-In-Time)编译,而契约则代表接口本身。由于经典的.NET 编程模式不需要处理地址或绑定,你可能认为它们是理所当然存在的。而WCF并未规定地址与绑定,因而必须对它们进行配置。
每个服务至少必须公开一个业务终结点,每个终结点有 且只能拥有一个契约。服务上的所有终结点都包含了唯一的地址,而一个单独的服务则可以公开多个终结点。这些终结点可以使用相同或不同的绑定,公开相同或不 同的契约。每个服务提供的不同终结点之间绝对没有任何关联。
重要的一点是,服务代码并没有包含它的终结点,它们 通常放在服务代码之外。我们可以通过管理方式(Administratively)使用配置文件或者通过编程方式(Programmatically)配 置终结点。
管理方式配置终结点
以管理方式配置一个终结点需要将终结点放到托管进程 的配置文件中,如下的服务定义:
namespace MyNamespace
{
[ServiceContract]
interface IMyContract
{...}
class MyService : IMyContract
{...}
}
例1-6演示了配置文件要求的配置入口。在每个服务 类型下列出它的终结点。
例1-6:管理方式配置终结点
<system.serviceModel>
<services>
<service name = "MyNamespace.MyService">
<endpoint
address = http://localhost:8000/MyService/
binding = "wsHttpBinding"
contract = "MyNamespace.IMyContract"
/>
</service>
</services>
</system.serviceModel>
当我们指定服务和契约类型时,必须使用类型全名。在 本书的其余例子中,为简略起见,我省略了类型的命名空间,但在实际应用中,命名空间是必备的。注意,如果终结点已经提供了基地址,则地址的样式必须与绑定 一致,例如HTTP对应WSHttpBinding。如果两者不匹配,就会在装载服务时导致异常。
例1-7的配置文件为一个单独的服务公开了多个终结 点。多个终结点可以配置相同的基地址,前提是URI互不不同。
例1-7:相同服务的多个终结点
<service name = "MyService">
<endpoint
address = "http://localhost:8000/MyService/"
binding = "wsHttpBinding"
contract = "IMyContract"
/>
<endpoint
address = "net.tcp://localhost:8001/MyService/"
binding = "netTcpBinding"
contract = "IMyContract"
/>
<endpoint
address = "net.tcp://localhost:8002/MyService/"
binding = "netTcpBinding"
contract = "IMyOtherContract"
/>
</service>
大多数情况下,我们的首选是管理的配置方式,因为它 非常灵活,即使修改了服务的地址、绑定和契约,也不需要重新编译服务和重新部署服务。
使用基地址
例1-7中的每个终结点都提供了自己独有的基地址。 如果我们提供了显式的基地址,它会重写宿主提供的所有基地址。
我们也可以让多个终结点使用相同的基地址,只要终结 点地址中的URI不同:
<service name = "MyService">
<endpoint
address = "net.tcp://localhost:8001/MyService/"
binding = "netTcpBinding"
contract = "IMyContract"
/>
<endpoint
address = "net.tcp://localhost:8001/MyOtherService/"
binding = "netTcpBinding"
contract = "IMyContract"
/>
</service>
反之,如果宿主提供了与传输样式匹配的基地址,则可 以省略地址项。此时,终结点地址与该基地址完全相同:
<endpoint
binding = "wsHttpBinding"
contract = "IMyContract"
/>
如果宿主没有提供匹配的基地址,则在装载服务宿主时 会抛出异常。
配置终结点地址时,可以为基地址添加相对URI:
<endpoint
address = "SubAddress"
binding = "wsHttpBinding"
contract = "IMyContract"
/>
此时,终结点地址等于它所匹配的基地址加上URI。 当然,前提是宿主必须提供匹配的基地址。
绑定配置
使用配置文件可以为终结点使用的绑定进行定制。为 此,需要在<endpoint>节中添加bindingConfiguration标志,它的值应该与<bindings>配置 节中定制的绑定名一致。例1-8介绍了使用这种技术启用事务传播的方法。其中的transactionFlow标志会在第7章详细介绍。
例1-8:服务端绑定的配置
<system.serviceModel>
<services>
<service name = "MyService">
<endpoint
address = "net.tcp://localhost:8000/MyService/"
bindingConfiguration = "TransactionalTCP"
binding = "netTcpBinding"
contract = "IMyContract"
/>
<endpoint
address = "net.tcp://localhost:8001/MyService/"
bindingConfiguration = "TransactionalTCP"
binding = "netTcpBinding"
contract = "IMyOtherContract"
/>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name = "TransactionalTCP"
transactionFlow = "true"
/>
</netTcpBinding>
</bindings>
</system.serviceModel>
如例1-8所示,我们可以在多个终结点中通过指向定制绑定的方式,重用已命名的绑定配置。
编程方式配置终结点
编程方式配置终结点与管理方式配置终结点等效。但它 不需要配置文件,而是通过编程调用将终结点添加到ServiceHost实例中。这些调用不属于服务代码的范围。ServiceHost定义了重载版本的 AddServiceEndpoint()方法:
public class ServiceHost : ServiceHostBase
{
public ServiceEndpoint AddServiceEndpoint(Type implementedContract,
Binding binding,
string address);
//其他成员
}
传入AddServiceEndpoint()方法 的地址可以是相对地址,也可以是绝对地址,这与使用配置文件的方式相似。例1-9演示了编程配置的方法,它配置的终结点与例1-7的终结点相同。
例1-9:服务端编程配置终结点
ServiceHost host = new ServiceHost(typeof(MyService));
Binding wsBinding = new WSHttpBinding();
Binding tcpBinding = new NetTcpBinding();
host.AddServiceEndpoint(typeof(IMyContract),wsBinding,
"http://localhost:8000/MyService");
host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,
"net.tcp://localhost:8001/MyService");
host.AddServiceEndpoint(typeof(IMyOtherContract),tcpBinding,
"net.tcp://localhost:8002/MyService");
host.Open();
以编程方式添加终结点时,address参数为 string类型,contract参数为Type类型,而binding参数的类型则是Binding抽象类的其中一个子类,例如:
public class NetTcpBinding : Binding,...
{...}
由于宿主提供了基地址,因此若要使用基地址,可以将 空字符串赋给address参数,或者只设置URI值,此时使用的地址就应该是基地址加上URI:
Uri tcpBaseAddress = new Uri("net.tcp://localhost:8000/");
ServiceHost host = new ServiceHost(typeof(MyService),tcpBaseAddress);
Binding tcpBinding = new NetTcpBinding();
//使用基地址作为地址
host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,"");
//添加相对地址
host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,"MyService");
//忽略基地址
host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,
"net.tcp://localhost:8001/MyService");
host.Open();
使用配置文件进行管理方式的配置,宿主必须提供一个 匹配的基地址,否则会引发异常。事实上,编程方式配置与管理方式配置并没有任何区别。使用配置文件时,WCF会解析文件,然后执行对应的编程调用。
绑定配置
我们可以通过编程方式设置绑定的属性。例如,以下代 码就实现了与例1-8相似的功能,启用事务传播:
ServiceHost host = new ServiceHost(typeof(MyService));
NetTcpBinding tcpBinding = new NetTcpBinding();
tcpBinding.TransactionFlow = true;
host.AddServiceEndpoint(typeof(IMyContract),tcpBinding,
"net.tcp://localhost:8000/MyService");
host.Open();
注意,在处理特定的绑定属性时,通常应该与具体的绑 定子类如NetTcpBinding交互,而不是使用抽象类Binding,正如例1-9所示。