最近在开发WSS RESTful服务的时候, 碰到了这些个纠结的问题. 在网上查找了半天, 找到n多种解决方案, 可是都是部分的, 要么是没有跨域的状况, 要么是没有post的状况, 要么不是用WCF进行开发. 可就是没有一个解决方案是将全部元素揉合在一块儿的, 真是奇怪, 然道我研究的是小众? jquery
呵呵, 闲话少说, 直接上陷阱和解决方案:web
1. UriTemplate要和<endpointBehaviros><behavior><webHttp>配合使用ajax
用WCF开发REST就不用多说了. 惟一须要注意的是若是使用了UriTemplate来定义REST接口地址, 那么EndpointBehavior不可以继续使用<enableWebScript/>了, 而要改为使用<webHttp/>, 对应的, 在<service><endpoint>中behaviorConfiguration也要设置成WebBehavior.json
<endpointBehaviors> <!--<behavior name="AjaxBehavior"> <enableWebScript/> </behavior>--> <behavior name="WebBehavior"> <webHttp /> </behavior> </endpointBehaviors>
2. 为WebOperation指定输入输出的格式跨域
因为使用了webHttp behavior, 咱们须要在ServiceContract中显示指定WebOperation的输入输出msg格式. 不然会在运行时报错找不到对应的序列化反序列化器.浏览器
[OperationContract(Name="CreateNew")] [WebInvoke(RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json, UriTemplate="/users", Method="POST")] string CreateNew(WSSAccount account);
3. 为了知足JQuery.Ajax, 须要改造WCF RESTful serviceapp
这一步困扰了我很久, JQuery.Ajax在POST的时候是无法设置ContentType的. 网上好多人讨论这个问题, 我想应该是一个bug或者至少是一个设计的限制. 总体表现就是设置了没有任何效果. 在这种状况下去调用WCF写的RESTful Service就坑爹了. 由于JQuery.Ajax在POST的时候永远将contentType设置成application/x-www-unencoded; 而WCF默认的ContentTypeMapper永远只在收到ContentType=application/json的时候才会用JSON来解析Body中的数据...dom
没办法了, 只有改写ContentTypeMapper:ide
public class RESTContentTypeMapper : WebContentTypeMapper { public override WebContentFormat GetMessageFormatForContentType(string contentType) { if (contentType.IndexOf("json", StringComparison.CurrentCultureIgnoreCase) != -1) {return WebContentFormat.Json; } else if (contentType.IndexOf("xml", StringComparison.CurrentCultureIgnoreCase) != -1) {return WebContentFormat.Xml; } else {return WebContentFormat.Json; } } }
强行将WebContentFormat.Json做为默认格式, 而后在<system.serviceModel><standardEndpoints>中配置以下:post
<standardEndpoints> <webHttpEndpoint> <standardEndpoint name="RESTEndpoint" contentTypeMapper="Webus.WSS.Core.RESTful.RESTContentTypeMapper, Core" /> </webHttpEndpoint> </standardEndpoints>
注意, 这个standardEndpoints只在.net4.0/4.5中才有效, 若是你用的老版本, 那仍是算了吧. 配置好了以后, 在<service><endpoint>中endpointConfiguration要设置成"RESTEndpoint", kind要设置成"webHttpEndpoint".
下面来一个完整的配置例子:
<system.serviceModel> <services> <service name="s_Authentication" behaviorConfiguration="DefaultBehavior"> <host> <baseAddresses> <add baseAddress="http://wss.gdtsearch.com/wss/services/test.Authentication" /> </baseAddresses> </host> <endpoint name="Authentication_Web" address="Authentication_Web" binding="webHttpBinding" bindingConfiguration="webHttpBindingJSONP" contract="Webus.WSS.Core.Authentication.IWSSAuthentication" behaviorConfiguration="WebBehavior" endpointConfiguration="RESTEndpoint" kind="webHttpEndpoint" bindingNamespace="http://gdtsearch.com/wss/services/Authentication" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services> <standardEndpoints> <webHttpEndpoint> <standardEndpoint name="RESTEndpoint" contentTypeMapper="Webus.WSS.Core.RESTful.RESTContentTypeMapper, Core" /> </webHttpEndpoint> </standardEndpoints> <behaviors> <endpointBehaviors> <!--<behavior name="AjaxBehavior"> <enableWebScript/> </behavior>--> <behavior name="WebBehavior"> <webHttp /> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="DefaultBehavior"> <serviceMetadata httpGetEnabled="True"/> <serviceDebug includeExceptionDetailInFaults="True"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <webHttpBinding> <binding name="webHttpBindingJSONP" crossDomainScriptAccessEnabled="true"/> </webHttpBinding> </bindings> </system.serviceModel>
4. JQuery.Ajax - crossDomain要设置为true / post的数据要首先转换成JSON格式
我用于POST数据的代码以下, 却老是获得一个HTTP400的错误, 打开Fiddler一看, 发现Body中的数据根本不是JSON格式, 而是普通的相似于querystring中的name-Value组合的字符串.
function createNewAccount(email, password) {var account = { "Email": email, "Password": CryptoJS.MD5(password).toString(CryptoJS.enc.Base64), "Enabled": true }; $.ajax({ type: "POST", crossDomain: true, url: "http://wss.gdtsearch.com/wss/services/test.Authentication/Authentication_Web/users", data: account, dataType: "json", success: function(msg) { alert(msg); }, error: function(a, b, c) { alert(a + b + c); } }); }
没办法, 只好手动将数据转换成JSON了, 好在我找到了一个不错的plugin: https://code.google.com/p/jquery-json/. 代码简单改改就可以用了:
$.ajax({ type: "POST", crossDomain: true, url: "http://wss.gdtsearch.com/wss/services/test.Authentication/Authentication_Web/users", data: $.toJSON(account), dataType: "json", success: function(msg) { alert(msg); }, error: function(a, b, c) { alert(a + b + c); } });
5. JQuery.ajax的跨域只支持XMLHTTPRequest
一切就绪以后, 终于能够开始测试了. 打开Chrome, 打开Fiddler, 访问页面, 点击按钮运行,,, ,,, ,,, 浏览器貌似正常, Fiddler获得一个HTTP200~! 惟一的遗憾是Chrome的Console会出现一个跨域的JS错误. 看来经过设置crossDomain=true确实能够进行跨域访问, 可是并不完美.
打开IE, 再次测试,,, 报错!? Fiddler中没有任何反应, 甚至连Request都没有发出去, 奇怪? googling... 答案在StackOverflow的一篇文章中找到: http://stackoverflow.com/questions/3362474/jquery-ajax-fails-in-ie-on-cross-domain-calls
OTOH, this page mentions that IE7 and eariler cannot do cross domain calls, but IE8 can, using a different object than XMLHttpRequest, the one JQuery uses. Could you check if XDomainRequest works?
也就是说IE8用的所谓XDomainRequest而非XMLHttpRequest, 可是JQuery只支持XMLHttpRequest... 因此想用IE的同窗就断了这个念想吧...
小结 -
研究了两天, 总算将JQuery.Ajax + crossDomain + POST + JSON + WCF RESTful 串起来跑通了, 但是也只可以在Chrome上面跑, FF和Safari没有试, 可是我估计可以行. 硬伤是IE不行, 没想到是这么个结果, 太遗憾了. 不过要实现功能, 也并不是无路可走, 关键仍是要看你在ServiceContract中要不要用UriTemplate, 若是你可以放弃本身设计RESTful接口的主张, 全盘接受微软的一套, 那么简单用<enableWebScript>+JSONP就行啦; 若是你坚持本身设计RESTful接口, 不妨考虑用NodeJS搭建一个本地的proxy svc来解决跨域的问题. 反正仁者见仁, 智者见智, 动手能力强的TX们本身取舍吧.