在定义和寄宿WCF服务的时候会面临三个名称/命名空间,它们分别是ServiceContractAttribute、ServiceBehaviorAttribute和Binding的Name和Namespace属性,很对人对此不能很好地区分。
每个服务契约都有一个确定的名称,当在一个接口或类上应用了ServiceContractAttribute特性,默认的名称就是接口或类的名称。我们可以通过Name属性显式地指定需要的名称,这在某些场景中往往具有重要的作用。比如在客户端有一个通过接口的形式定义的服务契约,现有的很多客户端代码均依赖于这个接口,如果这个时候服务方的名称改变了,客户端仅须更新这个Name属性,从而避免修改接口的名称而造成对现有代码的影响。
1: public sealed class ServiceContractAttribute : Attribute
2: {
3: //其他成员
4: public string Name { get; set; }
5: public string Namespace { get; set; }
6: }
至于服务契约的命名空间,其作用和我们托管语言(比如C#、VB.NET)的命名空间完全一样,旨在解决命名冲突问题。很多WCF的编程人员都不太注重在定义服务契约的时候指定命名空间,这是一个不太好的习惯。我们鼓励采用包含你所在的公司名称或项目名称作为命名空间。WCF默认采用的命名空间是http://tempuri.org/。
作为服务的描述信息,服务契约作为WSDL的一部分以元数据的形式发布出来。WSDL通过<portType>元素定义相应的服务契约。ServiceContractAttribute的Name和Namespace属性对应着用于描述服务契约的<portType>元素的名称和命名空间。
1: [ServiceContract(Name = "CaclService", Namespace = "http://www.artech.com/")]
2: public interface ICalculator
3: {
4: //省略成员
5: }
如上面的代码所示,我们应用了ServiceContractAttribute特性将接口ICalculator接口定义成服务契约。ServiceContractAttribute的Name和Namespace分别被设置成CaclService和http://www.artech.com/"。服务契约将会对应着如下一段WSDL。
1: <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
2: xmlns: tns = "http://www.artech.com/"...>
3: <wsdl:portType name="tns:CaclService">
4: ...
5: </wsdl:portType>
6: </wsdl:definitions>
关于通过ServiceContractAttribute特性定义的服务契约的名称和命名空间,很多人会和通过ServiceBehaviorAttribute定义的名称和命名空间混淆。
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
3: {
4: //其他成员
5: public string Name { get; set; }
6: public string Namespace { get; set; }
7: }
实际上服务行为特性ServiceBehaviorAttribute定义的是服务本身的名称和命名控件。这两个属性将作为整个WSDL根节点< definitions >的name和targetNamespace属性。如果没有对其进行显式设置,默认的命名空间为http://tempuri.org/。WCF将使用服务类型的名称作为作为服务名称。
1: [ServiceBehavior(Name = "CaclService",
2: Namespace ="http://www.artech.com")]
3: public class CalculatorService : ICalculator
4: {
5: //省略成员
6: }
对于上面定义的服务类型来说,由于我们通过ServiceBehaviorAttribute特性对名称和命名空间进行了显式设置。用于描述服务的WSDL将具有如下一个根节点。
1: <wsdl:definitions name="CaclService"
2: targetNamespace="http://www.artech.com"
3: xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" ...>
4: </wsdl:definitions>
既然已经将到了服务契约和服务的名称和命名空间,我们顺便来谈谈另一组命名和命名空间。其实作为终结点三要素之一的绑定也具有自己的名称和命名空间。如下面的代码所示,作为绑定基类的抽象类Binding同样具有一组Name和Namespace属性。
1: public abstract class Binding
2: {
3: //省略成员
4: public string Name { get; set; }
5: public string Namespace { get; set; }
6: }
Binding的名称和命名空间通过服务终结点的bindingName和bindingNamespace属性进行设置。由于这两个属性属于服务描述范畴,所以客户端终结点无此设置。由于绑定在WSDL中对应的节点为<binding>,所以绑定的Name和Namespace属性值将作为对应的<binding>节点的名称和命名空间。在默认的情况下,<binding>元素的命名空间的值依然是http://tempuri.org/。至于名称,则通过绑定类型名称和契约名称合并而成。比如说契约名称为ICalculator,并采用BasicHttpBinding,那么对应的<binding>元数的名称为BasicHttpBinding_ICalculator。
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service ...>
5: <endpoint bindingName="myBasicHttpBinding"
6: bindingNamespace="http://www.artech.com" .../>
7: </service>
8: </services>
9: </system.serviceModel>
10: </configuration>
比如说在服务寄宿时采用如上的配置将终结点的绑定名称和命名空间进行了显式设置,并且采用如上的服务契约(名称被定义成CalcService)。该终结点绑定在WSDL中将对应于如下一个<binding>元素。
1: <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
2: xmlns:tns = "http://www.artech.com/">
3: <wsdl:binding name="tns: myBasicHttpBinding_CalcService" ...>
4: </wsdl:binding>
5: </wsdl:definitions>