很多WCF的初学者是从之前的Web服务上转移过来的,他们非常怀念.asmx Web服务无配置的服务寄宿方式。你只需要在定义Web服务的时候再表示服务操作的方法上应用WebMethodAttribute特性就可以了,完全可以不需要手工进行相应的配置。WCF 4.0通过默认终结点添加机制为你提供无配置服务寄宿的支持。
很多WCF的初学者是从之前的Web服务上转移过来的,他们非常怀念.asmx Web服务无配置的服务寄宿方式。你只需要在定义Web服务的时候再表示服务操作的方法上应用WebMethodAttribute特性就可以了,完全可以不需要手工进行相应的配置,因为Web服务运行时会自动为你添加默认的配置。但是对于WCF来说,在进行服务寄宿的时候,你必须以编程或者配置的方式为服务添加至少一个终结点,而终结点需要具备基本的ABC三要素。
对于最新版本的WCF编程人员来说,你也可以采用无配置的服务寄宿了,这主要得益于WCF提供的默认终结点机制。所谓默认终结点,顾名思义,就是在你尚未为寄宿的服务添加任何终结点的时候,WCF会自动根据服务的基地址(Base Address)为你创建一个或者多个默认的终结点。
我们举个简单的例子,现在我们具有一个服务叫做GreetingService的服务,它实现了两个服务契约IHello和IGoodbye。下面的代码片断提供了服务类型GreetingService和服务契约接口的定义。
1: [ServiceContract]
2: public interface IHello
3: {
4: [OperationContract]
5: void SayHello(string name);
6: }
7: [ServiceContract]
8: public interface IGoodbye
9: {
10: [OperationContract]
11: void SayGoodbye(string name);
12: }
13: public class GreetingService : IHello, IGoodbye
14: {
15: public void SayHello(string name)
16: {
17: //省略实现
18: }
19: public void SayGoodbye(string name)
20: {
21: //省略实现
22: }
23: }
现在我们创建一个简单的控制台程序作为服务的宿主,在不提供任何配置文件的情况下调用如下的代码对服务进行自我寄宿。当用于寄宿服务的ServiceHost被开启之后,打印出其具有的终结点信息。
1: Uri baseHttpAddress = new Uri("http://127.0.0.1/greetingservice ");
2: Uri baseTcpAddress = new Uri("net.tcp://127.0.0.1/greetingservice ");
3: ServiceHost host = new ServiceHost(typeof(GreetingService), baseHttpAddress, baseTcpAddress);
4: host.Open();
5: int index = 0;
6: foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
7: {
8: Console.WriteLine("Endpoint {0}",++index);
9: Console.WriteLine("\tAddress: {0}\n\tBinding: {1}\n\tContract: {2}",
10: endpoint.Address, endpoint.Binding, endpoint.Contract.Name);
11: }
输出结果:
1: Endpoint 1
2: Address: http://127.0.0.1/greetingservice
3: Binding: System.ServiceModel.BasicHttpBinding
4: Contract: IHello
5: Endpoint 2
6: Address: http://127.0.0.1/greetingservice
7: Binding: System.ServiceModel.BasicHttpBinding
8: Contract: IGoodbye
9: Endpoint 3
10: Address: net.tcp://127.0.0.1/greetingservice
11: Binding: System.ServiceModel.NetTcpBinding
12: Contract: IHello
13: Endpoint 4
14: Address: net.tcp://127.0.0.1/greetingservice
15: Binding: System.ServiceModel.NetTcpBinding
16: Contract: IGoodbye
从输出的结果我们不难看出,虽然我们没有以任何形式为寄宿的服务提供终结点,但是WCF会自动为之添加四个默认的终结点。之所以是四个默认终结点,其原因在于:WCF会为服务实现的每一个服务契约基于指定的每一个基地址创建一个终结点。在本例中,服务GreetingService实现了两个服务契约,在寄宿过程中又为它指定了两个基地址,所以最终被自动创建的默认终结点是四个。对于自动创建的终结点,其地址和服务契约分别来源于指定的基地址和服务实现的契约,那么采用的绑定又是如何确定的呢?
一、默认终结点的绑定是如何确定的?
从上面的例子我们可以看到,对于自动创建的四个默认终结点,如果采用基于HTTP协议的地址,则采用BasicHttpBinding作为其终结点绑定;如果地址是基于TCP协议的,作为终结点绑定的则为NetTcpBinding。所以说定义在基地址中用以表示传输协议的前缀(Scheme)最终决定了采用的绑定类型。
但是,为什么基于HTTP协议的地址采用BasicHttpBinding,而不是WSHttpBinding或者WS2007HttpBinding呢?实际上,基地址的协议类型和最终作为默认终结点的类型之间的匹配关系是通过配置决定的。在<system.serviceModel>配置节中具有一个名为<protocolMapping>的子结点。它包含了一系列用以定义传输协议类型(scheme)和绑定类型匹配关系的配置元素。
如果你打开基于.NET Framework 4.0的配置文件machine.config.comments(该配置文件所在的目录为%Windows%Microsoft.NET\Framework\v4.0.30319\Config),你会发现<protocolMapping>配置节具有如下的定义。具体来说,<protocolMapping>配置节定义了四种传输协议(HTTP、TCP、Named Pipe和MSMQ)和对应的绑定类型(BasicHttpBinding、NetTcpBiding、NetNamedPipeBinding和NetMsmqBinding)之间的匹配关系。这实际代表了默认的协议绑定映射关系,这也是为什么在上面的例子中基于HTTP协议的默认终结点会采用BasicHttpBinding作为绑定类型的原因。除了scheme和binding这两个配置属性之外,<protocolMapping>的配置元素还具有另外一个额外的配置属性bindingConfiguration,表示对具体绑定配置的引用。
1: <system.serviceModel>
2: <protocolMapping>
3: <add scheme="http" binding="basicHttpBinding" bindingConfiguration="" />
4: <add scheme="net.tcp" binding="netTcpBinding" bindingConfiguration=""/>
5: <add scheme="net.pipe" binding="netNamedPipeBinding" bindingConfiguration=""/>
6: <add scheme="net.msmq" binding="netMsmqBinding" bindingConfiguration=""/>
7: </protocolMapping>
8: ...
9: </system.serviceModel>
如果默认的协议与绑定映射关系不满足具体应用场景的要求,你可以直接修改machine.config或者基于具体应用的App.config或者Web.config。比如,对于上面的例子,如果为之添加一个配置文件并进行如下的配置:将基于HTTP的绑定类型设置为WS2007HttpBinding。再次运行实例程序,将会发现默认创建的终结点类型发生了相应的改变。
1: version="1.0"
2: <configuration>
3: <system.serviceModel>
4: <protocolMapping>
5: <add scheme="http" binding="ws2007HttpBinding" />
6: </protocolMapping>
7: </system.serviceModel>
8: </configuration>
输出结果
1: Endpoint 1
2: Address: http://127.0.0.1/greetingservice
3: Binding: System.ServiceModel.WS2007HttpBinding
4: Contract: IHello
5: Endpoint 2
6: Address: http://127.0.0.1/greetingservice
7: Binding: System.ServiceModel.WS2007HttpBinding
8: Contract: IGoodbye
9: Endpoint 3
10: Address: net.tcp://127.0.0.1/greetingservice
11: Binding: System.ServiceModel.NetTcpBinding
12: Contract: IHello
13: Endpoint 4
14: Address: net.tcp://127.0.0.1/greetingservice
15: Binding: System.ServiceModel.NetTcpBinding
16: Contract: IGoodbye
二、默认终结点是如何被添加的?
接下来我们来具体介绍默认终结点机制是如何实现的,具体来讲就是表示默认终结点的ServiceEndpoint对象是如何被添加到用于表示寄宿服务描述的ServiceDescription的终结点列表(对应于ServiceDescription的Endpoints属性)中的。要了解默认终结点自动添加的原理,需要涉及到WCF 4.0为ServiceHostBase添加的一个新方法:AddDefaultEndpoints。
1: public abstract class ServiceHostBase : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
2: {
3: //其他成员
4: public virtual ReadOnlyCollection<ServiceEndpoint> AddDefaultEndpoints();
5: }
从方法名称我们不难看出,这个方法就是用于实现为ServiceHost添加默认终结点的。从上面给出的关于这个方法的定义我们可以知道这个方法是一个公有方法,可以在具体的服务寄宿应用中被直接调用。当这个方法被调用的时候,WCF会按照我们之前介绍的策略(为指定的每一个基地址和服务实现的契约的组合添加一个终结点,终结点绑定的类型决定于<protocolMapping>配置)进行默认终结点的添加。方法的返回值表示添加的默认终结点集合。
当ServiceHost在开启的时候,WCF会检验其Description熟悉表示的服务描述是否具有至少一个终结点。如果没有,在会自动调用这个AddDefaultEndpoints方法以添加默认的终结点。比如在下面的代码片断中,在开启ServiceHost之前调用AddServiceEndpoint方法添加了一个终结点,最终默认终结点将不会被添加,所以ServiceHost最终只会有一个唯一的终结点。
1: Uri baseHttpAddress = new Uri("http://127.0.0.1/greetingservice ");
2: Uri baseTcpAddress = new Uri("net.tcp://127.0.0.1/greetingservice ");
3: ServiceHost host = new ServiceHost(typeof(GreetingService), baseHttpAddress, baseTcpAddress);
4: host.AddServiceEndpoint(typeof(IHello), new WSHttpBinding(), "manuallyadded");
5: host.Open();
6: ...
输出结果:
1: Endpoint 1
2: Address: http://127.0.0.1/greetingservice/manuallyadded
3: Binding: System.ServiceModel.WSHttpBinding
4: Contract: IHello
由于公有的AddDefaultEndpoints方法可以手工被调用,所以当你在调用AddServiceEndpoint方法之后再调用该方法,ServiceHost最终将会具有
1: Uri baseHttpAddress = new Uri("http://127.0.0.1/greetingservice ");
2: Uri baseTcpAddress = new Uri("net.tcp://127.0.0.1/greetingservice ");
3: ServiceHost host = new ServiceHost(typeof(GreetingService), baseHttpAddress, baseTcpAddress);
4: host.AddServiceEndpoint(typeof(IHello), new WSHttpBinding(), "manuallyadded");
5: host.AddDefaultEndpoints();
6: host.Open();
7: ...
输出结果:
1: Endpoint 1
2: Address: http://127.0.0.1/greetingservice/manuallyadded
3: Binding: System.ServiceModel.WSHttpBinding
4: Contract: IHello
5: Endpoint 2
6: Address: http://127.0.0.1/greetingservice
7: Binding: System.ServiceModel.WS2007HttpBinding
8: Contract: IHello
9: Endpoint 3
10: Address: http://127.0.0.1/greetingservice
11: Binding: System.ServiceModel.WS2007HttpBinding
12: Contract: IGoodbye
13: Endpoint 4
14: Address: net.tcp://127.0.0.1/greetingservice
15: Binding: System.ServiceModel.NetTcpBinding
16: Contract: IHello
17: Endpoint 5
18: Address: net.tcp://127.0.0.1/greetingservice
19: Binding: System.ServiceModel.NetTcpBinding
20: Contract: IGoodbye