WCF 開發實戰系列 (一)

  • 16098
  • 0
  • WCF
  • 2016-06-25

也許有讀者會說,為什麼要談 WCF?近期不是 Web API 比較熱門?在這個手機、Devices 當道的現在,究竟有什麼地方會需要使用到 WCF 呢?

的確,現在訪間你能找到的課程,或者您可能參與某專案的實作 (網頁應用程式、手機 APP 也好),你需要的通常也都是提供 Services 層,而這個 Services 層通常也就是 Web API。那麼,難道 WCF 就無用武之地?其實也不是,看什麼情況用什麼技術,有一些地方是 Web API 無法做到的

前言

也許有讀者會說,為什麼要談 WCF?近期不是 Web API 比較熱門?在這個手機、Devices 當道的現在,究竟有什麼地方會需要使用到 WCF 呢?

的確,現在訪間你能找到的課程,或者您可能參與某專案的實作 (網頁應用程式、手機 APP 也好),你需要的通常也都是提供 Services 層,而這個 Services 層通常也就是 Web API。那麼,難道 WCF 就無用武之地?其實也不是,看什麼情況用什麼技術,有一些地方是 Web API 無法做到的,比如:

當你需要開發的是企業內部分散式系統軟體元件,而且還需要跨機器做交易,甚至你想要在服務端模擬類似軟體 Load Balance 的效果 (讓你設定訊息的 Filter 方式),這在 WCF 裡有 Routing Services 可以透過內建的 Message Filter 可以做到類似的效果,又或者您要跟 MSMQ 之間輕易的整合,這些都是 Web API 所做不到的。

或者可以這樣說,對外走輕量 JSON 的 Services 大部分以 Web API 為主,對內的系統較無平寬限制,剛好是 WCF 可以發揮的領域。

 

大綱

一、WCF 程式設計物件模型

二、建立與裝載 WCF 的幾種方式

三、建立服務合約與資料合約

四、如何維護 WCF 資料狀態與 支援分散式的交易?

五、如何在 WCF 中實作訊息加密、驗證、授權?

六、如何設計支援 MSMQ 的 WCF 應用程式?

七、如何設計良好效能的 WCF 服務?

 

下面我們開始今天要為大家談的內容,首先

一、WCF 程式設計物件模型

因為 WCF 也是 .NET 應用程式一種,要了解 WCF 的物件模型前,我們得先了解 .NET CLR 的模型與.NET應用程式定義域。

圖(一)、WCF 的 Hosting 的應用程式模型

雖然.NET 應用程式也是以 Win32 Process Model 中,但古子裡大不相同!一個處理序 Process 可以有多個 Application Domain ,一個 Application Domain 又可以有多個 ServiceHost,如上圖,應用程式定義域是 .NET CLR 用來隔離 Managed 程式碼與 Windows 的方法,處理序與應用程式定義域之間的關係,很像應用程式及應用程式定義域與 WCF ServiceHost 之間的關係,而且每個應用程式定義域可以裝載零個或多個 WCF ServiceHost 執行個體

 

註:另外WCF 要求必須在 Windows 處理序內至少裝載一個應用程式定義域

 

The ABC of WCF

我們可以透過下面這張圖,快速地來了解 WCF

圖(二)、The ABC of WCF

下面我們再來看一張圖,這一張圖會說明 WCF 中從 Messaging 層到 Service Runtime 的服務推疊。

圖(三)、WCF 的服務堆疊

由圖中可以看出 Service Runtime 可以可程式化 Metadata Behavior、Error Behavior 與 Transaction Behavior 等,Application 端需要透過 Contract 與 Service Runtime 溝通,Messaging 層則管理關於訊息的權限 (驗證/授權) & 可延展性 & 自訂通道 (Channels)、通道 Channels 傳遞的訊息支援加密 (text, binary, MTOT, XML) 與通道支援通訊協定 (TCP, HTTP)等等。

最下面的 Activation and Hosting 則是 WCF 可以 Hosting 的種類。

而使用 WCF 進行通訊時,您必須選定一種 Binding ,在圖(二)中,我們知道 Binding 是用來是用來辨識服務通訊的傳輸,格式以及通訊協定,下面列表為 WCF 4.0 所支援的 Binding 種類,(並未全部列出)

圖(四)、WCF 的 Binding 種類

二、建立與裝載 WCF 的幾種方式

一般來說,建立與裝載 WCF 有下面的幾種方式:

  • Windows Forms 應用程式

不管使用 Windows Form、Console Application、Win32 Services 都可使用自主掛載方式

自主掛載範例程式如下:

Uri baseAddress = new Uri(string.Format("http://localhost:{0}/ScheduleJobService", TcpPort)); 
ServiceHost WcfHost = new ServiceHost(typeof(ScheduleJobWcfService), baseAddress); 
ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); 
smb.HttpGetEnabled = true; 
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15; 
WcfHost.Description.Behaviors.Add(smb); 
//啟動 WCF 服務 
WcfHost.Open();
  • Console Application 主控台應用程式

Console 掛載方式與上面 Windows Form 相同

 

  • Windows Service 服務應用程式

掛載方式與上面 Windows Form 相同,而自主掛載 (ServiceHost) [優點]如下:

  1. 易於使用:只需幾行程式碼就能讓您的服務正常執行
  2. 具有彈性:您可以透過 ServiceHost<T> 的 Open() 和 Close()
  3. 易於偵錯:對裝載於自主裝載環境的 WCF 服務進行偵錯,可讓您以熟悉的方式偵錯,而不必附掛在別的應用程式中來啟動服務
  4. 易於部署:一般而言,部署簡單的 Windows 應用程式,就跟使用 xcopy 一樣容易。您不需要類似伺服陣列上的任何複雜部署,即可將簡單的 Windows 應用程式部署為 WCF ServiceHost
  5. 支援所有的繫結和傳輸:自主裝載完全不會侷限在現成的繫結和傳輸,在 Windows Server 2003 上的 IIS 6.0 則限制只能使用 HTTP,Server 2008/2012 只要繫結 net.tcp 協定即可

 

但自主掛載 (ServiceHost) 也不是沒有缺點:

  1. 有限的可用性:只有當應用程式正在執行時,才能連上服務
  2. 有限的功能:自主裝載的應用程式對於高可用性、便利管理能力、強固性、恢復能力、版本控制和部署案例,僅提供有限的支援。別的不說,至少預設配置下的 WCF 就不提供這些支援,所以在自主裝載的 ServiceHost,您必須自行實作這些功能;比如拿 IIS 來說,在 AppPool 裡便提供許多進階功能

 

  • Internet Information Services (IIS) 的 ASP.NET 應用程式

通常,只需使用傳統 ASP.NET Web 導向的服務 HTTP 可滿足需求,在 IIS 上裝載 WCF 服務時,WCF 服務可以享用 ASP.NET 應用程式集區 AppPool 的所有功能。

圖(五)、IIS 管理員

  • 在 IIS 7.0 以上提供的 WAS 內部的 WCF 服務

這裡要注意的是,Windows Server 2008/IIS 7.0 以上不需要再安裝 WSE 直接支援 WS-* 安全性規格,像是 WS-Security、WS-Trust 和 WS-SecureConversation,WAS (Windows Process Activation Service) 它讓IIS可以支援非 HTTP, HTTPS 的協定的服務載體。

圖(六)、開啟關閉Windows功能

圖(七)、IIS 7 以上安裝 HTTP 啟用後支援TCP

所謂的 WS-* 規格

所謂的 WS-* 規格是為了 Web Service 的安全性與互通性所發展而來的規格定義,像其中 WS-Security 規格定義了 Web Service 如何在安全方式下溝通、WS-Transactions 指定了如何在跨不同的 Web Service 的集合中實作交易,以及 WS-RelibleMessaging 描述在分散式應用系統環境中,如有網路斷線、元件失效的情況下卻能確保訊息傳遞是有效的 (微軟應用情境如:MSMQ)。

為確保 Web Service 的互通性,應建立符合這些規格的 Web Service 。

 

三、建立服務合約與資料合約

Service Contract:

所謂的 Service Contract 服務合約是用來定義服務中可以使用的 operation,描述每個 operation 內的參數、回傳的型別,程式開發人員可以使用這些資訊來建立用戶端應用程式,用戶端程式開發人員則可使用 svcutil.exe 公用程式從服務的 WSDL 描述檔來產生 proxy 類別,再使用這個 proxy 類別跟服務端通訊

Service Contract 應不依賴發送訊息所使用的通訊機制,但 SOAP 訊息依賴 Service Contract ,若 Service Contract 變更,用戶端也必須更新為新的版本,否則傳遞的訊息可能讓服務端看不懂或是無法正確處裡訊息。

 

Data Contract:

定義服務端與用戶端傳遞的資料格式、型別,但各平台可能都有些複雜的型別,像是類別、結構 (Structure)、列舉型別 (Enumeration) 等,需要由服務端來指定用戶端該如何封裝與傳遞指定的格式到服務器中,Data Contract 會包含在用戶端以 svcutil.exe 公用程式產生的 proxy 中。

 

Service Contract 服務端的版本控制

不要認為變更服務是個簡單的工作,而且盡可能減少用戶端的變更,並花一點時間在相容用戶端現有程式、檢查用戶端的功能無誤。

並且了解什麼是破壞性變更,一般來說當服務端有破壞性變更時,避免在這個時候回傳詳細錯誤訊息,因為惡意攻擊者可以透過它查探進一步的資訊。

C# 的 overloading 無法在 SOAP 標準中使用

[OperationContract(Name = “GetListProdcts”, …)]

List<ProductDTO> GetProducts();

 

何謂 Service Contract 的破壞性變更

底下列一張表格給各位參考:

Data Contract 相容性

在相容性部分,如果您需要維持原有用戶端應用程式相容性,DataMember 屬性提供兩種可用的屬性:

1. IsRequired:

如果設定為 True,服務所接受的 SOAP 訊息必須要在 DataMember 中包含其值,這個屬性預設值為 False,所以預設會為每個遺漏的欄位產生預設值

2. EmitDefaultValue:

若設為 True,如果欄位直沒有包含在用戶端傳送過來的 SOAP 訊息裡,用戶端的 WCF Runtime 會為這個遺漏的欄位產生預設值。該欄位預設值為True

 

重新產生 proxy 類別,更新用戶端應用程式

這有幾種方式,

  • 從工具 Visual Studio 中重新產生

圖(八)、從 VS 中產生 Proxy 類別

  • 使用 svcutil.exe 來產生

1. 從執行中服務或線上中繼資料文件產生用戶端程式碼

svcutil http://service/metadataEndpoint

2. 從執行中服務下載中繼資料文件

svcutil /t:metadata http://service/metadataEndpoint

3. 針對服務合約以及組件中關聯的類型產生中繼資料文件

svcutil myAssembly.dll

4. 從本機中繼資料文件產生用戶端程式碼

svcutil *.wsdl *.xsd /language:C#

 

四、如何維護 WCF 資料狀態與 支援分散式的交易?

我們先來談談 WCF 的狀態維護部分,因為這與能否交易有絕對關係,WCF Runtime 提供了 3 種執行個體內容模式:

  • InstanceContextMode.PerSession

PerSession 的狀態從 Client 任意呼叫一個 operation 之後開始,直到 Client 的 proxy 結束後結束,類似工作階段概念,每一個 Client 在Server上都有獨立、互不互相干擾的執行個體

  • InstanceContextMode.PerCall

如同 ASP.NET WebForm & MVC Controller 一樣是by request建立執行個體,用完即丟,不保留任何狀態 (Stateless)

  • InstanceContextMode.Single

與 SingleCall 相同,但要注意的是,它是單一執行緒,除非您設定為 ConcurrencyMode.Mutiple 模式,否則 Client 端的下一個 operation 的呼叫必須等待上一個 operation 完成才會執行。

 

另外,WCF 的 Session的定義是從 Client 端 new WcfProxyClient() 開始,一直到它在前端被.Close()結束或 Dispose() 被回收為止

且要啟用Session需選用

  • Ws*Binding
  • NetTcpBinding
  • NetNamedPipeBinding
註:BasicHttpBinding 不支援

 

還有要注意,即使您將 ConcurrencyMode 設為 Single,他的確會限制執行個體一次以一個執行緒執行服務,但最好是設定 MaxConcurrentCalls 為 1,以確保沒有次序不對的問題,可是當 ConcurrencyMode 為 Reentrant 時,對外部呼叫使用 Begin/End 非同步呼叫模式會觸發例外狀況,非同步傳出呼叫需要 ConcurrencyMode 為 Multiple ,而在該情況下,您必須處理同步處理問題。

一般來說,如果違反其並行模式的執行個體有訊息到達,該訊息會等到執行個體可供使用為止,或等到它逾時為止。

常見的作法,使用 lock 關鍵字以確保只有一個執行緒進入,如下圖:

圖(九)、使用 lock 關鍵字以確保只有一個執行緒進入

如何實作支援交易的 WCF 應用程式?

一般來說,官方建議使用 TransactionScope 來實作交易,您可以在 operation 上宣告 OperationBehavior 屬性中的 TransactionScopeRequired 屬性為True來初始化交易,然後 operation 可以使用 CommitableTransaction 來啟動交易或是由用戶端來啟用交易,不過在 WCF 的用戶端應用程式中,建議使用 TransactionScope物件來建立交易。

以及您必須使用 PerSession 執行個體內容模式,因為WCF必須在Operation與呼叫者間維護交易狀態,除非將 TransactionAutoComplete 設為 true,另外也不支援在交易的 WCF 使用單向 (one-way) 的 Operation

 

WCF 在 ServiceBehaviorAttribute 中提供額外的兩個屬性讓您設定:

  • ReleaseServiceInstanceOnTransacationComplete:(預設為True)

By default 服務在每個交易結束之後會強制回收服務執行個體,如果用戶端呼叫其他的 operation,它必須產生新的執行個體來服務交易

  • TransactionAutoCompleteOnSessionClose:(預設為False)

當工作階段完成時,WCF Runtime 會自動完成目前的交易

 

接著,透過下面範例來看如何撰寫支援交易的 WCF Service

圖(十)、範例程式碼

如果您需要做的異質平台間的交易,WCF 口已透過使用 WS-Atomic Transaction (WS-AT) 協定來實作支援異質(heterogeneous)環境、分散式交易。

首先

1. 您必須啟用WS-AT 組態公用程式來設定 (WS-AT) 支援

圖(十一)、啟用 WS-Atomic Transaction (WS-AT)

注意:這裡必須以 32位元來執行MMC

 

2. 設定兩部電腦間的信任關係

WS-AT 通訊協定服務需要系統管理員明確授權個別帳戶,才能參與分散式交易

步驟如下:

  • 使用 makecert.exe 公用程式建立暫時性憑證

makecert -n “CN=RootCaClientTest" -r -sv RootCaClientTest.pvk RootCaClientTest.cer

  • 將這些憑證安裝到適當的憑證存放區,再使用 wsatConfig.exe 工具將每部電腦的憑證新增至授權之參與者憑證清單中

 

五、如何在 WCF 中實作訊息加密、驗證、授權?

這裡要跟大家探討如何保護企業 WCF Service?這裡會有幾個安全性議題需要討論,這裡先談使用者驗證

所謂的驗證:

  • 維護用戶端應用程式和服務間通訊的機密性 (Confidentiality)
  • 舉例來說,有心人士可以利用軟體或硬體的TCP/UDP封包分析器以攔截可能包含私人金融卡帳密或是機密的個人資訊
  • 防止被竄改或有錯誤的訊息
  • 就算有訊息保密還是難防有心人士攔截後再送往目的地時破壞信息內容,因此您可以利用雜湊(hash)或是XML Signature簽章來偵測訊息是否遭到竄改
  • 確認訊息能夠正確的傳送
  • 即使訊息沒有竄改也可能遭攔截重複發送,可以使用時間註記是否超出合理時間限制,如超過就丟棄它
  • 防止假冒的服務
  • 如釣魚網站。防範方式可以使用憑證 (雙向驗證)

 

常見的驗證方式如下:

常見的驗證方式

  • 使用 Windows 驗證 (AD)
  • 或者透過 Kerberos 協定來識別使用者,但只能在 Windows 網域(Domain)中使用
  • 自訂驗證方式、Form 驗證、Cookie 驗證等等
  • 使用 SQL Membership Provider 進行驗證
  • 使用憑證
  • 使用憑證驗證用戶身分
  • SSL 雙向驗證 (Trust)

 

還有,也可以透過實作訊息的安全性來加密傳輸的資料,WCF 也有內建的演算法 SecurityAlgorithmSuite 可以做這件事。

常見的方式如:

  • 使用 SSL (Secure Socket Layer) 加密傳輸資料
  • 透過CA取得公鑰(Public)、私鑰(Private)
  • 使用演算法(如:SecurityAlgorithmSuite)加密訊息內容,WCF 具備三種常見安全性模式:
    1. Message:系統會使用 SOAP 訊息安全性來提供安全性
    2. Transport:安全性可使用安全性傳輸 (例如,HTTPS) 來提供
    3. TransportWithMessageCredential:傳輸、訊息與「使用訊息認證進行傳輸」

 

注意:不是所有繫結都支援都支援上面三種模式

 

使用 Windows 驗證 (AD)

圖(十二)

在服務端的 config 加入如下:

圖(十三) 

使用如下程式碼:

圖(十四)

自訂驗證方式、Form 驗證、Cookie 驗證

圖(十五)、自訂驗證的 Cookie 票卷做法

接著撰寫 Login 驗證的方法,檢核帳密無誤後,產生Cookie 票券

圖(十六)

使用 SQL Membership Provider 進行驗證

在 web.config 或是 app.config 上按滑鼠右鍵,選擇使用『編輯 WCF 組態』,然後點開『進階』底下的『服務行為』,在『(空白名稱)』上面再點滑鼠右鍵,選擇『新增服務行為延伸』,如下圖所示。

圖(十七)、新增服務行為延伸

圖(十八)、新增行為元素延伸區段

然後設定驗證模式為 『UseAspNetRoles』,在設定角色提供者為『AspNetSqlRoleProvider』

圖(十九)、設定 serviceAuthorization 的 PrincipalPermissionMode

圖(二十)、設定 serviceCredentials 的 MemberShip 提供者 與 使用的驗證憑證模式

再將 roleManager 設定為 enabled

圖(二十一)

記得最後要設好連線字串

圖(二十二)

使用憑證驗證用戶身分

如何在 WCF 中使用憑證來驗證身分呢?

1. 可使用 Makecert.exe 建立暫時性憑證

Makecert –sr CurrentUser –ss My –n CN=Alice –sky exchange

2. 設定 Binding 的 SecurityMode 為 Message

圖(二十三)

3. 設定 serviceBehaviors 的 behavior 的 serviceCredentials 底下的 authentication 屬性

圖(二十四)

4. 撰寫如下用戶端程式即可呼叫

圖(二十五)

六、如何設計支援 MSMQ 的 WCF 應用程式?

在設計 MSMQ 的 WCF 應用程式之前,我們先來了解什麼是 MSMQ?

何謂 MSMQ

圖(二十六)

MSMQ 是 (Microsoft Message Queue)的縮寫,它是 Windows 的隨附選用元件,可透過新增 Windows 功能公安裝,如下圖。

圖(二十七)

MSMQ 佇列管理員會實作可靠訊息傳輸通訊協定,使訊息不會在傳輸期間因為網路瞬斷遺失,且 MSMQ 中,佇列可以為交易式或非交易式,WCF netMsmqBinding 提供與佇列繫結以便與 MSMQ 進行通訊。

 

MSMQ 的適用情境

  • 任務性的傳輸,強調可靠性,比如金流服務、電子商務等等
  • 非同步傳輸,如:嵌入式和手持式裝置等應用
  • 強調系統間鬆散的耦合,有了彼此溝通的方式(佇列),使的各系統升級變得簡單
  • 網路急劇不穩定的傳輸需求,MSMQ 可在網路可通訊時才傳輸,在可靠傳輸的通訊下,可確保訊息正確送達,也不會因為網路瞬斷而丟失訊息

由於 WCF 本身有合約會指定要交換的項目,而且繫結中會指定用來交換訊息 (或「如何交換」) 的機制 (Binding),所以一般來說,WCF 非常適合拿來設計佇列 MSMQ 的應用程式。

在 WCF 中使用 netMsmqBinding 佇列繫結有以下幾點必須注意:

  • 所有服務作業必須為單向,因為 WCF 中的預設佇列繫結不支援使用佇列的雙工通訊
  • 使用佇列繫結,使用至少需要使用 WCF 內建的 netMsmqBinding 設定繫結,而且要設定訊息佇列 (MSMQ)
  • 為了讓用戶端方便使用,服務上最好有額外的 HTTP 端點 (mex),讓中繼資料可以直接經過查詢取得相關 proxy 資訊

 

圖(二十八)、設計支援 MSMQ 的 WCF 應用程式

測試結果

圖(二十九)

七、如何設計良好效能的 WCF 服務?

WCF 中提供了一種服務結流 (throttling) 方式來控制資源的使用,以免服務被過度的消費,要設計出良好效能的 WCF 應用程式之前,我們先來了解什麼是服務節流 (throttling)?

服務節流概論

服務節流(throttling) 是用來來控制資源的使用,當ChannelDispatcher物件在找尋合適的operation的時候,還可以檢視目前的需求是否超過負荷,若超過會先放置在內建的 Queue 中。在 ChannelDispatcher 有一個物件叫做 ServiceThrottle 可以設定是否要將需求封鎖或是放到佇列中,除非有特別設定,ServiceThrottle 屬性預設值為 null。

 

ServiceThrottle 提供三個整數數值的屬性設定:

1. MaxConcurrentInstances

這屬性表示服務允許並行的服務執行個體的最大數量

2. MaxConcurrentCalls

表示同時間能夠處裡 operation/Calls 的最大數量,當你執行個體模式是在PerSession或是PerCall都會受此限制,另外PerSession還會受下面MaxConcurrentSessions屬性所限制

3. MaxConcurrentSessions

這個屬性表示服務端同時間維護的Session的最大個數,Session會隨著用戶端應用程式終止而結束,但某些用戶端如果長時間運行,可能會導致其他用戶端遭到封鎖

 

而在傳遞 Binary 時,使用 MTMO (Message Transmission Mechanism Optimization) 會有較好的效能。

圖(三十)、使用 MTMO (Message Transmission Mechanism Optimization)

注意:MTOM 是用來取代先前 DIME (Direct Internet Message Encapsulation Protocol),因此不要將 MIME (Multiple Internet Mail Extension) 與 DIME 搞混了

 

實作:設定服務節流 (ServiceThrottle)

圖(三十一)、新增服務型為項目延伸

圖(三十二)、新增 serviceThrottling 項目延伸

圖(三十三)、設定 serviceThrottling 項目延伸屬性

 

結語

以上為第一篇 WCF 實戰部分,下一篇,筆者將帶著大家了解 WCF Routing Services 的世界,了解如何透過 WCF Routing Services 的 Messaging Filter 實作出類似軟體 Load Balance 的效果,並使用 Step by Step 方式,帶大家實作。

謝謝大家


 

簽名:

學習是一趟奇妙的旅程

這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。

軟體開發之路(FB 社團)https://www.facebook.com/groups/361804473860062/

Gelis 程式設計訓練營(粉絲團)https://www.facebook.com/gelis.dev.learning/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^