# 跟我一起学WCF(6)——深入解析服务契约[下篇]
## 一、引言
在上一篇博文中,我们分析了如何在WCF中实现操作重载,其主要实现要点是服务端通过ServiceContract的Name属性来为操作定义一个别名来使操作名不一样,而在客户端是通过重写客户端代理类的方式来实现的。在这篇博文中将分享契约继承的实现。
## 二、WCF服务契约继承实现的限制
首先,介绍下WCF中传统实现契约继承的一个方式,下面通过一个简单的WCF应用来看看不做任何修改的情况下是如何实现契约继承的。我们还是按照之前的步骤来实现下这个WCF应用程序。
* **步骤一:实现WCF服务**
在这里,我们定义了两个服务契约,它们之间是继承的关系的,具体的实现代码如下所示:
```
1 // 服务契约
2 [ServiceContract]
3 public interface ISimpleInstrumentation
4 {
5 [OperationContract]
6 string WriteEventLog();
7 }
8
9 // 服务契约,继承于ISimpleInstrumentation这个服务契约
10 [ServiceContract]
11 public interface ICompleteInstrumentation :ISimpleInstrumentation
12 {
13 [OperationContract]
14 string IncreatePerformanceCounter();
15 }
```
上面定义了两个接口来作为服务契约,其中ICompleteInstrumentation继承ISimpleInstrumentation。这里需要注意的是:虽然ICompleteInstrumentation继承于ISimpleteInstrumentation,但是运用在ISimpleInstrumentation中的ServiceContractAttribute却不能被ICompleteInstrumentation继承,这是因为在它之上的AttributeUsage的Inherited属性设置为false,代表[ServiceContractAttribute](http://msdn.microsoft.com/zh-cn/library/system.servicemodel.servicecontractattribute(v=vs.110).aspx)不能被派生接口继承。ServiceContractAttribute的具体定义如下所示:
接下来实现对应的服务,具体的实现代码如下所示:
```
// 实现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方法了。
* **步骤二:实现服务宿主**
定义完成服务之后,现在就来看看服务宿主的实现,这里服务宿主是一个控制台应用程序,具体实现代码与前面几章介绍的代码差不多,具体的实现代码如下所示:
```
1 // 服务宿主的实现,把WCF服务宿主在控制台程序中
2 class Program
3 {
4 static void Main(string[] args)
5 {
6 using (ServiceHost host = new ServiceHost(typeof(WCFService.CompleteInstrumentationService)))
7 {
8 host.Opened += delegate
9 {
10 Console.WriteLine("Service Started");
11 };
12
13 // 打开通信通道
14 host.Open();
15 Console.Read();
16 }
17
18 }
19 }
```
宿主程序对应的配置文件信息如下所示:
```
<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可执行文件。运行成功之后,你将在控制台中看到服务启动成功的消息,具体运行结果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4865888.png)
然后在客户端通过添加服务引用的方式来添加服务引用,此时必须记住,一定要先运行宿主服务,这样才能在添加服务引用窗口中输入地址: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。此时客户端的实现代码如下所示:
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Console.WriteLine("---Use Genergate Client by VS Tool to call method of WCF service---");
6 using (CompleteInstrumentationClient proxy = new CompleteInstrumentationClient())
7 {
8 Console.WriteLine(proxy.WriteEventLog());
9 Console.WriteLine(proxy.IncreatePerformanceCounter());
10 }
11
12 Console.Read();
13 }
14 }
```
从上面代码可以看出。虽然现在我们可以通过调用CompleteInstrumentationClient代理类来完成服务的调用,但是我们希望的是,客户端代理类也具有继承关系的契约结构。
## 三、实现客户端的契约层级
既然,自动生成的代码不能完成我们的需要,此时我们可以通过自定义的方式来定义自己的代理类。
* 第一步就是定义客户端的Service Contract。具体的自定义代码如下所示:
```
1 namespace ClientConsoleApp
2 {
3 // 自定义服务契约,使其保持与服务端一样的继承结果
4 [ServiceContract]
5 public interface ISimpleInstrumentation
6 {
7 [OperationContract]
8 string WriteEventLog();
9 }
10
11 [ServiceContract]
12 public interface ICompleteInstrumentation : ISimpleInstrumentation
13 {
14 [OperationContract]
15 string IncreatePerformanceCounter();
16 }
17 }
```
* 第二步:自定义两个代理类,具体的实现代码如下所示:
```
// 自定义代理类
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>
```
* 第三步:实现客户端来进行服务调用,此时可以通过两个自定义的代理类来分别对两个服务契约对应的操作进行调用,具体的实现代码如下所示:
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 using (SimpleInstrumentationClient proxy1 = new SimpleInstrumentationClient())
6 {
7 Console.WriteLine(proxy1.WriteEventLog());
8 }
9 using (CompleteInstrumentationClient proxy2 = new CompleteInstrumentationClient())
10 {
11 Console.WriteLine(proxy2.IncreatePerformanceCounter());
12 }
13
14 Console.Read();
15 }
16 }
```
这样,通过重写代理类的方式,客户端可以完全以面向对象的方式调用了服务契约的方法,具体的运行效果如下图所示:
![](https://box.kancloud.cn/2016-01-23_56a2eb4874d4e.png)
另外,如果你不想定义两个代理类的话,你也可以通过下面的方式来对服务契约进行调用,具体的实现步骤为:
* 第一步:同样是实现具有继承关系的服务契约,具体的实现代码与前面一样。
```
// 自定义服务契约,使其保持与服务端一样的继承结果
[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>
```
* 第三步:实现客户端代码。具体的实现代码如下所示:
```
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 using (ChannelFactory<ISimpleInstrumentation> simpleChannelFactory = new ChannelFactory<ISimpleInstrumentation>("ISimpleInstrumentation"))
6 {
7 ISimpleInstrumentation simpleProxy = simpleChannelFactory.CreateChannel();
8 using (simpleProxy as IDisposable)
9 {
10 Console.WriteLine(simpleProxy.WriteEventLog());
11 }
12 }
13 using (ChannelFactory<ICompleteInstrumentation> completeChannelFactor = new ChannelFactory<ICompleteInstrumentation>("ICompleteInstrumentation"))
14 {
15 ICompleteInstrumentation completeProxy = completeChannelFactor.CreateChannel();
16 using (completeProxy as IDisposable)
17 {
18 Console.WriteLine(completeProxy.IncreatePerformanceCounter());
19 }
20 }
21
22 Console.Read();
23 }
24 }
```
其实,上面的实现代码原理与定义两个客户端代理类是一样的,只是此时把代理类放在客户端调用代码中实现。通过上面代码可以看出,要进行通信,主要要创建与服务端的通信信道,即Channel,上面的是直接通过ChannelFactory<T>的CreateChannel方法来创建通信信道,而通过定义代理类的方式是通过ClientBase<T>的Channel属性来获得当前通信信道,其在ClientBase类本身的实现也是通过ChannelFactory.CreateChannel方法来创建信道的,再把这个创建的信道赋值给Channel属性,以供外面进行获取创建的信道。所以说这两种实现方式的原理都是一样的,并且通过自动生成的代理类也是一样的原理。
## 四、总结
到这里,本篇文章分享的内容就结束了,本文主要通过自定义代理类的方式来对契约继承服务的调用。其实现思路与上一篇操作重载的实现思路是一样的,既然客户端自动生成的代码类不能满足需求,那就只能自定义来扩展了。到此,服务契约的分享也就告一段落了,后面的一篇博文继续分享WCF中数据契约。
本人所有源码下载:[WCFServiceContract2.zip](http://files.cnblogs.com/zhili/WCFServiceContract2.zip)。
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结