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

nj.*_*nj. 5 c# rest wcf json .net-4.0

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

一些运营合同如下:

    [OperationContract]
    ITicket Login(string userName, byte[] passwordHash, string softwareVersion);
Run Code Online (Sandbox Code Playgroud)

即他们的摊位有复杂的论点和复杂的回报类型,甚至多次回报.

我们最近开始了一个外包的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);
Run Code Online (Sandbox Code Playgroud)

此方法现在可以按预期工作.

然后我尝试装饰另一种方法,如下所示:

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

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

'/ Jetas5MobileService'应用程序中的服务器错误.合同'IJetas5MobileService2'中的'GetMetaData'操作有一个名为'ticket'的查询变量,类型为'Jetas.MobileService.DataContracts.ITicket',但类型'Jetas.MobileService.DataContracts.ITicket'不能由'QueryStringConverter'转换.UriTemplate查询值的变量必须具有可由"QueryStringConverter"转换的类型.

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

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

Lad*_*nka 2

从他们那里我了解到,这不是与 iPhone 通信的良好实践(例如,缺乏使用 WSDL 的好方法)。

最大的问题不是缺乏好的“工具”,而是缺乏对 WSDL 是什么以及 Web 服务如何工作的理解。所有这些为开发人员生成服务存根的工具导致开发人员不了解幕后的内容。它适用于基本场景,其中所有魔法都为您完成,但是一旦开发人员必须跟踪任何问题或使用附加功能扩展“工具”,他们就会遇到大问题(并且通常会导致糟糕的解决方案)。说实话,SW开发不是做基础场景的。

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);
Run Code Online (Sandbox Code Playgroud)

Login方法显然是执行某些操作的东西 - 我猜它会创建票证。它绝对不适合 GET HTTP 请求。这绝对应该是对登录资源的 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);
Run Code Online (Sandbox Code Playgroud)

因为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);
Run Code Online (Sandbox Code Playgroud)