[經驗] - JQuery.Ajax + 跨域 (crossDomain) + POST + JSON + WCF RESTful, 5大陷阱和解決方案


最近在開發WSS RESTful服務的時候, 碰到了這些個糾結的問題. 在網上查找了半天, 找到n多種解決方案, 但是都是部分的, 要么是沒有跨域的情況, 要么是沒有post的情況, 要么不是用WCF進行開發. 可就是沒有一個解決方案是將所有元素揉合在一起的, 真是奇怪, 然道我研究的是小眾? 

呵呵, 閑話少說, 直接上陷阱和解決方案:

1. UriTemplate要和<endpointBehaviros><behavior><webHttp>配合使用

用WCF開發REST就不用多說了. 唯一需要注意的是如果使用了UriTemplate來定義REST接口地址, 那么EndpointBehavior不能夠繼續使用<enableWebScript/>了, 而要改成使用<webHttp/>, 對應的, 在<service><endpoint>中behaviorConfiguration也要設置成WebBehavior.

      <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 service

這一步困擾了我好久, JQuery.Ajax在POST的時候是沒法設置ContentType的. 網上好多人討論這個問題, 我想應該是一個bug或者至少是一個設計的限制. 整體表現就是設置了沒有任何效果. 在這種情況下去調用WCF寫的RESTful Service就坑爹了. 因為JQuery.Ajax在POST的時候永遠將contentType設置成application/x-www-unencoded; 而WCF默認的ContentTypeMapper永遠只在收到ContentType=application/json的時候才會用JSON來解析Body中的數據...

沒辦法了, 只有改寫ContentTypeMapper:

    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>中配置如下:

    <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>
View Code

 

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們自己取舍吧.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM