Angular POST to Web API doesn't pass data - angularjs

I've been writing code against ASP.NET Web API for a while now with jQuery and I'm starting something new in Angular (writing against the same Web API backend.)
I'm POSTing to a method that will return some search results for an entity in the system. It looks like this:
public IEnumerable<dynamic> Post(string entity, FormDataCollection parameters)
{
// entity is passed on the route.
// parameters contains all the stuff I'm trying to get here.
}
If I call the method using jQuery.post:
$.post('api/search/child', {where : 'ParentID = 1'}, function(d){ foo = d });
it works just right and returns what I would expect.
I've made a service in my angular application that makes a similar call:
$http.post('api/search/child', { where: 'parentID = ' + parent.ID })
.success(function (data, status, headers, config) {
// etc.
})
But when it hits my "Post" method on the server, "paramters" is null.
After some googling I've tried adding a content-type header to ensure it's passed as JSON, and trying JSON.stringify-ing and $.param()-ing the "data" argument, but that didn't do anything (and from what I've read that shouldn't be necessary.) What am I doing wrong here? Thanks for your help!
UPDATE:
Here's the raw request from the (working) jQuery example:
POST http://localhost:51383/api/search/child HTTP/1.1
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: http://localhost:51383/mypage.aspx
Accept-Language: en-US
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: localhost:51383
Content-Length: 23
DNT: 1
Connection: Keep-Alive
Pragma: no-cache
Cookie: (etc)
Authorization: (etc)
where=ParentID+%3D+1
And the raw request from the (failing) Angular sample:
POST http://localhost:51383/api/search/parent HTTP/1.1
Content-Type: application/json;charset=utf-8
Accept: application/json, text/plain, */*
Referer: http://localhost:51383/mypage.aspx
Accept-Language: en-US
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Connection: Keep-Alive
Content-Length: 27
DNT: 1
Host: localhost:51383
Pragma: no-cache
Cookie: (etc)
{"where":"ScorecardID = 1"}
Very weird. Even when I add the 'json' data type parameter to the end of the jQuery call, it still creates the www-form-urlencoded request. And that's the one that works. My Web API application is already set up for JSON (but thank-you Dalorzo).

Check if you have included the JSON Formatter in your configuration. It should be something like :
System.Web.Http.GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
config.Formatters.Insert(0, new System.Net.Http.Formatting.JsonMediaTypeFormatter());
The Content-Type=application/json only will work if you set the proper formatter.
You can also try using [FromBody] next to your parameter type.

Solved! Discovered this question:
AngularJs $http.post() does not send data
Pointing to this lovely article:
http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/
Turns out Angular doesn't post data the same way jQuery does but you can override it with some tweaking.

I solved this by below codes:
Client Side:
$http({
url: me.serverPath,
method: 'POST',
data: data,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}).
success(function (serverData) {
console.log("ServerData:", serverData);
......
Notice that data is an object.
On the server (ASP.NET MVC):
[AllowCrossSiteJson]
public string Api()
{
var data = JsonConvert.DeserializeObject<AgentRequest>(Request.Form[0]);
if (data == null) return "Null Request";
var bl = Page.Bl = new Core(this);
return data.methodName;
}
and 'AllowCrossSiteJsonAttribute' is needed for cross domain requests:
public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
base.OnActionExecuting(filterContext);
}
}

Related

AngularJS WebApi Authorization header does not appear to be getting passed

I am trying to create a call with AngularJS v1.3.15 into ASP.NET WebAPI (latest from Nuget) to get a list of customers. I can successfully authenticate and get back a token. I add the token to an Authentication header but when I make the call it gets kicked back saying the Authentication header is missing.
The Angular call after I get the token looks like this
$scope.baseUrl = "http://localhost:7800/";
$http({
method: 'GET',
url: $scope.baseUrl + 'customer',
headers: {
'Authorization': $scope.token
}})
I have also tried to utilize the angularjs $resourse
return $resource($scope.baseUrl + 'customer', { }, { 'get': { method: 'GET', isArray: true, headers: { 'Authorization': $scope.token } } });
In the WebApiConfig Register method I have the following setup
var cors = new EnableCorsAttribute("*","*","*");
config.EnableCors(cors);
in a DelegatingHandler I check for the Authorization header like so
var authHeader = request.Headers.Authorization;
From the Angular app it is always null. If I run a check from Fiddler and PostMan I get the Authorization header just fine.
When I press F12 from Chrome and look at the request header these are the results
OPTIONS /customer HTTP/1.1
Host: localhost:7800
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:63342
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2376.0 Safari/537.36
Access-Control-Request-Headers: accept, authorization
Accept: */*
Referer: http://localhost:63342/Test/index.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Fixed my issue, after some searches I found that OPTIONS does not seem to be supported out of the box. I found that if I add NuGet package Microsoft.Owin.Cors and then add
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
to the startup.cs file

AngularJS - Can't pass a header on a $http request

I'm trying to do requests to a RestAPI coded with Codeigniter 3. Access-Control-Allow-Origin and Access-Control-Allow-Headers are set to "*". The API has been successfully tested on Postman.
I'm coding a Web App using AngularJS, and trying to do requests to that API. The following code works properly:
api_service.getUsers = function() {
return $http({
method : 'GET',
url : url + '?dd-api-key=' + api_key
});
}
As this other code functions well too:
api_service.getUsers = function() {
return $http.get(url + '?dd-api-key=' + api_key);
}
But I need to send the API key as a header and not in the URL. So I wrote the function this way:
api_service.getUsers = function() {
return $http({
method: 'GET',
url: url,
headers: {
'accept': undefined,
'dd-api-key': api_key
}
});
}
That code doesn't work. Google Chrome console displays me this:
OPTIONS http://www.myweb.com/api/v1/users
XMLHttpRequest cannot load http://www.myweb.com/api/v1/users. Invalid HTTP status code 403
And at the network tab it shows that the request uses a method type 'OPTIONS', not a 'GET' method.
The Response Headers are:
Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:PUT, GET, POST, DELETE, OPTIONS
Access-Control-Allow-Origin:*
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection:keep-alive
Content-Language:es-ES
Content-Length:45
Content-Type:application/json; charset=utf-8
Date:Wed, 25 Mar 2015 01:45:09 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
Pragma:no-cache
Server:nginx/1.6.2
And the Request Headers are:
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:es-ES,es;q=0.8,en;q=0.6
Access-Control-Request-Headers:dd-api-key
Access-Control-Request-Method:GET
Connection:keep-alive
Host:www.myweb.com
Origin:http://127.0.0.1:55652
Referer:http://127.0.0.1:55652/index.html
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36
Excuse me if my english is not correct.
Many thanks.
you should add header with this:
$http.defaults.headers.common.dd-api-key = api_key;
You can add it in some config as global.

CSRF mismatch when POSTing to sails backend from Angular JS

In angular, I obtain a CSRF token like this:
// Get CSRF token and set as header
var csrfRequest = Auth.getCSRF().then(function(data){
console.log(data.data._csrf);
$rootScope.csrf = data.data._csrf;
});
Which logs the new token to the console (this works fine).
Then, I try to login to a sails.js api. Here's the request:
POST /auth/login HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 108
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Origin: http://localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.99 Safari/537.36
Content-Type: application/json;charset=UTF-8
Referer: http://localhost/sails-front/src/login/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Request Payloadview parsed
{"email":"myemail#email.com","password":"mypass","_csrf":"PIlVO7S362OroPGBSG0X1vW2FydkP9VhK8cMk="}
The _csrf field is the same as that which was received in the getCSRF call.
Yet I get the response 'CSRF mismatch'. However, when I try the exact same thing with Postman, it works fine (so I don't think it's a problem with the sails server).
Here's the login code in angular. In the controller:
$scope.login = function() {
Auth.login({
email: $scope.email,
password: $scope.password,
_csrf: $rootScope.csrf
},
function(res) {
$state.go('app.home');
},
function(err) {
$rootScope.error = "Failed to login";
});
};
And the Auth service:
/*********************************
* login
******************************** */
function login(user, success, error) {
$http.post(API_BASE_URL + 'auth/login', user).success(function(user){
changeUser(user);
success(user);
}).error(error);
}
You posted your full request, and the answer is hidden there in plain sight--not by looking at what is being sent, but what is being omitted: namely, the cookie. The CSRF token is valid for a single Sails session, but you are neglecting to send the cookie with your AJAX request, so Sails has no idea which session the CSRF token you're sending is for.
To tell Angular to send cookies with your request, use the withCredentials setting:
$http.post(API_BASE_URL + 'auth/login', user, {withCredentials: true})

AngularJS $http seems to not respond correctly when I am using CORS

I have an AngularJS application. It sends out requests to another server for data and so there's an OPTIONS request goes out with every $HTTP call.
When I check with fiddler there are two calls. The Options that always returns a 200 OK and then the data call.
However when I check the $HTTP it seems that it's getting the first request ( the options request ) and not getting the second request the one with real data.
Can someone point me in the right direction with this?
Here's one example of the code that is not responding correctly:
.factory('isUsernameAvailable', function (appConstant, $q, $http) {
return function (username) {
var deferred = $q.defer();
// if (!angular.isDefined(username) || username == null || username == "" || username.length < 6 ) return deferred.resolve();
var url = appConstant.baseUrl + '/api/user/existsByName';
$http({
url: url,
method: "PUT",
data: {
userName: username
}
}).then(function (data) {
// Found the user, therefore not unique.
deferred.reject("User name is taken");
}, function (data) {
// User not found, therefore unique!
deferred.resolve();
});
return deferred.promise;
}
})
I expect it to be returning as success or failure depending on if it finds the username. But in this case it always responds as a fail/error.
Here are the requests being made:
OPTIONS http://localhost:3048/api/user/existsByName HTTP/1.1
Host: localhost:3048
Connection: keep-alive
Access-Control-Request-Method: PUT
Origin: http://localhost:2757
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
Access-Control-Request-Headers: accept, authorization, content-type
Accept: */*
Referer: http://localhost:2757/Auth/register
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
giving:
HTTP/1.1 200 OK
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: http://localhost:2757
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: content-type
X-SourceFiles: =?UTF-8?B?QzpcR1xhYmlsaXRlc3Qtc2VydmVyXFdlYlJvbGVcYXBpXHVzZXJcZXhpc3RzQnlOYW1l?=
X-Powered-By: ASP.NET
Date: Mon, 12 Jan 2015 17:52:12 GMT
Content-Length: 0
Then:
PUT http://localhost:3048/api/user/existsByName HTTP/1.1
Host: localhost:3048
Connection: keep-alive
Content-Length: 35
Accept: application/json, text/plain, */*
Origin: http://localhost:2757
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
Authorization: null
Content-Type: application/json;charset=UTF-8
Referer: http://localhost:2757/Auth/register
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
{"userName":"abdddcdefgg#live.com"}
giving:
HTTP/1.1 404 Not Found
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: http://localhost:2757
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-Custom-Header
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcR1xhYmlsaXRlc3Qtc2VydmVyXFdlYlJvbGVcYXBpXHVzZXJcZXhpc3RzQnlOYW1l?=
X-Powered-By: ASP.NET
Date: Mon, 12 Jan 2015 17:52:12 GMT
Content-Length: 0
The problem is even if the second request returns a 200 when I debug the success and error functions it still goes to the error function all of the time.
You should use JSONP to do cross domain JSON calls. Look at the documentation here: https://docs.angularjs.org/api/ng/service/$http#jsonp. Also, your referring page and the response from the OPTIONS request must have the appropriate CORS headers set or else the browser will refuse to send the request here are the header settings that I use.
Access-Control-Allow-Headers:Content-Type, Authorization, Content-Length, X-Requested-With, Accept, x-csrf-token, origin
Access-Control-Allow-Methods:GET,PUT,POST,DELETE,OPTIONS
Access-Control-Allow-Origin:*
To call $http.jsonp with a PUT request, you would set up a configuration such as
var config = {
method: 'POST',
data: { test: 'test' }
};
and then pass that into the $http.jsonp call
$http.jsonp('http://example.com', config);
Here is more documentation https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS and http://en.wikipedia.org/wiki/JSONP

backbonejs + cors and save() method

I'm trying to execute a POST throw the save method. Here is my model.
app.Models.Dummy = Backbone.Model.extend({
initialize: function () {
url = 'http://anotherdomain/Hello/';
},
});
When i execute:
dummy.save({text : "greg"}, {
success : function(){
console.log('Ok!');
},
error: function(){
console.log('Error');
}
});
The request is fired with an OPTIONS header (code 200) but the POST request is never fired.
However When i execute:
$.ajax({
type: 'POST',
url: "http://anotherdomain/Hello/",
data: {text:"greg"},
success: function(r) { alert(r.Result) },
dataType: "application/json"
});
it's works!
Does i need to override something in backbone?
EDIT:
The request is:
OPTIONS http://anotherdomain/Hello/ HTTP/1.1
Host: anotherdomain
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/17.0 Firefox/17.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Origin: http://mydomain
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Pragma: no-cache
Cache-Control: no-cache
and the response is:
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 0
Server: Microsoft-IIS/7.5
Set-Cookie: ARRAffinity=611c389e4fd6c5d83202b700ce5627f6e0850faf0604f13c25903b4662919f36;Path=/;Domain=anotherdomain
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
X-Powered-By: ARR/2.5
X-Powered-By: ASP.NET
Date: Wed, 05 Dec 2012 18:44:27 GMT
That's not a valid OPTIONS response for CORS. The response needs to include headers that tell the browser what's allowed. For example (taken from MDN):
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
I know you said that the $.ajax worked but based on this CORS response I doubt that is accurate and suggest you double check. Under the covers, backbone is itself just using $.ajax

Resources