跟我一起学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不能被派生接口继承。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方法了。

  • 步骤二:实现服务宿主

   定义完成服务之后,现在就来看看服务宿主的实现,这里服务宿主是一个控制台应用程序,具体实现代码与前面几章介绍的代码差不多,具体的实现代码如下所示:

 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可执行文件。运行成功之后,你将在控制台中看到服务启动成功的消息,具体运行结果如下图所示:

  然后在客户端通过添加服务引用的方式来添加服务引用,此时必须记住,一定要先运行宿主服务,这样才能在添加服务引用窗口中输入地址: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     }

  这样,通过重写代理类的方式,客户端可以完全以面向对象的方式调用了服务契约的方法,具体的运行效果如下图所示:

  另外,如果你不想定义两个代理类的话,你也可以通过下面的方式来对服务契约进行调用,具体的实现步骤为:

  • 第一步:同样是实现具有继承关系的服务契约,具体的实现代码与前面一样。
 // 自定义服务契约,使其保持与服务端一样的继承结果
    [ServiceContract]
    public interface ISimpleInstrumentation
    {
        [OperationContract]
        string WriteEventLog();
    }

    [ServiceContract]
    public interface ICompleteInstrumentation : ISimpleInstrumentation
    {
        [OperationContract]
        string IncreatePerformanceCounter();
    }
View Code
  • 第二步:配置文件修改。把客户端配置文件修改为如下所示:
<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

 

posted @ 2014-10-23 21:08  Learning hard  阅读(...)  评论(...编辑  收藏