2011-11-17 63 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