Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem sending JSON data from JQuery to WCF REST method

I'm having some trouble getting jquery to post some json data to a rest method I have on my WCF service.

On the WCF side, here's the operation contract:

[OperationContract]
[WebInvoke(Method = "POST",
           BodyStyle = WebMessageBodyStyle.Bare,
           RequestFormat = WebMessageFormat.Json,
           ResponseFormat = WebMessageFormat.Json,
           UriTemplate = "PostSomething")]
MyResult PostSomething(MyRequest request);

both MyResult and MyRequest are marked with all the necessary DataContract and DataMember attributes and service is exposing WebHttp endpoint.

On the JQuery side, here's my function call:

var jsonStr = JSON.stringify(reqObj);

$.ajax({
    type: "POST",
    dataType: "json",
    url: "http://localhost/MyService/PostSomething",
    contentType: "application/json; charset=utf-8",
    data: jsonStr,
    success: function (html) {
        alert(html);
    }
});

this request never reaches my method (I get a 405 Method Not Allowed everytime), and looking in Charles the request looks like this:

OPTIONS /MyService/PostSomething HTTP/1.1
Host: localhost
Cache-Control: max-age=0
Access-Control-Request-Method: POST
Origin: null
Access-Control-Request-Headers: Content-Type, Accept
Accept: */*
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

couple of things which is strange about this:

  1. the method is OPTIONS not POST
  2. the content-type (in another tab) shows text/html; charset=UTF-8 instead of json
  3. the JSON data is no where to be seen

However, if I modify the request in Charles so that its headers is similar to the solution here, then everything works:

POST /MyService/PostSomething HTTP/1.1
Content-Type: application/json; charset=utf-8
Host: localhost
Content-Length: 152

{"Id":"", "Name":"testspot","Description":"test" } 

looking at tutorials and other questions on here others have managed to get JQuery to post to a WCF REST method like this, and I'm at a loss as to what I'm doing wrong here..

oh, to put some context, this is a WCF 4 service and I'm using JQuery 1.4.4.

Thanks,

UPDATE:

After some more reading and thanks to Darrel for pointing me towards the cross-domain spec, I managed to get a bit further by making some small changes to my service, on the service interface:

[OperationContract]
[WebInvoke(Method = "*",
           BodyStyle = WebMessageBodyStyle.Bare,
           RequestFormat = WebMessageFormat.Json,
           ResponseFormat = WebMessageFormat.Json,
           UriTemplate = "PostSomething")]
MyResult PostSomething(MyRequest request);

and in the implementation, I need to check if the incoming requests is for OPTIONS and in that case return some headers rather than doing the intended work:

if (WebOperationContext.Current.IncomingRequest.Method == "OPTIONS")
{
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST");
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept");

    return null;
}

the method then gets called twice, the first time the server returns null but adds some headers to the client, and then the actual request is made with POST as method and the server goes ahead and deals with the request normally.

like image 689
theburningmonk Avatar asked Feb 02 '11 13:02

theburningmonk


3 Answers

This seems to be a Firefox thing for avoiding cross domain calls. See http://www.petefreitag.com/item/703.cfm

The spec for this is here http://www.w3.org/TR/cors/ and after a very brief read, it appears that because you are doing a cross domain call, your service is expected to implement the OPTIONS method and return some headers that allow the POST method to be sent.

like image 173
Darrel Miller Avatar answered Nov 16 '22 10:11

Darrel Miller


The update on the question containing a proposed solution has some issues - The problem with it is that if your input does not support the POST method, the OPTIONS request is not actually returning the correct allowed headers. It really isn't looking at which methods are actually allowed on the WCF endpoint - its just artificially saying "POST" is allowed for every single endpoint in the application when a client performs an OPTIONS request (which is really the client asking what is supported).

This is probably OK, if you aren't really relying on the information in the OPTIONS method to return you a valid list of methods (as is the case with some CORS requests) - but if you are, you will need to do something like the solution on this question: How to handle Ajax JQUERY POST request with WCF self-host

Basically, each endpoint should implement:

Webinvoke(Method="OPTIONS", UriTemplate="")

and call an appropriate method which loads the proper headers to the response (including the proper "Access-Control-Allow-Method" list for that endpoint) to the caller. It kind of sucks that hosted WCF endpoints don't do this for us automatically, but this is a workaround that allows finer control over the endpoint. In that solution the proper response headers are loaded at the endpoint implementation:

public void GetOptions()
    {
        // The data loaded in these headers should match whatever it is you support on the endpoint
        // for your application. 
        // For Origin: The "*" should really be a list of valid cross site domains for better security
        // For Methods: The list should be the list of support methods for the endpoint
        // For Allowed Headers: The list should be the supported header for your application

        WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
        WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
        WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization");
    }

In addition to that, you should be setting the "CrossDomainScriptAccessEnabled" flag either in the web.config for the binding endpoint, or in code for the WebHttpBinding when configuring the endpoint. Otherwise, you are again lying in your header response when you say "Access-Control-Allow-Origin" is "*" (or a list of URLS)

like image 23
jeremyh Avatar answered Nov 16 '22 09:11

jeremyh


Update:

Try putting .svc after MyService so that the URL reads

http://localhost/MyService.svc/PostSomething

I was working on this the other day myself, and came across a post on Rick Strahl's blog:

http://www.west-wind.com/weblog/posts/324917.aspx

This works flawlessly for me, so give it a try!

Hope that helps! :)

like image 1
Yngve B-Nilsen Avatar answered Nov 16 '22 10:11

Yngve B-Nilsen