契约与元数据
- WCF 技术与其他分布式技术最显著的区别是耦合性是基于契约的,不是基于代码的耦合,因为在契约中不包含具体的逻辑,所以为我们构建松耦合的分布式应用程序提供了良好的基础。
- Web Service Description Language(WSDL)
Web Service 的描述语言 WSDL 里面包含了下面 5 个内容,这 5 个内容是我们在进行数据通信的时候必需的内容:- 互操作契约(Contract)
- 描述服务与相应的 Endpoint 端点
- Binding 绑定
- 消息和类型的定义
- 策略
在进行消息传递的时候,要应用哪些具体的策略,都是由 WSDL 来进行描述的。
- 契约与元数据用于生成客户端的代理和配置
服务描述和元数据
-
生成服务描述
图 1
如图 1,在 ServiceHost 里面运行着服务以及相应的端点,ServiceHost 里面又多了一个东西是 ServiceDescription 服务描述,也就是 Service 具体的描述,由这个 ServiceDescription 将 Service 和 Endpoint 以及相关的 Address、Binding、Contract 信息在这里进行了相应的记录,外面是有一个 Config 文件,当 ServiceHost 启动的时候来加载这个 Config 文件,根据 Config 文件里面具体的内容来生成相应的描述信息。
-
生成 WSDL
图 2
如图 2,由 ServiceDescription 服务描述来生成 WSDL 的文件,通过 svcutil.exe 这个工具来生成相应的在客户端所使用的这个 Proxy 代理以及相应的代码,同样在客户端我们可以通过 Config 配置文件的方式来影响 Proxy 代理以及 Channel 通道的生成。
-
元数据的交换
图 3
完成了图 2 阶段后,我们可以通过 svcutil.exe 这个工具来实现从 Service 服务这一端到 Client 客户这一端的数据交换,整个的服务描述与数据的交互就是图 3 的结构,从这个结构图我们可以看到,Client 客户端在获取 Service 服务具体的信息的时候,实际上不会直接与 Service 或者 Endpoint 来进行接触,它是通过 ServiceDescription 服务描述来获得的并且通过元数据交互这种方式来得到相应的信息,然后构建客户端自己的 Proxy 代理。所以,如果我们的 Service 或者 Endpoint 内部的一些逻辑发生变化的话,只要它不影响 ServiceDescription 服务描述,就不会对客户端的 proxy 代理产生影响,也就不会对客户端产生影响。通过这种方式实现了 Client 端和 ServiceHost 端的松耦合关系。
通过配置文件方式实现基于 WCF 服务的 HelloWorld 实例
服务相应的代码除了可以通过编码的方式实现的,还可以通过向导来生成。以下操作基于《3. 通过编码方式实现基于 WCF 服务的 HelloWorld 实例》一文中的项目代码。
1. 服务端配置 App.config
在 Host 工程右击 Add -> New Item选择 Application Configuration File,新建一个 App.config 配置文件。
配置文件解释:
- Service的配置 (<service/>节点)
- 在<services/>里我们添加了一个<service/>,这个service的名称就是我们前面实现的服务的名称,格式
name="HelloWorldService.HelloWorldService"
,就是我们的命名空间+具体实现服务的名称。behaviorConfiguration="serviceBehavior"
,表示它服务的行为配置采用 serviceBehavior 这个策略 - 我们看到暴露出了两个端点出来,这两个端点都是绑定Http协议,但是还是有区别的。上面的端点采用基本的 Http 进行绑定,基于
basicHttpBinding
的端点它所绑定的契约就是 Service 中定义的HelloWorldService.IHelloWorldService
这个契约。下面的端点使用mexHttp 进行绑定,这个mex是WCF里用于元数据交互的一种特有方式,在我们进行元数据交换的时候,指定了由WCF内建的一个接口IMetadataExchange
,这个接口的主要作用就是来完成元数据的交换,并且在后面指定了这个端点具体的地址 "mex"。 - 在<endpoint/>这个节点描述完毕以后,然后就是<Host/>节点,这个service 它所运行在的 Host,在 Host 里面主要描述了一个基本的地址就是"http://localhost:8000",所以对于 HelloWorldService 这个服务真实的地址是 "http://localhost:8000/HelloWorldService",那么一样元数据交换的地址是"http://localhost:8000/mex"
- 在<services/>里我们添加了一个<service/>,这个service的名称就是我们前面实现的服务的名称,格式
- Service 行为配置(<behaviors/>节点)
在<behaviors/>里我们可以指定服务具体的行为,<serviceMetadata httpGetEnabled="true"/>
是说服务的元数据是否能用Http协议Get出来,也就是说当我们应用了这个配置选项以后,元数据我们是允许Get方法操作的
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="HelloWorldService.HelloWorldService" behaviorConfiguration="serviceBehavior">
<endpoint address="HelloWorldService" binding="basicHttpBinding" contract="HelloWorldService.IHelloWorldService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
2.通过元数据生成代理和配置文件
2.1 保存 Host 这端的配置文件,启动 Host:
注意:HTTP 无法注册 URL http://+:8000/。进程不具有此命名空间的访问权限(有关详细信息,请参见 http://go.microsoft.com/fwlink/?LinkId=70353)。
如按照例子一开始就遇到这样的问题,原因可能是Win 7,解决方法是Visual Studio 以管理员的身份运行就可以了。
- 由于刚才我们在配置文件中已经指明了使用Http协议这么一个服务以及8000它相应的端口,因此在浏览器我们可以通过输入 http://localhost:8000/ 来看到和这个服务相关的具体内容如图 1 。我们可以通过其中提供的 svcutil.exe 工具,单机 http://localhost:8000/?WSDL 看到具体的描述信息在客户端生成相应的Proxy代码,对个这个WSDL配置文件当中,我们可以看到我们指定的一些信息,像我们使用什么样的协议进行传递等这些信息都在WSDL里面进行了非常详细的描述。
-
如图 2,我们已在Host这一端添加了相应的配置文件使得Host不但能够对外提供基于 Tcp 协议位置在localhost端口在9000下的服务。同时在上回通过编码实现中也提供了基于 Http 协议位置在 localhost 端口在8000下的服务。那么我们对比这两个服务的话,通过写代码所作的这些事情事实上我们可以通过配置文件的方式来完成,这也就意味着我们在 Host 这一端希望对外提供一个服务的时候其实对于这个服务而言是不需要编写任何的代码,我们需要做的是在配置文件当中来完成对这个服务的配置。
图 2:WSDL
2.2 客户端添加服务引用,自动生成配置文件与代码
在 Client 工程上右击 Add Service Reference,在 Address 栏输入已经开启的服务地址 http://localhost:8000/,点击 Go ,将命名空间改为 Proxy,点击 OK 就添加成功了。
上述步骤提供了基于Http协议消息的 Host,由Visual Studio会根据WSDL里面的内容来自动生成相应的代理文件以及相应的配置文件。Project -> Show All Files可以看到如下目录。在Reference.svcmap下面有个Reference.cs,在这个Reference.cs里面有由系统自动为我们生成的代理类,在我们进行使用的时候,我们直接使用代理类来进行服务的操作。
image.png
在 Client 端生成了和 Host 端相关的的相应的配置文件以及里面的配置信息和配置参数,对于这些参数可以根据我们具体需求来进行具体的配置如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IHelloWorldService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
<message clientCredentialType="UserName" algorithmSuite="Default"/>
</security>
</binding>
</basicHttpBinding>
<!--<netTcpBinding>
<binding name="NetTcpBinding_IHelloWorldService" />
</netTcpBinding>-->
</bindings>
<client>
<endpoint address="http://localhost:8000/HelloWorldService" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IHelloWorldService"
contract="Proxy.IHelloWorldService" name="BasicHttpBinding_IHelloWorldService" />
<!--<endpoint address="net.tcp://localhost:9000/HelloWorldService"
binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IHelloWorldService"
contract="Proxy.IHelloWorldService" name="NetTcpBinding_IHelloWorldService">
</endpoint>-->
</client>
</system.serviceModel>
</configuration>
2.3 客户端调用服务
由于说Client端系统已经为我们生成好了和这个IHelloWorldService契约相关的 Proxy 代码了,因此我们自己手动的代码就不需要了,然后对于 Proxy 的使用我们就不需要对通过 CreateChannel 这种方式来进行调用,我们可以直接 Proxy.HelloWorldServiceClient proxy = new Proxy.HelloWorldServiceClient();
,通过实例化对象的方式来实例化一个新的Proxyl对象出来就可以了,那我们实例化出来的这个 proxy 这个对象就自动包含了契约相关的信息,在 Proxy 生成以后我可以调用相应的方法来完成相应的任务,这样就实现了基于Http协议的分布式调用。在 WCF 当中无论是我们使用基于http协议的消息的传递还是使用基于 Tcp 协议的消息传递都是遵循同一个模型来开发的,如果说我们开发了一个复杂系统在原来是使用 Tcp 协议来进行数据传递的,然而在实际部署过程中要改变成Http协议,那么我们可以通过配置的方式修改一些参数就能够项目从使用Tcp协议移植到使用 Http 协议上面。
网友评论