跟我一起学WCF(6)——深入解析服务契约[下篇]
一、引言
在上一篇博文中,我们分析了如何在WCF中实现操作重载,其主要实现要点是服务端通过ServiceContract的Name属性来为操作定义一个别名来使操作名不一样,而在客户端是通过重写客户端代理类的方式来实现的。在这篇博文中将分享契约继承的实现。
二、WCF服务契约继承实现的限制
首先,介绍下WCF中传统实现契约继承的一个方式,下面通过一个简单的WCF应用来看看不做任何修改的情况下是如何实现契约继承的。我们还是按照之前的步骤来实现下这个WCF应用程序。
- 步骤一:实现WCF服务
在这里,我们定义了两个服务契约,它们之间是继承的关系的,具体的实现代码如下所示:
// 服务契约
[ServiceContract]
public interface ISimpleInstrumentation
{
[OperationContract]
string WriteEventLog();
} // 服务契约,继承于ISimpleInstrumentation这个服务契约
[ServiceContract]
public interface ICompleteInstrumentation :ISimpleInstrumentation
{
[OperationContract]
string IncreatePerformanceCounter();
}
上面定义了两个接口来作为服务契约,其中ICompleteInstrumentation继承ISimpleInstrumentation。这里需要注意的是:虽然ICompleteInstrumentation继承于ISimpleteInstrumentation,但是运用在ISimpleInstrumentation中的ServiceContractAttribute却不能被ICompleteInstrumentation继承,这是因为在它之上的AttributeUsage的Inherited属性设置为false,代表ServiceContractAttribute不能被派生接口继承。ServiceContractAttribute的具体定义如下所示:
[AttributeUsageAttribute(AttributeTargets.Class|AttributeTargets.Interface, Inherited = false,
AllowMultiple = false)]
public sealed class ServiceContractAttribute : Attribute
接下来实现对应的服务,具体的实现代码如下所示:
// 实现ISimpleInstrumentation契约
public class SimpleInstrumentationService : ISimpleInstrumentation
{
#region ISimpleInstrumentation members
public string WriteEventLog()
{
return "Simple Instrumentation Service is Called";
}
#endregion
} // 实现ICompleteInstrumentation契约
public class CompleteInstrumentationService: SimpleInstrumentationService, ICompleteInstrumentation
{
public string IncreatePerformanceCounter()
{
return "Increate Performance Counter is called";
}
}
上面中,为了代码的重用,CompleteInstrumentationService继承自SimpleInstrumentationService,这样就不需要重新定义WriteEventLog方法了。
- 步骤二:实现服务宿主
定义完成服务之后,现在就来看看服务宿主的实现,这里服务宿主是一个控制台应用程序,具体实现代码与前面几章介绍的代码差不多,具体的实现代码如下所示:
// 服务宿主的实现,把WCF服务宿主在控制台程序中
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(WCFService.CompleteInstrumentationService)))
{
host.Opened += delegate
{
Console.WriteLine("Service Started");
}; // 打开通信通道
host.Open();
Console.Read();
} }
}
宿主程序对应的配置文件信息如下所示:
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="metadataBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<!--service标签的Name属性是必须,而且必须指定为服务类型,指定格式为:命名空间.服务类名-->
<!--更多信息可以参考MSDN:http://msdn.microsoft.com/zh-cn/library/ms731303(v=vs.110).aspx-->
<service name="WCFService.CompleteInstrumentationService" behaviorConfiguration="metadataBehavior">
<endpoint address="mex" binding="mexHttpBinding" contract="WCFService.ICompleteInstrumentation" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:9003/instrumentationService/"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
- 步骤三:实现客户端
最后,就是实现我们的客户端来对服务进行访问了,这里首先以管理员权限运行宿主应用程序,即以管理员权限运行WCFServiceHostByConsoleApp.exe可执行文件。运行成功之后,你将在控制台中看到服务启动成功的消息,具体运行结果如下图所示:
然后在客户端通过添加服务引用的方式来添加服务引用,此时必须记住,一定要先运行宿主服务,这样才能在添加服务引用窗口中输入地址:http://localhost:9003/instrumentationService/ 才能获得服务的元数据信息。添加成功后,svcutil.exe工具除了会为我们生成对应的客户端代理类之前,还会自动添加配置文件信息,而且还会为我们添加System.ServiceModel.dll的引用。下面就是工具为我们生成的代码:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference.ICompleteInstrumentation")]
public interface ICompleteInstrumentation { [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISimpleInstrumentation/WriteEventLog", ReplyAction="http://tempuri.org/ISimpleInstrumentation/WriteEventLogResponse")]
string WriteEventLog(); [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISimpleInstrumentation/WriteEventLog", ReplyAction="http://tempuri.org/ISimpleInstrumentation/WriteEventLogResponse")]
System.Threading.Tasks.Task<string> WriteEventLogAsync(); [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICompleteInstrumentation/IncreatePerformanceCounter", ReplyAction="http://tempuri.org/ICompleteInstrumentation/IncreatePerformanceCounterResponse")]
string IncreatePerformanceCounter(); [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ICompleteInstrumentation/IncreatePerformanceCounter", ReplyAction="http://tempuri.org/ICompleteInstrumentation/IncreatePerformanceCounterResponse")]
System.Threading.Tasks.Task<string> IncreatePerformanceCounterAsync();
} [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public interface ICompleteInstrumentationChannel : ClientConsoleApp.ServiceReference.ICompleteInstrumentation, System.ServiceModel.IClientChannel {
} [System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
public partial class CompleteInstrumentationClient : System.ServiceModel.ClientBase<ClientConsoleApp.ServiceReference.ICompleteInstrumentation>, ClientConsoleApp.ServiceReference.ICompleteInstrumentation { public CompleteInstrumentationClient() {
} public CompleteInstrumentationClient(string endpointConfigurationName) :
base(endpointConfigurationName) {
} public CompleteInstrumentationClient(string endpointConfigurationName, string remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
} public CompleteInstrumentationClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
base(endpointConfigurationName, remoteAddress) {
} public CompleteInstrumentationClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress) {
} public string WriteEventLog() {
return base.Channel.WriteEventLog();
} public System.Threading.Tasks.Task<string> WriteEventLogAsync() {
return base.Channel.WriteEventLogAsync();
} public string IncreatePerformanceCounter() {
return base.Channel.IncreatePerformanceCounter();
} public System.Threading.Tasks.Task<string> IncreatePerformanceCounterAsync() {
return base.Channel.IncreatePerformanceCounterAsync();
}
}
在服务端,我们定义了具有继承层次结构的服务契约,并为ICompleteInstrumentation契约公开了一个EndPoint。但是在客户端,我们通过添加服务引用的方式生成的服务契约却没有了继承的关系,在上面代码标红的地方可以看出,此时客户端代理类中只定义了一个服务契约,在该服务契约定义了所有的Operation。此时客户端的实现代码如下所示:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("---Use Genergate Client by VS Tool to call method of WCF service---");
using (CompleteInstrumentationClient proxy = new CompleteInstrumentationClient())
{
Console.WriteLine(proxy.WriteEventLog());
Console.WriteLine(proxy.IncreatePerformanceCounter());
} Console.Read();
}
}
从上面代码可以看出。虽然现在我们可以通过调用CompleteInstrumentationClient代理类来完成服务的调用,但是我们希望的是,客户端代理类也具有继承关系的契约结构。
三、实现客户端的契约层级
既然,自动生成的代码不能完成我们的需要,此时我们可以通过自定义的方式来定义自己的代理类。
- 第一步就是定义客户端的Service Contract。具体的自定义代码如下所示:
namespace ClientConsoleApp
{
// 自定义服务契约,使其保持与服务端一样的继承结果
[ServiceContract]
public interface ISimpleInstrumentation
{
[OperationContract]
string WriteEventLog();
} [ServiceContract]
public interface ICompleteInstrumentation : ISimpleInstrumentation
{
[OperationContract]
string IncreatePerformanceCounter();
}
}
- 第二步:自定义两个代理类,具体的实现代码如下所示:
// 自定义代理类
public class SimpleInstrumentationClient : ClientBase<ICompleteInstrumentation>, ISimpleInstrumentation
{
#region ISimpleInstrumentation Members
public string WriteEventLog()
{
return this.Channel.WriteEventLog();
}
#endregion
} public class CompleteInstrumentationClient:SimpleInstrumentationClient, ICompleteInstrumentation
{
public string IncreatePerformanceCounter()
{
return this.Channel.IncreatePerformanceCounter();
}
}
对应的配置文件修改为如下所示:
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="MetadataExchangeHttpBinding_ICompleteInstrumentation1">
<security mode="None" />
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:9003/instrumentationService/mex"
binding="wsHttpBinding" bindingConfiguration="MetadataExchangeHttpBinding_ICompleteInstrumentation1"
contract="ClientConsoleApp.ICompleteInstrumentation" name="MetadataExchangeHttpBinding_ICompleteInstrumentation1" />
</client>
</system.serviceModel>
</configuration>
- 第三步:实现客户端来进行服务调用,此时可以通过两个自定义的代理类来分别对两个服务契约对应的操作进行调用,具体的实现代码如下所示:
class Program
{
static void Main(string[] args)
{
using (SimpleInstrumentationClient proxy1 = new SimpleInstrumentationClient())
{
Console.WriteLine(proxy1.WriteEventLog());
}
using (CompleteInstrumentationClient proxy2 = new CompleteInstrumentationClient())
{
Console.WriteLine(proxy2.IncreatePerformanceCounter());
} Console.Read();
}
}
这样,通过重写代理类的方式,客户端可以完全以面向对象的方式调用了服务契约的方法,具体的运行效果如下图所示:
另外,如果你不想定义两个代理类的话,你也可以通过下面的方式来对服务契约进行调用,具体的实现步骤为:
- 第一步:同样是实现具有继承关系的服务契约,具体的实现代码与前面一样。
// 自定义服务契约,使其保持与服务端一样的继承结果
[ServiceContract]
public interface ISimpleInstrumentation
{
[OperationContract]
string WriteEventLog();
} [ServiceContract]
public interface ICompleteInstrumentation : ISimpleInstrumentation
{
[OperationContract]
string IncreatePerformanceCounter();
}
- 第二步:配置文件修改。把客户端配置文件修改为如下所示:
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:9003/instrumentationService/mex"
binding="mexHttpBinding" contract="ClientConsoleApp.ISimpleInstrumentation"
name="ISimpleInstrumentation" />
<endpoint address="http://localhost:9003/instrumentationService/mex"
binding="mexHttpBinding" contract="ClientConsoleApp.ICompleteInstrumentation"
name="ICompleteInstrumentation" />
</client>
</system.serviceModel>
</configuration>
- 第三步:实现客户端代码。具体的实现代码如下所示:
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<ISimpleInstrumentation> simpleChannelFactory = new ChannelFactory<ISimpleInstrumentation>("ISimpleInstrumentation"))
{
ISimpleInstrumentation simpleProxy = simpleChannelFactory.CreateChannel();
using (simpleProxy as IDisposable)
{
Console.WriteLine(simpleProxy.WriteEventLog());
}
}
using (ChannelFactory<ICompleteInstrumentation> completeChannelFactor = new ChannelFactory<ICompleteInstrumentation>("ICompleteInstrumentation"))
{
ICompleteInstrumentation completeProxy = completeChannelFactor.CreateChannel();
using (completeProxy as IDisposable)
{
Console.WriteLine(completeProxy.IncreatePerformanceCounter());
}
} Console.Read();
}
}
其实,上面的实现代码原理与定义两个客户端代理类是一样的,只是此时把代理类放在客户端调用代码中实现。通过上面代码可以看出,要进行通信,主要要创建与服务端的通信信道,即Channel,上面的是直接通过ChannelFactory<T>的CreateChannel方法来创建通信信道,而通过定义代理类的方式是通过ClientBase<T>的Channel属性来获得当前通信信道,其在ClientBase类本身的实现也是通过ChannelFactory.CreateChannel方法来创建信道的,再把这个创建的信道赋值给Channel属性,以供外面进行获取创建的信道。所以说这两种实现方式的原理都是一样的,并且通过自动生成的代理类也是一样的原理。
四、总结
到这里,本篇文章分享的内容就结束了,本文主要通过自定义代理类的方式来对契约继承服务的调用。其实现思路与上一篇操作重载的实现思路是一样的,既然客户端自动生成的代码类不能满足需求,那就只能自定义来扩展了。到此,服务契约的分享也就告一段落了,后面的一篇博文继续分享WCF中数据契约。
本人所有源码下载:WCFServiceContract2.zip。
跟我一起学WCF(6)——深入解析服务契约[下篇]的更多相关文章
- 跟我一起学WCF(5)——深入解析服务契约[上篇]
一.引言 在上一篇博文中,我们创建了一个简单WCF应用程序,在其中介绍到WCF最重要的概念又是终结点,而终结点又是由ABC组成的.对于Address地址也就是告诉客户端WCF服务所在的位置,而Cont ...
- 跟我一起学WCF(13)——WCF系列总结
引言 WCF是微软为了实现SOA的框架,它是对微乳之前多种分布式技术的继承和扩展,这些技术包括Enterprise Service..NET Remoting.XML Web Service.MSMQ ...
- 跟我一起学WCF(11)——WCF中队列服务详解
一.引言 在前面的WCF服务中,它都要求服务与客户端两端都必须启动并且运行,从而实现彼此间的交互.然而,还有相当多的情况希望一个面向服务的应用中拥有离线交互的能力.WCF通过服务队列的方法来支持客户端 ...
- WCF中配置文件解析
WCF中配置文件解析[1] 2014-06-14 WCF中配置文件解析 参考 WCF中配置文件解析 返回 在WCF Service Configuration Editor的使用中,我们通过配置工具自 ...
- [老老实实学WCF] 第十篇 消息通信模式(下) 双工
老老实实学WCF 第十篇 消息通信模式(下) 双工 在前一篇的学习中,我们了解了单向和请求/应答这两种消息通信模式.我们知道可以通过配置操作协定的IsOneWay属性来改变模式.在这一篇中我们来研究双 ...
- [老老实实学WCF] 第九篇 消息通信模式(上) 请求应答与单向
老老实实学WCF 第九篇 消息通信模式(上) 请求应答与单向 通过前两篇的学习,我们了解了服务模型的一些特性如会话和实例化,今天我们来进一步学习服务模型的另一个重要特性:消息通信模式. WCF的服务端 ...
- [老老实实学WCF] 第七篇 会话
老老实实学WCF 第七篇 会话 通过前几篇的学习,我们已经掌握了WCF的最基本的编程模型,我们已经可以写出完整的通信了.从这篇开始我们要深入地了解这个模型的高级特性,这些特性用来保证我们的程序运行的高 ...
- [老老实实学WCF] 第八篇 实例化
老老实实学WCF 第八篇 实例化 通过上一篇的学习,我们简单地了解了会话,我们知道服务端和客户端之间可以建立会话连接,也可以建立非会话连接,通信的绑定和服务协定的 ServiceContract 的S ...
- [老老实实学WCF] 第六篇 元数据交换
老老实实学WCF 第六篇 元数据交换 通过前两篇的学习,我们了解了WCF通信的一些基本原理,我们知道,WCF服务端和客户端通过共享元数据(包括服务协定.服务器终结点信息)在两个 终结点上建立通道从而进 ...
随机推荐
- Mybatis保存数据时事务问题
今天不小心在sqlplus中用for update ,然后事务没提交,结果在项目中一直保存不进去数据,找了很久发现是sqlplus中的事务没提交,哎,这种问题真得避免啊,一定要细心啊!
- shell_基础知识
参考: http://blog.csdn.net/kaizi318/article/details/9343551 开头程序必须以下面的行开始(必须方在文件的第一行):#!/bin/sh符号#!用来告 ...
- An attempt was made to load a program with an incorrect format
用.net调用一个C++ 32位的DLL, 编译的时候选择x86, 在部署到一个64位的机器上的时候报错:"An attempt was made to load a program w ...
- Spring HtmlUtils把HTML编码转义,可将HTML标签互相转义
Spring HtmlUtils把HTML编码转义,可将HTML标签互相转义 2014年09月05日 ⁄ 综合 ⁄ 共 372字 ⁄ 字号 小 中 大 ⁄ 评论关闭 org.springframe ...
- 理解GC日志
每一种收集器的日志形式都是由它们自身的实现所决定的,换而言之,每个收集器的日志格式都可以不一样.但虚拟机设计者为了方便用户阅读,将各个收集器的日志都维持一定的共性,例如以下两段典型的GC日志: 33. ...
- HTTP-崔希凡笔记
HTTP协议(重点) 协议:协议的甲乙双方,就是客户端(浏览器)和服务器! 理解成双方通信的格式! l 请求协议: l 响应协议: 1 安装HttpWatch HttpWatch是专门为IE浏览器 ...
- Revenge of Nim hdu 4994 (博弈)
http://acm.split.hdu.edu.cn/showproblem.php?pid=4994 题意:现在有两个人在取石子,共有n堆石子,每堆石子取完后才可以取下一堆石子,最后一个取石子的人 ...
- Maven学习之 插件plugin
Maven本质上是一个执行插件的框架.插件共分两类:build插件和reporting插件. build插件,会在build阶段被执行,应该配置在POM的<build/>元素中. repo ...
- mybatis generate 自动生成 entity dao 和 xml 文件
其中的一种方式 ,使用maven 插件 <build> <plugins> <plugin> <groupId>org.mybatis.generato ...
- vb.net下载代码
'后台 Partial Public Class Download2 Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender ...