一、引言
在上一篇博文中,我們分析了如何在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(); }
- 第二步:配置文件修改。把客戶端配置文件修改為如下所示:
<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。