代码改变世界

Chapter 1.3:WCF实践 HelloWorld

2011-01-10 19:10  田志良  阅读(2298)  评论(2编辑  收藏  举报

1. 源码下载

下载地址:https://files.cnblogs.com/tianzhiliang/WCF.Chapter1.HelloWorld.rar

 

2. 契约

 

using System;
using System.ServiceModel;

namespace WCF.Chapter1.HelloWorld.Host
{
    [ServiceContract]
    public interface Contract
    {
        [OperationContract]
        string HelloWorld();
    }
}

 

  ServiceContract 特性可以将一个 CLR 接口(或者通过推断获得的接口)映射为与技术无关的服务契约。ServiceContract 特性公开了 CLR 接口(或者类)作为 WCF 契约。WCF 契约与类型的访问限定无关,因为类型的访问限定属于 CLR 的概念。即使将 ServiceContract 特性应用在内部(Internal)接口上,该接口同样会公开为公有服务契约,以便于跨越服务边界实现服务的调用。如果接口没有标记 ServiceContract 特性,WCF 客户端则无法访问它(即使接口是公有的)。这一特点遵循了面向服务的一个原则,即明确的服务边界。为满足这一原则,所有契约必须明确要求:只有接口(或者类)可以被标记为 ServiceContract 特性,从而被定义为WCF 服务,其他类型都不允许。即使应用了ServiceContract特性,类型的所有成员也不一定就是契约中的一部分。我们必须使用OperationContractAttribute特性显式地标明哪些方法需要暴露为WCF契约中的一部分。

  可以为契约定义命名空间。契约的命名空间具有与 .NET 编程相同的目的:确定契约的类型范围,以降低类型的冲突几率。可以使用 ServiceContract类型的 Namespace 属性设置命名空间:

 

[ServiceContract(Namespace = "MyNamespace")]
interface IMyContract
{...}

 

  若非特别指定,契约的默认命名空间为 http ://tempuri.org。对外服务的命名空间通常使用公司的 URL;至于企业网(Intranet)内部服务的命名空间,则可以定义有意义的唯一名称,例如 MyApplication。

  在默认情况下,契约公开的名称就是接口名。但是也可以使用 ServiceContract 特性的 Name 属性为契约定义别名,从而在客户端的元数据(Metadata)中公开不同的名称:

 

[ServiceContract(Name = "IMyContract")]
interface IMyOtherContract
{...}

 

  相似的,操作公开的名称默认为方法名,但我们同样可以使用 OperationContract 特性的 Name 属性设置别名,从而公开不同的操作名:

 

[ServiceContract]
interface IMyContract
{
[OperationContract(Name = "SomeOperation")]
void MyMethod(string text);
}

 

  WCF只允许将OperationContract特性应用到方法上,而不允许应用到同样属于CLR概念的属性、索引器和事件上。WCF 只能识别作为逻辑功能的操作(Operation)。通过应用 OperationContract 特性,可以将契约方法暴露为逻辑操作,使其成为服务契约的一部分。接口(或类)中的其他方法如果没有应用 OperationContract 特性,则与契约无关。这有利于确保明确的服务边界,为操作自身维护一个明确参与(Opt-In )的模型。此外,契约操作不能使用引用对象作为参数,只允许使用基本类型或数据契约。

 

3. 服务

 

using System;

namespace WCF.Chapter1.HelloWorld.Host
{
    class Service : Contract
    {
        public string HelloWorld()
        {
            return "Hello World !";
        }
    }
}

 

  服务类通过继承和实现多个标记了 ServiceContract 特性的接口,可以支持多个契约。服务类有一些实现上的约束,WCF 只能使用默认构造函数,要避免使用带参构造函数。同样,虽然类可以使用内部(internal )的属性、 索引器以及静态成员,但 WCF 客户端却无法访问它们。

 

4. 宿主

 

using System;
using System.ServiceModel;

namespace WCF.Chapter1.HelloWorld.Host
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(Service)))
            {
                host.Opened += delegate
                {
                    Console.WriteLine("服务已启动,请按ENTER键终止!");
                };
                host.Open();
                Console.ReadLine();
            }

            Console.WriteLine("服务已关闭!");
            Console.ReadLine();
        }
    }
}

 

  WCF服务类不能凭空存在。每个 WCF服务都必须托管(Hosting) 在 Windows 进程中,该进程被称为宿主进程(Host Process)。单个宿主进程可以托管多个服务,而相同的服务类型也能够托管在多个宿主进程中。WCF 没有要求宿主进程是否同时又是客户端进程。显然,一个独立的进程有利于错误与安全的隔离。谁提供进程或是提供何种类型的进程并不重要。宿主可以由 IIS 提 供,也 可以由 Window s Vista 的 Windows 激活服务(Windows Activation Service,WAS)提供,或者开发者直接将它作为应用程序的一部分。

  创建 ServiceHost 对象时,需要为 ServiceHost 的构造函数提供服务类型,至于默认的基地址则是可选的。可以将基地址集合设置为空。如果提供了多个基地址,也可以将服务配置为使用不同的基地址。ServiceHost 拥有基地址集合可以使得服务能够接收来自于多个地址和协议的调用,同时只需要使用相对的 URI。注意,每个 SeriviceHost 实例都与特定的服务类型相关,如果宿主进程需要运行多个服务类型,则必须创建与之匹配的多个 ServiceHost 实例。在宿主程序中, 通过调用 Open() 方法,可以允许调用传入;通过调用 Close() 方法终结宿主实例,完成进程中的调用。此时, 即使宿主进程还在运行,仍然会拒绝客户端的调用。而在通常情况下,执行关闭操作会停止宿主进程。

  打开宿主时,将装载 WCF 运行时(WCF runtime),启动工作线程监控传入的请求。由于引入了工作线程,因此可以在打开宿主之后执行阻塞(blocking )操作。通过显式控制宿主的打开与关闭,提供了 IIS 托管难以实现的特征,即能够创建定制的应用程序控制模块,管理者可以随意地打开和关闭宿主,而不用每次停止宿主的运行。

  ServiceHost实现的 ICommunicationObject 接口定义了一些高级特性。如果打开或关闭宿主的操作耗时较长, 可以采用异步方式调用 BeginOpen() 和 BeginClose() 方法。我们可以订阅诸如状态改变或错误发生等宿主事件, 通过调用 State 属性查询当前的宿主状态。ServiceHost 类同样实现了 Abort( )方法。该方法提供强行退出功能,能够及时中断进程中的所有服务调用,然后关闭宿主。此时,活动的客户端会获得一个异常。

 

5. 服务配置

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WCF.Chapter1.HelloWorld.Host.Service">
        <endpoint address="http://localhost:8000/HelloWorld" binding="wsHttpBinding" contract="WCF.Chapter1.HelloWorld.Host.Contract">
        </endpoint>
      </service>
    </services>
  </system.serviceModel>
</configuration>

 

  注意:service节点的name属性为服务类的type名,endpoint节点的contract属性为契约类的type名。

  配置既可以用管理方式配置,也可以用编程方式配置。管理配置方式允许开发者在部署服务之后,修改服务与客户端的主要特性,而不需要重新编译或重新部署。主要缺陷则是不具备类型安全,只有在运行时才能发现配置的错误。如果配置的决策完全是动态的,那么编程配置方式就体现了它的价值,它可以在运行时基于当前的输入或条件对服务的配置进行处理。如果判断条件是静态的,而且是恒定不变的,就可以采取硬编码方式。例如,如果我们只关注于进程内托管的调用,就可以采取硬编码方式,使用 NetNamePipeBinding 以及它的配置。不过,大体而言,大多数客户端和服务都会使用配置文件。

 

6. 客户端代理

 

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace WCF.Chapter1.HelloWorld.Client
{
    public class Proxy : ClientBase<Contract>, Contract
    {
        public Proxy()
        { }

        public Proxy(string configurationName) :
            base(configurationName)
        { }

        public string HelloWorld()
        {
            return base.Channel.HelloWorld();
        }
    }
}

 

  若要调用服务的操作 ,客户端首先需要导入服务契约到客户端的本地描 述(NativeRepresentation)中。如果客户端使用了 WCF,调用操作的常见做法是使用代理。代理是一个 CLR 类,它公开了一个单独的 CLR 接口用以表示服务契约。注意,如果服务支持多个契约(至少是多个终结点),客户端则需要一个代理对应每个契约类型。代理不仅提供了与服务契约相同的操作,同时还包括管理代理对象生命周期的方法,以及管理服务连接的方法。代理完全封装了服务的每个方面:服务的位置、实现技术、运行时平台以及通信传输。

  代理类的闪光之处在于它可以只包含服务公开的契约,而不需要添加对服务实现类的引用。我们可以通过提供地址和绑定的客户端配置文件使用代理,也可以不通过配置文件直接使用。注意,每个代理的实例都确切地指向了一个终结点。创建代理时需要与终结点交互。

  若要使用代理,客户端首先需要实例化代理对象,并为构造函数提供终结点信息,即配置文件中的终结点节名。如果没有使用配置文件,则为终结点地址和绑定对象。 然后,客户端使用代理类的方法调用服务。一旦客户端调用完毕, 就会关闭代理实例 。

  如果在客户端配置文件中,只为代理正在使用的契约类型定义了一个终结点,则客户端可以省略构造函数中的终结点名:

 

MyContractClient proxy = new MyContractClient();
proxy.MyMethod();
proxy.Close();

 

  使用代理的 Dispose()方法可以关闭代理。这种方式的优势在于它支持 using 语句的使用,即使出现异常,仍然能够调用:

 

using(MyContractClient proxy = new MyContractClient())
{
    proxy.MyMethod();
}

 

  WCF客户端的每次调用都必须在配置的超时值内完成。无论何种原因,一旦调用时间超出该时限,调用就会被取消,客户端会收到一个 TimeoutException 异常。绑定的一个属性用于设定超时的确切值,默认的超时值为1min。若要设置不同的超时值,可以设置Binding 抽象基类的 SendTimeout 属性:

 

<client>
<endpoint
...
binding = "wsHttpBinding"
bindingConfiguration = "LongTimeout"
...
/>
</client>
<bindings>
<wsHttpBinding>
<binding name = "LongTimeout" sendTimeout = "00:05:00"/>
</wsHttpBinding>
</bindings>