征服使用 WebClient 呼叫 WCF後的下個目標,自然是學會用 PowerShell 呼叫 WCF,繼續精進野外求生技能。

歷經這段時間的 PowerShell 實戰練習,我得到重要心得:.NET 開發者想用 PowerShell 處理未知情境有捷徑 - 先寫出 C# 程式達成目標再將程式邏輯轉成 PowerShell。大多數 C# 語法在 PowerShell 都有對映寫法(例如 Lambda),且 PowerShell 可直接建立及操作 .NET 物件,要載入第三方程式庫也不是問題,但有些眉角要實際動手才會知道,例如這次改寫我就學到一些新東西。

第一個是 Here-String,相當 C# 的 String Liberal(逐字字串常值),就是能包含換行的 @"..." 字串表示法。Here-String 的格式為:

$x = @"<Enter換行>
內容文字可以有多行
不需要避開 " 或 '
內嵌 HTML 或 XML 很方便
<rows>
    <row attr="123"></row>
</rows>
結尾處換行再接雙引號跟 @<Enter換行>
"@

第二點是「PowerShell 不能直接使用 LINQ 方法」。LINQ 基於 IEnumerable<T> 泛型,依賴編譯期強型別概念。因此 "A,B,C".Split(',').First() 在 C# 可行,在 PowerShell 則會以不認得 .First() 收場。不過不打緊,PowerShell 裡有 ForEach-ObjectWhere-ObjectSelect-Object 可以實現與 LINQ 相似的操作。

經過一番琢磨,我把前一篇的 C# 程式轉成 PowerShell 版:

Add-Type -AssemblyName System.Xml.Linq
Add-Type -AssemblyName System.Linq
$ErrorActionPreference = "Stop"
$xml = @"
 <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IHelloWorldService/QueryProductsByCatetory</a:Action>
<a:MessageID>urn:uuid:47f2ab1a-be57-4ea6-a0d4-8477bd33f259</a:MessageID>
<a:ReplyTo>
	<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<a:To s:mustUnderstand="1">http://192.168.50.7/HelloWorld/HelloWorldService.svc</a:To>
</s:Header>
<s:Body>
<QueryProductsByCatetory xmlns="http://tempuri.org/">
	<catg>Keyboard</catg>
</QueryProductsByCatetory>
</s:Body>
</s:Envelope>
"@
$xdReq = [System.Xml.Linq.XDocument]::Parse($xml)
$ns = [ System.Xml.Linq.XNamespace] "http://tempuri.org/"
$ns_a  = [System.Xml.Linq.XNamespace] "http://www.w3.org/2005/08/addressing"
$url = "http://192.168.50.7/HelloWorld/HelloWorldService.svc"
($xdReq.Descendants($ns_a + "To") | Select-Object -First 1).Value = $url
($xdReq.Descendants($ns + "QueryProductsByCatetory") | Select-Object -First 1).Element($ns + "catg").Value = "Mouse";
$res = (Invoke-WebRequest -Method POST -Uri $url -ContentType "application/soap+xml; charset=utf-8" -Body $xdReq.ToString()).Content
$xdResp = [System.Xml.Linq.XDocument]::Parse($res)
$ns_b = [System.Xml.Linq.XNamespace]"http://schemas.datacontract.org/2004/07/MyWCFServices"
($xdResp.Descendants($ns + "QueryProductsByCatetoryResult") | Select-Object -First 1).Elements() |
ForEach-Object {
	$p = $_
	"ModelId,Category,Name,Price,StockQty".Split(',') | 
	ForEach-Object {
		Write-Host "$($_) $($p.Element($ns_b + $_).Value)"
	}
	Write-Host "===="
}

本次還有意外收獲是發現用 VSCode 寫 PowerShell 很讚,有語法檢查、IntelliSence,下方視窗可直接測試,非常方便。

Example of calling WCF method with PowerShell Invoke-WebRequest.


Comments

# by

黑暗大大~ 想請教你有沒有試過 WCF 的 TransactionScope 呢? 我設置了兩個 wcf service 分別在不同的 Server 上使用, 它們都會寫入一個 record 於 一個MSSQL Server 的 table 內~ 各自的 program都有用 sqlClient 及 Transaction object 去完成寫入,,  小弟正在設置一個客戶端~ 使用了 TransactionScope 但當呼叫 wcf service 時 timeout 了... 在網路上扒了許多文~ 都說要把 DTC 設置好~ 小弟亦有跟按各路文章的建議設置好.... 但都是呼叫 wcf service 時沒有反應了... 想請問大大有波有興趣去試試這個 TransactionScope 呢?

# by Jeffrey

to 熙,不太確定你說的情境。你是指 WCF 動作內含 SQL Transaction,單獨呼叫 OK,客戶端分別呼叫兩個 WCF OK,一次呼叫兩個 WCF 也 OK,但加上 TransactionScope 呼叫兩個 WCF 會 Timeout?

# by

小弟都是參考以下網文來試試 TransactionScope https://www.tutorialspoint.com/wcf/wcf_transaction.htm 有兩個 wcf service 分別在 server1 和 server2 上運行, 它們都是用 SQL Client 和用 SqlTransaction 對資料庫內的 table 進行寫入動作.. 我在 client 上先呼叫 server1 之後再 呼叫 server2, 當 server1 的 wcf service 執行完畢後~ 假若 throw new exception , 那麼 server1 的 transaction 會自動 rollback 即使整個 wcf service 在 server1 已經執行完畢... 在 client 端上使用了 TransactionScope, 而 client 端, server1 和 server2 都啟動了 MSDTC.... 在不使用 TransactionScope 的情況下~ 兩個 wcf service 都時可呼叫和運作正常, 但當常試使用 TransactionScope 時~ 當呼叫 server1 後就沒有回應~ 當反覆撿揸後~ 確下了 MSDTC 是運動正常的~ 因為當我關閉任何一方的 MSDTC 時~ 便會有 MSDTC 沒有正常運作的 exception 顯示... 現在我想 timeout 的問題有可能是 client 端, server1 和 server2 沒有 join 任何 Windows Active Directory 吧~ 因為網上扒文後~ 發覺要令 MSDTC 正常運作是需要把 client 端, server1 和 server2 都加入網域.... 可能要再需要點時間再新步處所需 windows AD 了~ 不知大大是否遇見這個問題?

# by Jeffrey

to 熙,沒試過這麼進階的 WCF 功能,但我建議先確認 Server1 與 Server2 能成功參與 MSDTC 交易。做法是在 Server1 寫一小段程式,以 TransactionScope 包住兩段 SQL 查詢,刻意讓二者的連線字串不同以觸發分散式交易,參考:https://blog.darkthread.net/blog/oracle-msdtc-case/ Transaction.Current.TransactionInformation.DistributedIdentifier 有值才代表 MSDTC 啟動。

Post a comment