WCF配置檔案與檔案下載之坎坷路
題外話:本以為我會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();
}
}
<!-- .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } -->
這裡呼叫了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的介面就行了,關鍵就是寫好配置檔案就行了
(待續……)