是否有一种使用basicHttpBinding扩展WCF服务以允许REST服务与JSON通信的好方法?

时间:2011-11-17 13:12:37

标签: c# wcf json rest .net-4.0

我们在VS2010中内置并运行了一个Web服务。

一些运营合同如下:

    [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);

当我现在尝试访问它时,我收到以下错误:

  

我已经设法构建一个只接受字符串作为参数的OperationContract,然后使用DataContractJsonSerializer在后​​端解析瘦,但这感觉更像是一个丑陋的黑客。

有没有办法以更好的方式解决这个问题? 我是初学者,当涉及到WCF和REST时,所以不要害怕指向任何有可能存在的初学者教程。我试图搜索它们,但是大量的资源使得很难找到好的资源。

3 个答案:

答案 0 :(得分:2)

我使用WCF Rest Starter Kit遇到了类似的问题。

如果我没记错的话,使用WebGet或WebInvoke时,路径中的UriTemplate变量始终会解析为字符串。当UriTemplate变量位于UriTemplate的查询部分时,您只能将它们绑定到int,long等。 因此无法在中传递复杂对象。

我认为没有干净的方法可以做到这一点。我刚刚使用了解析解决方案。

现在,您可以查看新堆栈,以便使用名为WCF Web Api的WCF执行REST。它非常适合复杂类型作为方法参数。

答案 1 :(得分:2)

  

从我从他们那里学到的东西,我明白这不是一件好事   与iPhone沟通的做法(缺乏良好的消费方式)   例如WSDL。)

最大的问题不是缺乏良好的“工具”,而是缺乏对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请求。这绝对应该是对Login资源的POST请求,为每个调用返回新的ITicket表示。为什么?因为GET请求应该是安全且幂等的。

  • 安全:请求不应导致任何副作用=它不应对资源进行任何更改,但在您的情况下,它很可能会创建新资源。
  • 幂等:这个对于示例并不那么重要,因为您已经违反了安全规则,但这意味着对资源的请求应该是可重复的。这意味着具有相同用户名,密码和版本的第一个请求可以创建新资源,但是当再次执行请求时,它不应创建新资源,而是返回已创建的资源。当资源在服务器上持久化/维护时,这更有意义。

因为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的身份验证(登录)= Basic,Digest,OAuth等。这并不意味着您不能拥有类似的资源,但您应该首先考虑使用标准的HTTP方式。

你的第二个例子实际上要好得多,但它有WCF限制的问题。 WCF只能从URL读取基本类型(顺便说一句。你想如何在URL中传递对象?)。任何其他参数类型都需要自定义WCF行为。如果需要公开接受数据契约的方法,则必须再次使用接受正文中的参数的HTTP方法 - 再次使用POST并将JSON序列化票证放入请求正文:

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

答案 2 :(得分: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>

然后你可以发布类似“{”userName“:”testuser“,”password“:”testpass“,”softwareVersion“:”1.0.0“}”到网址https://yourdomain.com/service.svc/json/login

如果要传递复杂类型,则只需传入与自定义对象匹配的JSON即可。因此,如果您有一个具有颜色和大小属性的动物对象,则JSON看起来像“{”animal“:{”Color“:”red“,”Size“:”Large“}}”。

应该这样做,你根本不需要改变方法的实现。当以上述方式调用时,WCF将仅针对JSON端点返回JSON格式的数据。您现有的SOAP方法将继续正常工作。