是否有一个好方法来扩展WCF服务使用basicHttpBinding也允许REST服务与JSON通信
本文关键字:服务 REST 通信 JSON basicHttpBinding 方法 有一个 扩展 是否 WCF | 更新日期: 2023-09-27 18:14:52
我们已经在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);
当我现在尝试访问它时,我收到以下错误:
'/Jetas5MobileService'应用程序出现服务器错误。操作'GetMetaData'在合同'IJetas5MobileService2'有一个查询变量命名'ticket'的类型' jetas . mobileservice . datcontracts . iticket ',但是键入"Jetas.MobileService.DataContracts"。ITicket则不是可转换的'QueryStringConverter'。UriTemplate的变量查询值必须具有可被转换的类型"QueryStringConverter"。
我已经设法建立一个OperationContract,只接受一个字符串作为参数,然后通过使用DataContractJsonSerializer
在后端解析薄,但这感觉更像是一个丑陋的黑客。
有没有更好的方法来解决这个问题?当涉及到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 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);
我在使用WCF Rest Starter Kit时遇到了类似的问题。
如果我没记错的话,当使用WebGet或WebInvoke时,路径中的UriTemplate变量总是解析为字符串。你只能将UriTemplate变量绑定到int、long等,当它们在UriTemplate的查询部分时。所以没有办法在中传递复杂对象。
我认为没有干净的方法来做这件事。我只是像您一样使用了解析解决方案。现在,您可以查看用于使用WCF进行REST的新堆栈,称为WCF Web Api。它很好地处理了作为方法参数的复杂类型。
您应该将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"}"到URL https://yourdomain.com/service.svc/json/login.
如果你想传入一个复杂的类型,那么你只需要传入与自定义对象匹配的JSON。所以如果你有一个带有颜色和大小属性的动物对象,JSON将看起来像"{"animal": {" color ": "red", " size ": "Large"}}"。
应该就可以了,你根本不需要改变方法的实现。当以上述方式对JSON端点调用时,WCF将只返回JSON格式的数据。您现有的SOAP方法将继续正常工作。