2011-11-17 65 views
5

我們有一個在VS2010中建立並運行的Web服務。有沒有一種使用basicHttpBinding擴展WCF服務以允許REST服務與JSON通信的好方法?

一些業務合同是這樣的:

[OperationContract] 
    ITicket Login(string userName, byte[] passwordHash, string softwareVersion); 

即他們的展位具有複雜的論點和複雜的回報類型,甚至是多重回報。

我們最近開始了一個外包iPhone項目,並讓他們使用此服務與我們的服務器通信。 從我從他們身上學到的東西我明白,這不是一個溝通到iPhone的好習慣(缺乏消耗WSDL的好方法)。因此,我開始考慮將服務作爲與JSON通信的REST服務公開的可能性。

我添加了一個新的端點,使用的WebHttpBinding,裝飾這樣的合同:

[OperationContract] 
    [WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)] 
    ITicket Login(string userName, string password, string softwareVersion); 

這種方法現在按預期工作。

然後我試圖來裝飾另一種方法是這樣的:

[OperationContract] 
    [WebGet(UriTemplate = "/GetMetaData?ticket={ticket}",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] 
    IMetaData GetMetaData(ITicket ticket); 

現在當我嘗試訪問此我收到以下錯誤:

Server Error in '/Jetas5MobileService' Application. Operation 'GetMetaData' in contract 'IJetas5MobileService2' has a query variable named 'ticket' of type 'Jetas.MobileService.DataContracts.ITicket', but type 'Jetas.MobileService.DataContracts.ITicket' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.

我必須設法建立一個OperationContract的,只有需要一個字符串作爲參數,然後通過使用DataContractJsonSerializer來解析後端的精簡,但這更像是一種醜陋的黑客攻擊。

有什麼方法可以更好地解決這個問題嗎? 我是WCF和REST的初學者,所以不要害怕將我指向任何有可能在那裏的初學者教程。我試圖尋找它們,但是大量的信息來源使得很難找到好的信息。

+0

您正在使用哪個版本的WCF? –

+0

我正在使用.net4和VS2010,這是否回答這個問題?否則讓我知道我該如何查找它。 –

回答

2

From what I have learnt from them I understood that this is not a good practice for communicating to the iPhone (lack of good ways to consume the WSDL for example).

最大的問題不是缺乏好的「工具」,而是缺乏對WSDL是什麼以及Web服務如何工作的理解。所有這些爲開發人員生成服務存根的工具都會導致開發人員無法理解隱藏的內容。它適用於爲你完成所有魔術的基本場景,但是一旦開發人員需要跟蹤任何問題或擴展「工具」以增加具有大問題的功能(並且通常會導致錯誤的解決方案)。說實話,軟件開發並不是基本情況。

REST對開發者來說是一個很大的挑戰,因爲它不提供任何「魔術」工具。 REST關於HTTP協議的正確使用,它充分利用了現有的HTTP基礎設施。如果不瞭解HTTP協議的基礎知識,您將無法創建良好的REST服務。那就是你應該開始的地方。

這是不正確的使用的一些例子:

[OperationContract] 
[WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)] 
ITicket Login(string userName, string password, string softwareVersion); 

Login方法顯然是執行某些操作的東西 - 我猜它創建票。 GET HTTP請求絕對不適合。這肯定是一個POST請求登錄資源返回新的ITicket表示每個電話。爲什麼?因爲GET請求應該是安全的和冪等的。

  • 安全:請求不應引起任何副作用=它不應該做的資源,但在你的情況下,它很可能會創建一個新的資源的任何變化。
  • Idempotent:這個例子對於這個例子並不重要,因爲你已經違反了安全規則,但這意味着對資源的請求應該是可重複的。這意味着具有相同用戶名,密碼和版本的第一個請求可以創建新資源,但是當請求再次執行時,它不應創建新資源,而是返回已創建的資源。當資源在服務器上被保存/維護時,這更有意義。

由於HTTP GET請求是通過HTTP基礎結構認爲是安全和冪等的,因此它以不同的方式處理。例如,GET請求可以被緩存重定向等。當請求不安全並且冪等時,它應該使用POST方法。所以正確的定義是:

[OperationContract] 
[WebInvoke(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)] 
ITicket Login(string userName, string password, string softwareVersion); 

因爲WebInvoke默認爲POST方法。這也是所有協議隧道(例如SOAP)爲所有請求通常使用POST HTTP方法的原因。

前一個例子中的另一個問題可能再次是REST方法=充分利用HTTP基礎結構。它應該使用基於HTTP的認證(登錄)=基本,摘要,OAuth等。這並不意味着你不能有類似的資源,但你應該首先考慮使用標準的HTTP方式。

你的第二個例子實際上好很多,但它有WCF限制的問題。 WCF只能從URL讀取基本類型(順便說一句,你想如何在URL中傳遞對象?)。任何其他參數類型都需要自定義WCF行爲。如果您需要公開其接收數據的合同法,你必須再次使用它接受身體參數的HTTP方法 - 再次使用POST和地方JSON序列票請求的身體:

[OperationContract] 
[WebInvoke(UriTemplate = "/GetMetaData",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] 
IMetaData GetMetaData(ITicket ticket); 
+0

感謝您的好評!我將更多地閱讀HTTP協議。我已經有一個問題了。 GetMetaData基本上是一個返回數據的查詢,具體取決於票據。當我閱讀http://www.w3.org/2001/tag/doc/whenToUseGet.html#checklist時,我認爲GET是最適合此操作的,即服務器的狀態相同。是否因爲參數的複雜類型,POST是首選? –

+0

是的,它更適合GET,但由於WCF默認功能設置您的方法定義需要POST來接受複雜類型。 –

2

我面對使用WCF Rest Starter Kit的類似問題。

如果我沒有記錯,當使用WebGet或WebInvoke時,路徑中的UriTemplate變量總是會解析爲字符串。當UriTemplate變量位於UriTemplate的查詢部分時,您只能將UriTemplate變量綁定到int,long等。 所以沒有辦法在中傳遞複雜對象。

我認爲沒有乾淨的方式來做到這一點。我只是像你一樣使用解析解決方案。

現在,您可以查看用於執行REST的新堆棧,WCF名爲WCF Web Api。它很好地處理複雜類型作爲方法參數。

+0

謝謝!我會看看那個。 –

1

你應該張貼JSON數據該方法可以設置聲明,如:

[OperationContract] 
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, 
     UriTemplate = "/login", BodyStyle = WebMessageBodyStyle.Wrapped)] 
    ITicket Login(string userName, string password, string softwareVersion); 

然後一個新的端點添加到您的配置如下(離開你現有的端點和配置它所代表的方式,你只需添加新的JSON端點和新行爲):

<service behaviorConfiguration="Your.ServiceBehavior.Here" name="Your.Stuff.Here"> 
     <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicBindingSettings" behaviorConfiguration="basic" contract="Your.Contract.Here"> 
     </endpoint> 
     <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> 
     <endpoint address="json" binding="webHttpBinding" contract="Your.Contract.Here" behaviorConfiguration="web"></endpoint> 
     </service> 
<behaviors> 
     <endpointBehaviors> 
     <behavior name="web"> 
      <webHttp /> 
     </behavior> 
    </behaviors> 

然後您可以在URL https://yourdomain.com/service.svc/json/login上發佈類似「{」userName「:」testuser「,」password「:」testpass「,」softwareVersion「:」1.0.0「}的內容。

如果你想傳遞一個複雜的類型,那麼你只需要傳入與自定義對象匹配的JSON即可。因此,如果您有一個具有顏色和大小屬性的動物對象,則JSON將看起來像「{」animal「:{」Color「:」red「,」Size「:」Large「}}」。

應該這樣做,你根本不需要改變方法的實現。當以上述方式調用WCF時,WCF將僅返回JSON格式的數據,而不是JSON端點。您現有的SOAP方法將繼續正常工作。

+0

我的終端和「登錄」方法都適用於我。當我嘗試使用更復雜的類型時,錯誤就開始了。 –

+0

我意識到你在我的文章中已經包含了一些已經完成的內容,但是我正在經歷從上到下的完整性步驟,以防其他人絆倒這個。我描述的場景中複雜類型的關鍵是設置RequestFormat = WebMessageFormat.Json,然後正確格式化您的JSON請求。 – GCaiazzo