WCF 4, JSONP, and jQuery cause parsererror

2020-07-24 04:34发布

问题:

I've tried nearly everything I can think of, but I'm still running into issues with my ajax calls to a WCF service.

My WCF service has a method like below:

//[WebInvoke(ResponseFormat = WebMessageFormat.Json, Method = "POST")]
[WebGet]    
public string Test(int value)
{
    return string.Format("You entered: {0}", value);
}

As mentioned on Twitter by Patrick Thomas, I've also tried using [WebGet(BodyStyle = WebMessageBodyStyle.Wrapped)] and [WebGet(BodyStyle = WebMessageBodyStyle.WrappedResponse)] with no luck.

And configuration like so:

<system.serviceModel>
    <behaviors>
        <serviceBehaviors>
            <behavior>
                <serviceMetadata httpGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
            <behavior name="RestEndpoint">
                <enableWebScript/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <bindings>
        <webHttpBinding>
            <binding name="NoSecurityRestBinding" crossDomainScriptAccessEnabled="true">
                <security mode="None" />
            </binding>
        </webHttpBinding>
    </bindings>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <services>
        <service name="WebServices.TestService">
            <endpoint address="" binding="webHttpBinding" contract="WebServices.ITestService" bindingConfiguration="NoSecurityRestBinding" behaviorConfiguration="RestEndpoint" />
        </service>
    </services>
</system.serviceModel>

The service is located on a different domain, so I'm formatting my data as JSONP:

$.ajax({
    cache: false,
    async: true,
    type: "GET", // Was "POST"
    dataType: "jsonp",
    url: "http://mydomain.com/TestService.svc/Test?callback=?",
    data: dataToPost,
    contentType: "application/json;charset=utf-8",
    success: function (msg) {
        var testMsg = JSON.parse(msg);
        var status = testMsg.TestResult;
        alert(status);
    },
    error: function (msg) {
        alert('Please email Jason with this exception: ' + msg.statusText);
    }
});

And I'm getting:

"parsererror"

"jQuery16408722478272714725_1332817261195 was not called"

What can I possibly be doing wrong? I did verify that all of the WCF binaries are 4.0.

Thanks in advance for your help!

回答1:

You don't want the enableWebScript behavior applied to the endpoint. That specifically enables support for Microsoft's ASP.NET AJAX client stack which has a specific JSON encoding for all request/responses. Replace that with just webHttp instead and see if that solves your problem. Everything else looks ok to me.

The next thing I would suggest is setting the automaticFormatSelection attribute of the webHttp behavior element to true as well. This way it'll make sure to serialized the response as JSON when it detects the accepted content type of the HTTP request is JSON.

Update

What I just remembered is that, since this is JSONP, the request is going to come from a <script/> tag and therefore WCF will probably default to an XML response. Therefore you also want to set defaultOutgoingResponseFormat="Json" on the webHttp behavior as well so that it will, by default, format responses using JSON.

As a side note, it's pointless to set the contentType on the jQuery AJAX request because there is no body for JSONP requests since it's all querystring based.



回答2:

If you want to avoid WCF web.config hassles then you can add the "Factory" attribute to your WCF markup.

If you also want to do JSONP calls and avoid the parsererror issue then use this class:

<%@ ServiceHost Language="C#" Service="MyWebApp.MyWCFService" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"  %>

Use the following class the override the factory

using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Activation;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;

public class JSONPEnabledWebServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new JSONPEnabledWebServiceHost(serviceType, baseAddresses);
    }

    private class JSONPEnabledWebServiceHost : WebServiceHost
    {
        public JSONPEnabledWebServiceHost(Type serviceType, Uri[] baseAddresses) : base(serviceType, baseAddresses)
        {
        }

        protected override void OnOpening()
        {
            base.OnOpening();
            foreach (ServiceEndpoint endpoint in this.Description.Endpoints) {
                WebHttpBinding webBinding = endpoint.Binding as WebHttpBinding;
                if (webBinding != null) {
                    webBinding.CrossDomainScriptAccessEnabled = true;
                }
            }
        }
    }
}

so now your factory attribute will be

<%@ ServiceHost Language="C#" Service="MyWebApp.MyWCFService" Factory="MyWebApp.JSONPEnabledWebServiceHostFactory"  %>


回答3:

JSONP and cross domain calls don't support POST requests. JSONP returns JSON fragment wrapped by padding (the callback function). On behind this is handled as dynamically added script element in your page DOM which is pointing to the cross domain address. Once the targeted script resource is loaded the callback function is executed. Because of that you can only use GET requests - script element is not able to do POST.



回答4:

Add the jsonpCallback option

$.ajax({

    jsonpCallback: 'yourCallbackFunctionName',

    cache: false,
    async: true,
    type: "GET", // Was "POST"
    dataType: "jsonp",
    url: "http://mydomain.com/TestService.svc/Test?callback=?",
    data: dataToPost,
    contentType: "application/json;charset=utf-8",
    success: function (msg) {
        var testMsg = JSON.parse(msg);
        var status = testMsg.TestResult;
        alert(status);
    },
    error: function (msg) {
        alert('Please email Jason with this exception: ' + msg.statusText);
    }
});


回答5:

This problem has been bugged me several times in past 3 years. Every time I met it and fixed it and then forgot to take a note. Next time it happened, I start scratching and search for solution one more time. So, let me record my solution here and share with whoever might deal with this problem. Follow the steps below and make sure your are using JQuery 1.8 or upper with .net framework 4.0 and it will work.

  • In the behavior note under system.serviceModel note, set to webHttp:
<behaviors>
  <endpointBehaviors>
    <behavior name="webHttpBehavior">
      <webHttp />
    </behavior>
  </endpointBehaviors>
</behaviors>
  • Define an JsonP binding:
<bindings>
      <webHttpBinding>
        <binding name="webHttpBindingWithJsonP" crossDomainScriptAccessEnabled="true" />
      </webHttpBinding>
    </bindings>
  • set your service bindingConfiguration to the binding just defined
<services>
  <service name="dpRestService.qcmmsService">
    <endpoint address="" behaviorConfiguration="webHttpBehavior"
      binding="webHttpBinding" bindingConfiguration="webHttpBindingWithJsonP"
      contract="gwsRestService.qcmmsService" />
  </service>
</services>
  • Add Jaon as response Format for ResponseFormat attribute

[WebGet(UriTemplate = "your service template" , ResponseFormat = WebMessageFormat.Json)]

  • Jquery request:

$.ajax({ url: url, type: 'GET', //Type of ajax call data: {}, contentType: 'application/json', crossDomain: true, dataType: 'jsonp',
success: callBackFun, error: errorFun });

I hope this will help.



回答6:

After much pain I managed to get a simple jquery / ajax call to a simple rest service returning jsonp with the callback. After trying to set the callback in the url I finally managed to get it working by adding "jsonpCallback" and the callback name to the params.

$.ajax({
        type : 'GET',
        url : 'http://localhost:8080/RestimpleApp/report/extract',
        dataType : 'jsonp',
        jsonpCallback : "jsoncallback",
        success : function(json) {
            alert('success');

        },
        error : function(jqXHR, status) {
            alert("Failed " + status + jqXHR);
        }
    });

On the server side I had to wrap my response JSON in the callback name("jsoncallback").

@GET
@Produces({ MediaType.APPLICATION_JSON })
public String extractData() {
    System.out.println("Incoming call = ");
    StringBuilder json = new StringBuilder("jsoncallback(");

    json.append("{\"sEcho\": 7,");
    json.append("\"iTotalRecords\": \"600\",");
    json.append("\"iTotalDisplayRecords\": \"12\",");
    json.append("\"aaData\": [");
    json.append("{");
    json.append("\"engine\": \"Gecko\",");
    json.append("\"browser\": \"Firefox 1.0\",");
    json.append("\"platform\": \"Win 98+ / OSX.2+\",");
    json.append("\"version\": \"1.7\",");
    json.append("\"grade\": \"A\"");
    json.append("},");
    json.append("{");
    json.append("\"engine\": \"Gecko\",");
    json.append("\"browser\": \"Firefox 1.5\",");
    json.append("\"platform\": \"Win 98+ / OSX.2+\",");
    json.append("\"version\": \"1.8\",");
    json.append("\"grade\": \"A\"");
    json.append("}  ]");
    json.append("}");
    json.append(");");


    return json.toString();
}

many hours wasted trying to get this to work :(