Calling .Net web service from jQuery+Ajax - json

I'm trying to call a homemade vb.net web service using jQuery+Ajax and I'm struggling with the specifics.
Here's a small function exposed as a web method:
<WebMethod()> <ScriptMethod(ResponseFormat:=ResponseFormat.Xml, UseHttpGet:=True)> _
Public Function GetAllVotes() As XmlDocument
Dim theVotes = getVotes()
Dim strResult As String = theVotes.XMLSerialize
Dim doc As XmlDocument = New XmlDocument()
doc.LoadXml(strResult)
Return doc
End Function
After looking the web I've added the ScriptMethod attributes since i was returning XML but feel free to tell me i don't need them if that's the case.
Then, on the client side, this is the code :
function getVotes() {
$.support.cors = true;
$.ajax({
type: "GET",
contentType: "application/json",
url: "http://nhrd635:8008/votingmanager.asmx/GetAllVotes",
data: {},
dataType: "xml text jsonp",
success: function(msg) {
// Hide the fake progress indicator graphic.
// Insert the returned HTML into the <div>.
$('#myPlaceHolder').html(msg);
},
error: function(msg) {
$('#myPlaceHolder').html(msg);
// alert(msg);
}
});
}
I've tried many .. many variations of this code, using post or get, changing the content-type, with or without charset=utf-8. with and without double quotes on data: {}.
i use firebug to trace the output my request. only when i set dataType to jsonp do i ever get a result, but in all instances, the code ends up on the "error" function, even when status give 200 OK. but i know that setting it to jsonp is wrong since that gets my xml treated as actual javascript...
I've read very useful blog entries from a guy on encosia.
(sample: http://encosia.com/3-mistakes-to-avoid-when-using-jquery-with-aspnet-ajax/)
but even following his examples i am unable to get a proper return.
am i doing something wrong that's very obvious? is it the fact that i am returning an xml string rather than a json serialized string?

With more perusing of Stack Overflow and the help of Dave Ward from Encosia, I've managed to solve my problem. I've thought I should post my final solution here, in case that helps someone in the future.
First of all, Web Services were a bad way of doing it, I went with the HttpHandler solution, as suggested by Dave Ward in reply to my original question.
Returning XML was also a poor choice, that I wasn't really aware of. I added a reference to JSon.net to my project and used it to transform my object into a Json string.
I really wanted to stick to ".net only" to transform into a json string, as suggested in Dave's blog post, but somehow I struggled to learn how to instruct .net to automatically transform into Json as in Dave's example, so i took an easy way out with Json.net to "get it working"
Then, in my HttpHandler, I had the response string follow the instructions on this post from StackOverflow:
https://stackoverflow.com/a/3703221/1060133
in my case, it was :
context.Response.Write(String.Format("{0}({1});", context.Request("callback"), jsonVotes))
The jquery call also used the instructions in the above post.
Interesting note, even in a parameter-less call, you have to send empty data like so:
$.getJSON('http://url/httpHandler.ashx?callback=?', {},
function(data) {
alert(data);
}
);
Best of luck...

I think most of your trouble here probably stems from the cross-origin request (even making a request across different ports on the same machine counts). That's why you were able to get a glimmer of it working when you switched to JSONP. Unfortunately, ASMX "ScriptServices" don't support JSONP, so the data your WebMethod returned wouldn't be a valid parameter to the JSONP callback function that jQuery injects.
The best solution, if at all possible, is to get the service running on the same domain as the page that's calling it. There are various solutions to the cross-origin problem, but none of them are as widely compatible/reliable as a simple XHR request to the same domain that the page making the request resides on.
If you can't do that, consider enabling CORS support for the site serving up votingmanager.asmx. That doesn't work in most versions of IE, but will allow cross-origin requests in other browsers. More info on how to do that here: http://encosia.com/using-cors-to-access-asp-net-services-across-domains/
Tangentially, I'd avoid the extra XML serialization layer if possible. If getVotes() returns something like a List, use that as your return type and let ASP.NET automatically serialize the collection as JSON and then jQuery will automatically convert that to a JavaScript array in your success handler. More info about that here: http://encosia.com/asp-net-web-services-mistake-manual-json-serialization/

Related

Should I use JSend for wrapping json ajax responses, or is there a more standard standard?

I'm setting up a json-over-http service. The responses should have some meta-data, primarily for success/failure. That could be done via http headers, but putting it in the json is nicer (it makes the meta-data available even if some higher-up part of the client code has consumed the http response object).
Is there an emerging standard for this?
I'm aware of JSend, but it doesn't seem to have widepsread adoption.
JSend, in a nutshell, is:
{
status: "success"|"fail"|"error",
message: String, // optional error message
data: any, // the ajax payload
code: Number // optional numeric code for errors
}
My conclusion is: JSend is the most widely adopted wrapper format for json responses, so that is the way to go.

HTTP Status 0 from AngularJS Get for JSON

I'm running a $http.get for a JSON and am getting a status of 0. I've downloaded the same JSON and the get works locally, and in Python using the requests library I can get the JSON no problem, but in AngularJS it's not working. What I don't understand is why angular isn't getting it but everything else is. Code snippet below.
function AgentListCtrl($scope, $http) {
$http.get('http://foo.bar/api/objects').success(function(data) {
$scope.objects = data;
}).error(function(data, status) {
$scope.status1 = status;
});
This provides the JSON and parses it when using a local file, but otherwise it fails and sets status1 to 0.
Just to make this clear since is not directly stated in the above answer (but in its comments) and, like me, some Angular newbies may be spending some time on this:
Angular's $resource will be able to execute a REST verb on another server, which in turn will respond correctly (with a status 200). Angular will nevertheless fail with a cryptical message, identifyiable by the status 0. It is further misleading since, in a browser's debugger, you may actually see the server's answer.
Angular will do an OPTIONS request on a cross-domain request (at least for the default query() method) unless specified on the contrary. Usually the server will not answer with the desired content (i.e. your representation). One simple way of doing this per request is specifying the method to be 'GET'.
$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
The server answering your REST requests MUST include the headers specified by CORS [1] in order to allow Angular to consume properly the response. Essentially this means including the Access-Control-Allow-Origin header in your response, specifying the servers from where the request comes from, that are allowed. This value may be *.
Complementing this answer for anyone integrating AngularJS with spring-data-rest-webmvc:
the HATEOAS json formatted response will not be properly consumed by Angular, producing instead the error Expected response to contain an array but got an object. This is solved by adding the isArray: false parameter to the $resouce's configuration;
a very to-the-point example of configuring CORS for the spring-data-rest-webmvc scenario is presented at [2] (see the SimpleCORSFilter)
[1] https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
[2] https://spring.io/guides/gs/rest-service-cors/
In your code, the status assignment only occurs when the error happens. You should be able to get the status when the call was made successfully like this:
success(function(data, status, headers, config) {
$scope.objects = data;
$scope.status1 = status;
}).error(function(data, status) {
$scope.status1 = status;
});
I was having a similar problem myself. A third party API that returns JSON just fine through every other means was failing with status 0 when called through Angular's $http.get() method.
In my case there wasn't any CORS problem. Instead, the URL I was using for the API was not quite right and the server was issuing a 301 response. Angular wasn't respecting the redirect.
Word to the wise.

WebAPI Model Binding from JSON

I am creating an application using Durandal, with WebAPI as the server. I have a KendoUI grid that displays the data from the server correctly and functions properly until the POST or PUT methods are called. Here is my GET method:
and you can see that that data binds to the UI (used the data-bind extensibility in Durandal to change to kendo bindings):
Then I edit the data in the Grid and it passes the changes inside the request to the server as you can see in this Fiddler result:
On the server side I cannot get the data that is passed from the client to bind to anything I place as a parameter for the method on the POST or PUT.
I realize this is a couple different technologies to troubleshoot (e.g. Durandal, KnockoutJs, Kendo DataBinding, and WebAPI) but I think the fundamentals are working, the data is retrieved and bound to the UI and it is posted back when changed, but the WebAPI endpoint cannot bind to the data.
How can I get the passed "models" array to bind through the ModelBinding structure in WebAPI?
UPDATE- Here is the helpful JSFiddle that gave me the correct Content-Type to add: http://jsfiddle.net/Xhrrj/1/
new kendo.data.DataSource({
transport: {
read: {
type: "POST",
url: "../cccs/service.svc/SupplierSearch",
contentType: "application/json; charset=utf-8",
dataType: 'json'...
this is coming from the Telerik forum here
It looks as if it was mixing up form-urlencoded with json format - if you lookat the decoded string it is sending models= and then urlencoded JSON objects follow.
From my experience I think that your PUT end point declaration needs to look like this:
[HttpPut]
public void Put([FromBody]IEnumerable<Product> models) { }
So you need the FromBody attribute because your product array is in the body (I think?) and not in the url of the request.

How can I get DotNetNuke 6.2 Service Framework to modelbind json data

DotNetNuke Serviceframework is based on ASP.NET MVC 2, and therefore does not include json modelbinding out of the box.
I've tried a number of approaches:
MVC3 jsonvalueprovider
custom json model binder
custom value provider
The code to register these was called, however the methods on these objects themselves were not called.
Registering these is an interesting area in itself as in DotNetNuke, we don't have access to the global.asax file.
I've also attempted to deserialize the request input stream, in the the controller, but I get a nullreferenceexception there, and I get the correct data size, but all nulls!
Any ideas?!
Ok,
I have a workaround that is clean and functional.
I'm using a jquery plugin from here .This converts json into a standard forms form post for you.
using this with jquery & knockout looks like this:
$.ajax({
url: '<%= ModulePath %>Api/Register/Search',
type: 'POST',
data: $.toDictionary(ko.mapping.toJS($root),"",true),
success: function (data) { //do something });
Leaving the question open, in case anyone has any ideas to get json working directly.
You need to register a JSON value provider to get this to work. See http://haacked.com/archive/2010/04/15/sending-json-to-an-asp-net-mvc-action-method-argument.aspx for details.
The best way to register the value provider is to do it in your route mapper. Be sure to guard the registration to ensure it occurs only once, as the route mapper is occasionally called more than once. If you are going to do this in a module deployed to servers you don't control you should probably inspect the contents of the factories collection to ensure no other service has already registered the value provider.
Services Framework in DNN 7 is based on WebAPI and natively supports JSON, so this hassle will go away soon.

IE tries to download json response while submitting jQuery multipart form data containing file

I'm trying to submit a form with a file field in it via jQuery.Form plugin, here's the code:
$('form').ajaxSubmit({
url: "/path",
dataType: "json",
contentType: "multipart/form-data"
...
The server then returns json as a response. Works great in all browsers except IE, which tries to download the response as a file. If I remove the file field from the form, it also works just fine.
I've seen various solutions here and in Google and basically tried almost everything described, including setting enctype for the form via jQuery, but it didn't work.
Any suggestions would be very welcomed.
You can simply return JSON from the controller as "text/html" and then parse it on the client side using JQuery.parseJSON().
Controller:
return this.Json(
new
{
prop1 = 5,
prop2 = 10
},
"text/html");
Client side:
jsonResponse = $.parseJSON(response);
if(jsonResponse.prop1==5) {
...
}
This solution has been working for me.
I have not found a direct solution to this, but I eventually implemented the following workaround: I used dataType: "text" in my ajax settings and then returned plaintext from controller, separating values with ; and parsing them on the client side. That way IE and Forefox stopped trying to download a response.
I did not find any other way to prevent said behavior other then to return plaintext. I tried returning JSON as plaintext and then parsing it with $.parseJSON, but it didn't work due to some js errors.
Just send response with 'Content-Type', 'text/html' header.
Just set Content-Type: text/html
This happens because IE8 doesn't recognize application/... mimetype.
This works for me.
Hope it helps.
Same situation than you folks : the problem only occurs with enctype="multipart/form-data" form used with $(form).ajaxSubmit(...) function.
My team and I had to replace (in this function) dataType: 'json' option with dataType: 'text' and add responseText = $.parseJSON(responseText); to force server response parsing.
Of course we also had to step in server side to return a response with "text/plain" header instead of "application/json"
We're not proud of it :( IE is definitely killing everything...
I didn't try the advice given by zmonteca (already spent too much time on it) but it seems worthy : let us know if it was OK for you.
Hope it helps!
if you work with Zend you can do
$this->getResponse()->setHeader('Content-Type', 'text/html');
in your controller action. and on client-side, in case of jQuery, you can do
data = $.parseJSON(data);
This site has some info about wrapping the response in a
http://forum.jquery.com/topic/jquery-form-malsup-ie7-file-download-security-warning-on-ajax-file-upload
I seemed to be able to fix my problem by making my server return the JSON as a string.
Then I used JSON.parse() inside the complete event.
I came up with the following workaround (in Zend Framework):
if (!$this->_request->isXmlHttpRequest()) {
die('<textarea>'.Zend_Json::encode($data).'</textarea>');
}
$this->view->assign($data);
Removing ContentType from serverside works for me.