最近在開發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們本身取捨吧.