在前面的文章中講述過WCF服務的宿主程序主要包括:三種,在那篇文章中,簡單的描述了如何把一個WCF服務寄宿到IIS上面,這篇文章中將具體講述如何把一個WCF服務寄宿到IIS上面。
一、新建一個WCF服務應用程序:
文件->新建->項目:選擇WCF下面的WCF服務應用程序
二、分析WcfSvcDemo項目,該項目的結構如下:
在該項目中,會默認生成一個IService1.cs的文件和Service1.svc文件。Service1.svc文件封裝的就是提供給客戶端的服務引用。
首先查看IService1.cs文件,從名字上面就可以看出這是一個接口文件,里面定義了一個接口IService1,接口上面使用了ServiceContract,意思是把這個接口聲明為服務契約,服務契約是對客戶端而言的,就是這個接口可以暴露出來讓客戶端可以看見。接口里面定義了兩個方法,每個方法上面都使用了[OperationContract],意思是把這兩個方法聲明為操作契約。只有把接口里面的方法聲明為操作契約,在客戶端里面才可以看到相應的方法,否則在客戶端里面看不到在接口里面定義的方法。
在來看Service.svc文件,可以看到下面有一個Service.svc.cs文件,這個文件里面定義了一個繼承IService1接口的類Service1,並實現了IService1接口里面的方法。
刪除Service.svc.cs文件,可以查看Service.svc文件,該文件里面就一行代碼;
1 <%@ ServiceHost Language="C#" Debug="true" Service="WcfSvcDemo.Service1" CodeBehind="Service1.svc.cs" %>
這里面有兩個重要的參數:Service和CodeBehind。Service是屬性值是WCF的服務實現類的完全限定名。CodeBehind是服務實現類所在的文件名。在運行的時候,宿主程序從svc文件中的Service屬性得到WCF服務的完全限定名,然后從配置文件中找到同名的servicce,進而找到所有的EndPoint,並根據其屬性進行實例化。
配置文件中的Service名字必須是Service類名的完全限定名(即Namespace.classname),EndPoint的Contract必須是Service接口的完全限定名。否則,程序就無法從程序集中找到相應的類進行加載。
注意:如果要修改接口實現類的名稱,必須使用“重構”的方式進行修改,因為只有利用“重構”的方式修改Servie類名的時候,.svc文件里面Service的屬性值才會被修改,利用其它方式修改類名,.svc文件里面Service的屬性值會保留原值,這樣在運行的時候,根據svc里面Service的屬性值查找不到相應的類,程序就會報錯。
svc文件里面還有一個重要的參數:ServiceHostFactory。ServiceHostFactory旨在解決從IIS或WAS中訪問自定義ServiceHost的問題。因為從ServiceHost派生的自定義宿主是動態配置的並且可能為各種類型,所以宿主環境從不會直接將其實例化。相反,WCF使用工廠模式提供宿主環境和服務的具體類型之間的間接層。除非進行通知,否則它使用返回ServiceHost的實例的ServiceHostFactory的默認實現。(在新建的svc文件中默認實現就是CodeBehind屬性的值)。但也可以通過在@ServiceHost指令中指定工廠實現的CLR類型名稱來提供自己的工廠(用於返回派生宿主)。
下面是用於返回派生的ServiceHost的自定義ServiceHostFactory:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.ServiceModel.Activation; 7 8 namespace Public.CustomService 9 { 10 public class CustomServiceHostFactory : ServiceHostFactory 11 { 12 protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) 13 { 14 CustomServiceHost customServiceHost = new CustomServiceHost(serviceType, baseAddresses); 15 return customServiceHost; 16 } 17 } 18 }
其中CustomServiceHost是自定義的繼承自ServiceHost的類,用於讀取配置文件的配置,CustomServiceHost類的定義如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.ServiceModel.Activation; 7 8 namespace Public.CustomService 9 { 10 public class CustomServiceHost :ServiceHost 11 { 12 public CustomServiceHost(Type serviceType, params Uri[] baseAddresses) 13 : base(serviceType, baseAddresses) 14 { 15 //加載Web.config的配置 16 log4net.Config.XmlConfigurator.Configure(); 17 } 18 protected override void ApplyConfiguration() 19 { 20 base.ApplyConfiguration(); 21 } 22 } 23 }
若要使用此工廠,而不使用默認工廠,則應該在@ServiceHost指令中提供相應的類型名稱:
1 <%@ ServiceHost Service="CustomSvcDemo.DatabaseService" Factory="Public.CustomService.CustomServiceHostFactory" %>
其中Service是實現類的完全限定名,Factory是自定義ServiceHostFactory的完全限定名,Public是一個dll文件。
若要使用此工廠,而不使用默認工廠,則應該在@ServiceHost指令中提供相應的類型名稱:
盡管對於從CreateServiceHost返回的ServiceHost可以執行什么操作沒有技術限制,但建議您盡可能使工廠實現簡單化。如果有大量的自定義邏輯,最好將這些邏輯放入宿主內而不是工廠內,以便可以重用它們。
應在這里提及另一個承載API的層。WCF還具有ServiceHostBase和ServiceHostFactoryBase,可從中分別派生ServiceHost和ServiceHostFactory。對於您必須通過自己的自定義創建來交換元數據系統的大型組件的更高級方案,存在上述這些特性。
下面通過兩個具體的示例程序分別實現上面描述的默認工廠和自定義工廠。
三、使用默認工廠方式
刪除新建項目時自動創建的IService1.cs和Service1.svc文件,然后添加一個svc文件,在項目上面右鍵->添加->新建項:
在新建項里面選擇web里面的WCF服務,命名為MyService:
點“添加”,除了創建MyService.svc文件以外,還會自動創建一個名為IMyService.cs的接口文件,MyService.svc.cs里面的MyService默認實現IMyService接口.
刪除IMyService接口里面自動生成的方法,添加一個GetCurrentTime的方法,用來返回當前的時間,IMyService接口定義如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7 8 namespace WcfSvcDemo 9 { 10 // 注意: 使用“重構”菜單上的“重命名”命令,可以同時更改代碼和配置文件中的接口名“IMyService”。 11 [ServiceContract] 12 public interface IMyService 13 { 14 /// <summary> 15 /// 獲取當前時間 16 /// </summary> 17 /// <returns></returns> 18 [OperationContract] 19 DateTime GetCurrentTime(); 20 } 21 }
4、MyService.svc.cs里面的MyService類實現IMyService接口,MyService類定義如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7 8 namespace WcfSvcDemo 9 { 10 // 注意: 使用“重構”菜單上的“重命名”命令,可以同時更改代碼、svc 和配置文件中的類名“MyService”。 11 // 注意: 為了啟動 WCF 測試客戶端以測試此服務,請在解決方案資源管理器中選擇 MyService.svc 或 MyService.svc.cs,然后開始調試。 12 public class MyService : IMyService 13 { 14 /// <summary> 15 /// 返回當前時間 16 /// </summary> 17 /// <returns></returns> 18 public DateTime GetCurrentTime() 19 { 20 return DateTime.Now; 21 } 22 } 23 }
5、修改配置文件,增加service、binding等節點,修改后的配置文件如下:
1 <?xml version="1.0"?> 2 <configuration> 3 <appSettings/> 4 <system.web> 5 <compilation debug="true" targetFramework="4.0"/> 6 <httpRuntime/> 7 </system.web> 8 <system.serviceModel> 9 <services> 10 <service behaviorConfiguration="metadataBehavior" name="WcfSvcDemo.MyService"> 11 <endpoint address="" binding="basicHttpBinding" bindingConfiguration="Contract" name="MyService" contract="WcfSvcDemo.IMyService"/> 12 </service> 13 </services> 14 <behaviors> 15 <serviceBehaviors> 16 <behavior name="metadataBehavior"> 17 <!-- 為避免泄漏元數據信息,請在部署前將以下值設置為 false --> 18 <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/> 19 <!-- 要接收故障異常詳細信息以進行調試,請將以下值設置為 true。在部署前設置為 false 以避免泄漏異常信息 --> 20 <serviceDebug includeExceptionDetailInFaults="false"/> 21 </behavior> 22 </serviceBehaviors> 23 </behaviors> 24 <bindings> 25 <basicHttpBinding> 26 <binding name="Contract" closeTimeout="00:00:05" openTimeout="00:00:05" receiveTimeout="11:00:00" sendTimeout="11:00:00" maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" transferMode="Buffered"> 27 <readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/> 28 </binding> 29 </basicHttpBinding> 30 </bindings> 31 <protocolMapping> 32 <add binding="basicHttpsBinding" scheme="https"/> 33 </protocolMapping> 34 <serviceHostingEnvironment aspNetCompatibilityEnabled="false" multipleSiteBindingsEnabled="true"/> 35 </system.serviceModel> 36 <system.webServer> 37 <modules runAllManagedModulesForAllRequests="true"/> 38 <!-- 39 若要在調試過程中瀏覽 Web 應用程序根目錄,請將下面的值設置為 True。 40 在部署之前將該值設置為 False 可避免泄露 Web 應用程序文件夾信息。 41 --> 42 <directoryBrowse enabled="true"/> 43 </system.webServer> 44 </configuration>
主要是修改service節點里面的name是服務實現類的完全限定名,contract是服務接口的完全限定名。
6、把WCF服務部署到IIS上面
在IIS上面網站->添加網站:
配置網站名稱、路徑、IP地址和端口:
網站配置完成以后,瀏覽.svc文件,驗證網站是否配置成功,如出現下面的截圖,說明網站配置成功:
7、創建代理類
客戶端引用WCF的時候一般是靜態引用,直接添加服務引用,這種方式如果IP地址和端口號變了,需要用代碼重新編譯然后在部署,這樣不方便。這里使用svcutil代理類的方式進行客戶端的調用。
使用svcutil生成代理類:
新建一個項目,選擇類庫項目,把剛才生成的類文件添加到類庫項目中,項目結構如下:
在類庫項目中新添加一個類,命名為:MyServiceProxy,使用這個類來調用代理類,MyServiceProxy類的定義如下:
1 using Public.ConfigBinding; 2 using System; 3 using System.Collections.Generic; 4 using System.Configuration; 5 using System.Linq; 6 using System.ServiceModel; 7 using System.Text; 8 using System.Threading.Tasks; 9 10 namespace MyProxyService 11 { 12 public class MyServiceProxy 13 { 14 private static MyServiceClient _databaseService; 15 16 private static MyServiceClient DatabaseService 17 { 18 get 19 { 20 if (_databaseService == null) 21 { 22 string sApServer1 = ConfigurationManager.AppSettings["ApServer1"]; 23 24 if (sApServer1 == null) 25 { 26 _databaseService = new MyServiceClient(); 27 } 28 else 29 { 30 EndpointAddress endPointAddr = new EndpointAddress(string.Format("{0}/MyService.svc", sApServer1)); 31 _databaseService = new MyServiceClient(HttpBinding.BasicHttpBinding, endPointAddr); 32 } 33 } 34 35 if (_databaseService.State == CommunicationState.Faulted) 36 { 37 string sApServer2 = ConfigurationManager.AppSettings["ApServer2"]; 38 39 if (sApServer2 == null) 40 { 41 _databaseService = new MyServiceClient(); 42 } 43 else 44 { 45 EndpointAddress endPointAddr = new EndpointAddress(string.Format("{0}/MyService.svc", sApServer2)); 46 _databaseService = new MyServiceClient(HttpBinding.BasicHttpBinding, endPointAddr); 47 } 48 } 49 50 return _databaseService; 51 } 52 } 53 54 /// <summary> 55 /// 返回當前時間 56 /// </summary> 57 /// <returns></returns> 58 public static DateTime GetCurrentTime() 59 { 60 return DatabaseService.GetCurrentTime(); 61 } 62 } 63 }
ApServer1和ApServer2是在配置文件中配置的IP地址和端口號,這樣如果IP地址和端口號變了,只需要修改配置文件就可以。
GetCurrentTime()方法是調用的代理類里面的方法,把該方法定義為靜態方法。
8、創建客戶端調用
在解決方案中,新建一個winform程序,界面上面只有一個button按鈕,點擊按鈕,彈出當前時間。需要添加對MyProxyService.dll文件的引用,在配置文件中增加ApServer1和ApServer2兩個節點,配置文件如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <configuration> 3 <appSettings> 4 <add key="ApServer1" value="http://127.0.0.1:8090"/> 5 <add key="ApServer2" value="http://127.0.0.1:8090"/> 6 </appSettings> 7 <startup> 8 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> 9 </startup> 10 </configuration>
button按鈕事件代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using MyProxyService; 11 12 namespace WinformClient 13 { 14 public partial class Form1 : Form 15 { 16 public Form1() 17 { 18 InitializeComponent(); 19 } 20 21 private void btn_CurrentTime_Click(object sender, EventArgs e) 22 { 23 DateTime dtNow = MyServiceProxy.GetCurrentTime(); 24 MessageBox.Show("當前時間:" + dtNow.ToString()); 25 } 26 } 27 }
點擊按鈕后,運行結果如下:
四、使用自定義工廠的方式
1、新添加一個WCF服務,命名為CustomService,把默認生成的CustomService.svc.cs文件刪掉,重新添加一個類:CustomService,該類繼承自生成的ICustomService接口,項目結構如下:
修改CustomService.svc文件:
1 <%@ ServiceHost Service="WcfSvcDemo.CustomService" Factory="Public.CustomService.CustomServiceHostFactory" %>
CustomServiceHostFactory是在另外的Public.dll里面創建的工廠類,用來返回ServiceHost,Public.dll的項目結構如下:
CustomServiceHost類繼承自ServiceHost類,代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.ServiceModel.Activation; 7 8 namespace Public.CustomService 9 { 10 public class CustomServiceHost :ServiceHost 11 { 12 public CustomServiceHost(Type serviceType, params Uri[] baseAddresses) 13 : base(serviceType, baseAddresses) 14 { 15 //加載Web.config的配置 16 log4net.Config.XmlConfigurator.Configure(); 17 } 18 protected override void ApplyConfiguration() 19 { 20 base.ApplyConfiguration(); 21 } 22 } 23 }
CustomServiceHostFactory是工廠類,繼承自ServiceHostFactory,用來返回ServiceHost,代碼如下;
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.ServiceModel.Activation; 7 8 namespace Public.CustomService 9 { 10 public class CustomServiceHostFactory : ServiceHostFactory 11 { 12 protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses) 13 { 14 CustomServiceHost customServiceHost = new CustomServiceHost(serviceType, baseAddresses); 15 return customServiceHost; 16 } 17 } 18 }
HttpBinding代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.ServiceModel; 5 using System.Text; 6 using System.Threading.Tasks; 7 using System.Xml; 8 9 namespace Public.ConfigBinding 10 { 11 public class HttpBinding 12 { 13 private static BasicHttpBinding _BasicHttpBinding; 14 15 public static BasicHttpBinding BasicHttpBinding 16 { 17 get 18 { 19 if (_BasicHttpBinding == null) 20 { 21 _BasicHttpBinding = new BasicHttpBinding(); 22 23 // 接收的訊息大小上限,默認值為65,536字節, 24 // 目前設定1k * 512,如果資料量大於這個值,請提出討論,ex:8000筆資料大概128k 25 _BasicHttpBinding.MaxReceivedMessageSize = 400 * 8192 * 512; 26 27 // 由於回傳String長度過長在反串行化時會出錯! 28 // 所以放大最大字符串長度 29 _BasicHttpBinding.ReaderQuotas.MaxStringContentLength = 8192 * 1022; 30 _BasicHttpBinding.ReaderQuotas.MaxArrayLength = 8192 * 1022; 31 _BasicHttpBinding.SendTimeout = new TimeSpan(0, 5, 0); 32 33 } 34 35 return _BasicHttpBinding; 36 } 37 } 38 } 39 }
把CustomService.svc部署到IIS上面、創建代理類的方法、客戶端調用和默認工廠里面的方法一樣,此處不在描述。
項目代碼下載路徑:https://files.cnblogs.com/files/dotnet261010/WCFStudyDemo.rar