題外話:本以為我會WCF了,精通WCF了,畢竟剛做過一個WCF的項目,不就是寫寫契約接口,然后實現接口,改下配置。最后用控制台或者服務發布一下,不就能用了。不就是簡單ABC嗎?不是So Easy嗎?做第二個項目的時候我悲劇了,被碰的頭破血流!忽然發現什么什么都不會(第一個項目比照網上教程一步一步弄的),連寫一個簡單hello world都寫不出來。我之前還以為自己很懂了……
一、WCF文件配置
為了不重蹈覆轍,這次爭取把他整懂整透(當然這才是入門而已)。WCF很強大,它的強大跟它的配置有很大的關系,所以我首先要先把它的配置搞懂。
WCF的配置文件共分為兩部分:服務端配置與客戶端配置。兩者由於功能的不同,在配置文件的使用上也略有不同。
WCF的服務端配置
服務端的配置文件主要包括endpoint、binding、behavior的配置。一個標准的服務端配置文件所包含的主要xml配置節如下所示:
<system.ServiceModel> <services> <service> <endpoint/> </service> </services> <bindings> <!—定義一個或多個系統提供的binding元素,例如<basicHttpBinding> --> <!—也可以是自定義的binding元素,如<customBinding>. --> <binding> <!—例如<BasicHttpBinding>元素. --> </binding> </bindings> <behaviors> <!—一個或多個系統提供的behavior元素. --> <behavior> <!—例如<throttling>元素. --> </behavior> </behaviors> </system.ServiceModel>
1.1 <services>配置節
在<services>配置節中可以定義多個服務,每一個服務都被放到<service>配置節中,WCF的宿主程序可以通過配置文件找到這些定義的服務並發布這些服務。
<service>配置節包含name和behaviorConfiguration屬性。其中,name配置了實現Service Contract的類型名。類型名必須是完整地包含了命名空間和類型名。而behaviorConfiguration的配置值則與其后的<behaviors>配置節的內容有關。<endpoint>是<service>配置節的主體,其中,<endpoint>配置節包含了endpoint的三個組成部分:Address、Binding和Contract。由於具體的binding配置是在<bindings>配置節中完成,因而,在<endpoint>中配置了bindingConfiguration屬性,指向具體的binding配置。如下所示:
<services>
<service name="MyService.Service1" behaviorConfiguration="MyBehavior">
<endpoint address="" binding="netTcpBinding" bindingConfiguration="DuplexBinding" contract="MyService.IHello" />
</service>
</services>
我們也可以定義多個endpoint,例如:
<services>
<service name="Microsoft.ServiceModel.Samples.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">
<endpoint address="" binding="wsHttpBinding" contract="Microsoft.ServiceModel.Samples.ICalculator" />
<endpoint address="mex" binding="mexHttpBinding" contract=" Microsoft.ServiceModel.Samples.IMetadataExchange" />
</service>
</services>
如果address值為空,那么endpoint的地址就是默認的基地址(Base Address)。例如ICalculator服務的地址就是http://localhost/servicemodelsamples/service.svc,而IMetadataExchange服務的地址則為http://localhost/servicemodelsamples/service.svc/mex。這里所謂的基地址可以在
<service>中通過配置<host>來定義:
<service name="Microsoft.ServiceModel.Samples.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior"> <host> <baseAddresses> <add baseAddress="http://127.0.0.1/ServiceModelSamples”/> </baseAddresses> </host> <endpoint … /> </service>
1.2 <behaviors>配置節
當我們在定義一個實現了Service Contract的類時, binding和address信息是客戶端必須知道的,否則無法調用該服務。然而,如果需要指定服務在執行方面的相關特性時,就必須定義服務的behavior。在WCF中,定義behavior就可以設置服務的運行時屬性,甚至於通過自定義behavior插入一些自定義類型。例如通過指定ServiceMetadataBehavior,可以使WCF服務對外公布Metadata。配置如下:
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
<serviceBehaviors>
<behaviors>
在WCF中,behavior被定義為Attribute,其中,System.ServiceModel.ServiceBehaviorAttribute和System.ServiceModel.OperationBehaviorAttribute是最常用的behavior。雖然,behavior作為Attribute可以通過編程的方式直接施加到服務上,但出於靈活性的考慮,將behavior定義到配置文件中才是最好的設計方式。
利用ServiceBehavior與OperationBehavior可以控制服務的如下屬性:
1、 對象實例的生命周期;
2、 並發與異步處理;
3、 配置行為;
4、 事務行為;
5、 序列化行為;
6、 元數據轉換;
7、 會話的生命周期;
8、 地址過濾以及消息頭的處理;
9、 模擬(Impersonation);
例如,通過ServiceBehavior設置對象實例的生命周期:
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<instanceContextMode httpGetEnabled="true" httpGetUrl=""/>
</behavior>
<serviceBehaviors>
<behaviors>
這是通過使用配置文件指定的地址
另外我們還可以通過代碼的方式指定地址:代碼如下
private static void Main(string[] args) { using (ServiceHost serviceHost = new ServiceHost(typeof (Service1))) { serviceHost.AddServiceEndpoint(typeof (IService1), new WSHttpBinding(), "http://127.0.0.1:9999/Service1"); serviceHost.AddServiceEndpoint(typeof (IService1), new NetTcpBinding() , "net.tcp://127.0.0.1:8888/Service1"); serviceHost.Opened += (s, e) => Console.WriteLine("服務已打開!"); serviceHost.Open(); Console.Read(); } }
這里調用了ServiceHost 類的AddServiceEndpoint方法:它的抽象方法為
public ServiceEndPoint AddServiceEndpoint(Type implementedContract,Binding binding,string address);
當然你還可以使用ServiceHost 繼承自ServiceHostBase的方法AddServiceEndpoint:它的方法簽名為:
public ServiceEndPoint AddServiceEndpoint(string implementedContract,Binding binding,string address);
這里只是將implementedContract以字符串的形式表示服務契約類型的有效名稱。
上面添加終結地就變成了
serviceHost.AddServiceEndpoint(“命名空間.IService1”,new WSHttpBinding(),http://127.0.0.1:9999/Service1);
基地址與相對地址
除了向上面那樣以絕對路徑的方式指定服務的終結點地址外,還可以通過“基地址+相對地址”的方式進行設置。對於一個服務來說,可以指定一個或多個基地址,但是對於一種傳輸方式協議類型,只能具有一個唯一的基地址。服務的基地址與終結點相對地址可以通過編碼的方式,在創建ServiceHost對象時在構造函數中指定。具體實現代碼如下:
internal class Program { private static void Main(string[] args) { Uri[] baseAddress = { new Uri("http://127.0.0.1:8888/myservices"), new Uri("net.tcp://127.0.0.1/:8888") }; using (ServiceHost serviceHost = new ServiceHost(typeof (Service1),baseAddress)) { serviceHost.AddServiceEndpoint( typeof (IService1), new BasicHttpBinding(), "Service1"); serviceHost.AddServiceEndpoint( typeof (IService1), new NetTcpBinding(), "Service1"); serviceHost.Opened += (s, e) => Console.WriteLine("服務已打開!"); serviceHost.Open(); Console.Read(); } } }
上面的代碼中,在寄宿Service1服務的時候,添加了兩個基地址,一個是基於HTTP的,另外一個是基於net.tcp的。然后為Service1添加了兩個終結地,基於HTTP的BasicHttpBinding和基於TCP的NetTcpBinding。添加的兩個終結點均采用相對地址Service1。
由於AddServiceEndpoint指定的是相對地址,所以WCF會根據綁定采用的傳輸協議在ServiceHost的基地址列表中尋找與之匹配的基地址,相對地址與基地址組合確定終結點的絕對地址。(完整地址為:http://127.0.0.1:9999/myservices/Service1).
由於基地址與相對地址的匹配關系是根據綁定對象采用的傳輸協議確定的,所以對於一個確定的傳輸協議,最多只能有一個基地址。如果在上面的基地址中再加一個HTTP的基地址,那程序就會拋出異常。
如果采用上面配置文件的方式:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> <compilation debug="true" /> </system.web> <!-- 部署服務庫項目時,必須將配置文件的內容添加到 主機的 app.config 文件中。System.Configuration 不支持庫的配置文件。--> <system.serviceModel> <services> <service name="ConsoleApplication1.Service1"> <host> <baseAddresses> <add baseAddress = "http://127.0.0.1:9999/myservice" /> <add baseAddress="net.tcp://127.0.0.1:9999/myservice"/> </baseAddresses> </host> <!-- Service Endpoints --> <!-- 除非完全限定,否則地址將與上面提供的基址相關 --> <endpoint address ="Service1" binding="basicHttpBinding" contract="ConsoleApplication1.IService1" bindingConfiguration="HttpStreaming"> </endpoint> <endpoint address="Service1" binding="netTcpBinding" contract="ConsoleApplication1.IService1" bindingConfiguration="netTcpBindingConfiguration"/> <!-- Metadata Endpoints --> <!-- 元數據交換終結點供相應的服務用於向客戶端做自我介紹。 --> <!-- 此終結點不使用安全綁定,應在部署前確保其安全或將其刪除--> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior> <!-- 為避免泄漏元數據信息, 請在部署前將以下值設置為 false 並刪除上面的元數據終結點 --> <serviceMetadata httpGetEnabled="false"/> <!-- 要接收故障異常詳細信息以進行調試, 請將以下值設置為 true。在部署前設置為 false 以避免泄漏異常信息--> <serviceDebug includeExceptionDetailInFaults="False" /> <dataContractSerializer maxItemsInObjectGraph="2147483647"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <basicHttpBinding> <binding name="HttpStreaming" maxReceivedMessageSize="67108864" transferMode="Streamed"/> </basicHttpBinding> <netTcpBinding> <binding name="netTcpBindingConfiguration" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxBufferPoolSize="2147483647 " maxBufferSize="2147483647 " maxConnections="10" maxReceivedMessageSize="2147483647 "> <readerQuotas maxDepth="64" maxStringContentLength="2147483647 " maxArrayLength="2147483647 " maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" /> <security mode="Transport"> <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" /> </security> </binding> </netTcpBinding> </bindings> </system.serviceModel> </configuration>
注:如果采用代碼和配置的方式,兩者都會生效,所以必須確保兩者的設置的內容不會相互沖突。
這是服務端的配置,真的搞明白了,確實還挺有意思的。不過在調試過程中出現了錯誤
在服務“Service1”實現的協定列表中找不到協定名稱,這個問題費我半天時間,
出錯的原因有兩個:
1. 看契約是否寫對, 這個一般不會寫錯
2.看配置文件:service name="命名空間名+服務名稱" endpoint contract="命名空間名+契約名稱"
(這里有個小細節要注意, ""中不能出現空格,否則依然報錯)
我出的問題原因是第二種,命名空間名前多了空格。費了半天勁原來是自己的粗心大意,唉,真想把自己殺了……
在編寫配置中當然還出現了各種各樣無法八門的問題,都是因為配置沒有寫對的原因,這也給我一個教訓,編寫代碼一定不能粗心大意,不然都是血的代價……
下面是本文的重點了,文件的下載。
二、WCF文件的下載
其實WCF下載也沒有什么可說的,就是寫個返回Steam的接口就行了,關鍵就是寫好配置文件就行了
(待續……)