WCF技术剖析(卷1)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.2.2 服务调用对绑定的指定

通过前面章节的介绍,我们知道了WCF服务调用具有两种典型的方式:一种是借助代码生成工具(比如SvcUtil.exe)导入元数据生成客户端代码和配置,添加服务引用采用的便是这种方式。代码生成工具最终会创建一个继承自ClientBase<TChannel>的服务代理类型。另一种则是通过ChannelFactory直接创建服务代理对象进行服务调用。

为ClientBase<TChannel>指定绑定

无论是直接借助于SvcUtil.exe,还是添加服务引用的方式,生成的核心类是继承自System.ServiceModel.ClientBase<TChannel>的子类,TChannel为服务契约类型。

        namespace Artech.WcfServices.Contracts
        {
            [ServiceContract(NameSpace="http://www.artech.com/")]
            public interface ICalculator
            {
                [OperationContract]
                double Add(double x, double y);
            }
        }

针对上面一个服务契约,将会生成3个类型:ICalculator、ICalculatorChannel、CalculateClient。ICalculator可以看成是客户端的等效服务契约(Equavilent ServiceContract);ICalculatorChannel继承了System.ServiceModel.IClientChannel,IClientChannel定义了客户端信道的基本行为;CalculateClient是最终用于服务访问的服务代理类,该类继承了泛型基类ClientBase<ICalculator>,并实现了服务契约。CalculateClient的定义如下。

        namespace Artech.WcfServices.Clients.ServiceReference {
            //其他类型
            public partial class CalculateClient : System.ServiceModel.
              ClientBase<Artech.WcfServices.Clients.ServiceReference.ICalculator>,
              Artech.WcfServices.Clients.ServiceReference.ICalculator {
                public CalculateClient() {
                }
                public CalculateClient(string endpointConfigurationName) :
                        base(endpointConfigurationName) {
                }
                public CalculateClient(string endpointConfigurationName, string
                  remoteAddress) :
                        base(endpointConfigurationName, remoteAddress) {
                }
                public CalculateClient(string endpointConfigurationName,
                  System.ServiceModel.EndpointAddress remoteAddress) :
                        base(endpointConfigurationName, remoteAddress) {
                }
                public CalculateClient(System.ServiceModel.Channels.Binding binding,
                  System.ServiceModel.EndpointAddress remoteAddress) :
                        base(binding, remoteAddress) {
                }
                public double Add(double x, double y) {
                      return base.Channel.Add(x, y);
                }
            }
        }

在生成的CalculateClient类中定义了5个构造函数重载,它们都是通过调用基类相应的构造函数实现的。绑定对象可以通过其中一个构造函数指定:

        public CalculateClient(System.ServiceModel.Channels.Binding binding,
          System.ServiceModel.EndpointAddress remoteAddress) :
              base(binding, remoteAddress) {
        }

下面的代码片断简单演示了通过创建CalculateClient实现对CalculatorService的调用,在构造该对象的时候显式指定绑定对象和终结点地址:

        namespace Artech.WcfServices.Clients
        {
            class Program
            {
                static void Main(string[] args)
                {
                      EndpointAddress address = new EndpointAddress
                        ("http://127.0.0.1:6666/CalculatorService");
                      Binding binding = new BasicHttpBinding();
                      double result;
                      using (CalculateClient calculator = new CalculateClient(binding,
                        address))
                      {
                          result = calculator.Add(1, 2);
            //其他操作
                      }
                }
            }
        }

对于生成的服务代理类的5个重载构造函数,只有唯一一个能够显式地指定绑定对象,如果使用其他的构造函数来创建服务代理对象,使用的是通过配置方式指定的绑定。服务代理类通过endpointConfigurationName引用定义配置文件中的某个终结点,并采用该终结点的绑定。在客户端的配置较服务端更加简洁,所有的终结点列表位于<client>配置节中,在具体的<endpoint>配置中通过binding属性指定绑定类型。

        <?xml version="1.0" encoding="utf-8" ?>
        <configuration>
            <system.serviceModel>
                <client>
                    <endpoint name="CalculatorService_wsHttpBinding" address=
                      "http://127.0.0.1:8888/CalculatorService" binding=
                      "wsHttpBinding" bindingConfiguration="" contract="Artech.
                        WcfServices.Contracts.ICalculator">
                    </endpoint>
                    <endpoint name="CalculatorService_netTcpBinding" address=
                      "net.tcp://127.0.0.1:9999/CalculatorService" binding=
                      "netTcpBinding" bindingConfiguration="" contract="Artech.
                        WcfServices.Contracts.ICalculator">
                    </endpoint>
                </client>
            </system.serviceModel>
        </configuration>

和服务端一样,依然可以通过配置的方式定义需要的绑定。所有定制的绑定位于<bindings>配置节中。如下面的配置所示,名称为calculateservice的终结点采用了定制的BasichHttpBinding,我们修改了BasicHttpBinding默认的Timeout时间。

        <?xml version="1.0" encoding="utf-8" ?>
        <configuration>
            <system.serviceModel>
                <bindings>
                      <basicHttpBinding>
                          <binding name="BasicHttpBinding_ICalculator" closeTimeout=
                            "00:01:00" openTimeout="00:02:00" receiveTimeout="00:05:00"
                            sendTimeout="00:02:00" />
                      </basicHttpBinding>
                </bindings>
                <client>
                      <endpoint address="http://127.0.0.1:9999/CalculatorService"
                        binding="basicHttpBinding" bindingConfiguration=
                        "BasicHttpBinding_ICalculator" contract="Artech.WcfServices.
                        Contracts.ICalculator" name="CalculatorService" />
                </client>
            </system.serviceModel>
        </configuration>

如果服务调用的终结点完全通过配置进行设置的话,客户端代码就可以进一步得到简化。在下面的客户端代码中,当创建CalculateClient对象时,直接指定终结点配置名称就可以了。

        using System.ServiceModel;
        using System.ServiceModel.Channels;
        namespace Artech.WcfServices.Clients
        {
            class Program
            {
                static void Main(string[] args)
                {
                      double result;
                      using (CalculateClient calculator = new CalculateClient
                        ("CalculatorService"))
                      {
                          result = calculator.Add(1, 2);
            ......
                      }
                }
            }
        }

为ChannelFactory<TChannel>指定绑定

从远程调用的角度来理解WCF的客户端程序,其目的在于创建一个代理对象实现远程调用;从消息交换的角度来讲,WCF客户端程序的目的在于创建一个信道栈用于向服务端发送消息和接收回复。所以我们需要的就是通过一个对象来创建这样的服务代理或是信道栈,从编程模型的角度来看,这个对象就是System.ServiceModel.ChannelFactory <TChannel>。实际上,System.ServiceModel.ClientBase<TChannel>在内部也是通过ChannelFactory<TChannel>来创建这个TChannel的。

通过ChannelFactory<TChannel>,可以自由地指定终结点的三要素(地址、绑定和契约,其中泛型类型即代表契约类型)。下面是ChannelFactory<TChannel>的主要方法定义。

        public class ChannelFactory<TChannel> : ChannelFactory,
        IChannelFactory<TChannel>, IChannelFactory, ICommunicationObject
        {
            public ChannelFactory();
            public ChannelFactory(Binding binding);
            public ChannelFactory(ServiceEndpoint endpoint);
            public ChannelFactory(string endpointConfigurationName);
            public ChannelFactory(Binding binding, EndpointAddress remoteAddress);
            public ChannelFactory(Binding binding, string remoteAddress);
            public ChannelFactory(string endpointConfigurationName, EndpointAddress
              remoteAddress);
            public TChannel CreateChannel();
            public TChannel CreateChannel(EndpointAddress address);
            public virtual TChannel CreateChannel(EndpointAddress address, Uri via);
            //其他成员
        }

由于ChannelFactory<TChannel>的目的是通过一个终结点对象创建用于服务调用的代理,所以定义一系列的构造函数显式地指定一个确定终结点对象或是构成终结点的两个要素,地址和绑定(另一个要素已经通过泛型类型确定下来)。在没有显式指定地址或绑定的情况下,通过endpointConfigurationName参数引用定义在配置文件中的终结点,采用配置的地址或绑定。当ChannelFactory<TChannel>被成功创建时,就可以通过它来创建实现了服务企业约定的服务代理对象,并由它进行服务的调用。下面的代码中 ,借助ChannelFactory<TChannel>,完全使用代码的方式实现了服务的调用。

        namespace Artech.WcfServices.Clients
        {
            class Program
            {
                static void Main(string[] args)
                {
                      double result;
                      EndpointAddress  address = new EndpointAddress
                        ("http://127.0.0.1:9999/CalculatorService");
                      Binding binding = new BasicHttpBinding();
                      ContractDescription contract = ContractDescription. GetContract
                        (typeof(ICalculator));
                      ServiceEndpoint endpoint = new ServiceEndpoint(contract,
                        binding,address);
                      using (ChannelFactory<ICalculator> channelFactory = new
                        ChannelFactory<ICalculator>(endpoint))
                      {
                          ICalculator calculator = channelFactory.CreateChannel();
                          using (calculator as IDisposable)
                          {
                              result = calculator.Add(1, 2);
                      ......
                          }
                      }
                }
            }
        }

如果采用配置文件中配置的终结点信息,将会使代码得到极大的简化。如果使用下面的配置,上面那么一大段代码就会得到极大的简化,在创建ChannelFactory<TChannel>时只须要指定终结点配置名称(CalculatorService)就可以了。

        <?xml version="1.0" encoding="utf-8" ?>
        <configuration>
            <system.serviceModel>
                <client>
                      <endpoint address="http://127.0.0.1:9999/CalculatorService"
                        binding="basicHttpBinding" contract="Artech.WcfServices.
                        Contracts.ICalculator" name="CalculatorService" />
                </client>
            </system.serviceModel>
        </configuration>
        namespace Artech.WcfServices.Clients
        {
            class Program
            {
                static void Main(string[] args)
                {
                      double result;
                      using (ChannelFactory<ICalculator> channelFactory = new
                        ChannelFactory<ICalculator>(“CalculatorService”))
                      {
                          ICalculator calculator = channelFactory.CreateChannel();
                          using (calculator as IDisposable)
                          {
                              result = calculator.Add(1, 2);
                              //......
                        }
                      }
                }
            }
        }

ChannelFactory<TChannel>类型除了提供实例方法CreateChannel之外,还提供一个静态的同名方法,借助于该静态方法,你无须再创建ChannelFactory<TChannel>对象就可以即席创建服务代理对象。

        public class ChannelFactory<TChannel> : ChannelFactory,
          IChannelFactory<TChannel>, IChannelFactory, ICommunicationObject
        {
            public static TChannel CreateChannel(Binding binding, EndpointAddress
              endpointAddress, Uri via);
            //... ...
        }