C# 有没有一种好方法可以使用basicHttpBinding扩展WCF服务,以允许REST服务与JSON通信?

C# 有没有一种好方法可以使用basicHttpBinding扩展WCF服务,以允许REST服务与JSON通信?,c#,wcf,json,rest,.net-4.0,C#,Wcf,Json,Rest,.net 4.0,我们在VS2010中建立并运行了一个web服务 一些运营合同如下所示: [OperationContract] ITicket Login(string userName, byte[] passwordHash, string softwareVersion); [OperationContract] [WebGet(UriTemplate = "/login?username={userName}&password={password}&so

我们在VS2010中建立并运行了一个web服务

一些运营合同如下所示:

    [OperationContract]
    ITicket Login(string userName, byte[] passwordHash, string softwareVersion);
    [OperationContract]
    [WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)]
    ITicket Login(string userName, string password, string softwareVersion);
也就是说,它们有复杂的参数和复杂的返回类型,甚至有多个返回

我们最近启动了一个外包的iPhone项目,并允许他们使用此服务与我们的服务器进行通信。 从他们身上我了解到,这不是与iPhone通信的好做法(例如,缺乏使用WSDL的好方法)。因此,我开始考虑将服务公开为与JSON通信的REST服务的可能性

我使用webHttpBinding添加了一个新的端点,对契约进行如下修饰:

    [OperationContract]
    ITicket Login(string userName, byte[] passwordHash, string softwareVersion);
    [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);
当我现在尝试访问此文件时,我收到以下错误:

“/Jetas5MobileService”应用程序中出现服务器错误。活动 合约“IJetas5MobileService2”中的“GetMetaData”有一个查询变量 命名为“Jetas.MobileService.DataContracts.ITicket”类型的“票证”, 但类型“Jetas.MobileService.DataContracts.ITicket”不是 可通过“QueryStringConverter”转换。UriTemplate的变量 查询值必须具有可由转换的类型 “QueryStringConverter”

我已经成功地构建了一个OperationContract,它只接受一个字符串作为参数,然后使用
DataContractJsonSerializer
在后端解析精简,但这更像是一个丑陋的黑客行为

有没有更好的方法来解决这个问题?
说到WCF和REST,我是初学者,所以请不要害怕向我指出可能存在的任何初学者教程。我已经尝试过搜索它们,但是大量的源代码使得很难找到好的源代码。

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

如果我没记错的话,在使用WebGet或WebInvoke时,路径中的UriTemplate变量总是解析为字符串。只有在UriTemplate的查询部分中,才能将UriTemplate变量绑定到int、long等因此无法在中传递复杂对象

我认为没有干净的方法可以做到这一点。我只是像你一样使用解析解决方案

现在,您可以使用名为的WCF检查用于执行REST的新堆栈。它可以很好地处理复杂类型作为方法参数

从他们身上我了解到这不是一个好主意 与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请求。这肯定是对登录资源的POST请求,为每个调用返回新的
ITicket
表示。为什么?因为GET请求应该是安全的且幂等的

  • 安全:请求不应引起任何副作用=它不应对资源进行任何更改,但在您的情况下,它很可能会创建一个新资源
  • 幂等元:这个对于示例来说并不重要,因为您已经违反了安全规则,但这意味着对资源的请求应该是可重复的。这意味着具有相同用户名、密码和版本的第一个请求可以创建新资源,但当再次执行该请求时,它不应创建新资源,而应返回已创建的资源。在服务器上持久化/维护资源时,这更有意义
因为HTTP基础设施认为HTTP GET请求是安全的且幂等的,所以以不同的方式处理它。例如,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);

您应该将JSON数据发布到方法中,并可以设置如下声明:

[OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "/login", BodyStyle = WebMessageBodyStyle.Wrapped)]
    ITicket Login(string userName, string password, string softwareVersion);
然后按如下方式将新端点添加到配置中(保留现有端点和配置