Web Api 2 deserialize LINQ2SQL object with collection property - ajax

The context is long, so I'll start with the question: Why isn't the InductionQAs collection property being rehydrated?
In a Web API 2 method, LINQ produces an object ic with a collection property InductionQAs. After I eliminate a huge irrelevant object graph and prevent a circular reference, ic is returned by the method. Here's the code. Error handling has been removed for brevity.
// GET: api/InductionContent/5
[Authorize]
public object Get(int id)
{
var dc = new Models.InductionsDataContext();
var user = GetUser(dc);
var ic = dc.InductionContents.Single(c => c.InductionContentID == id);
ic.User.InductionContents.Clear(); //don't ship this it can be huge
return ic;
}
Here's the invoking JS code. Ignore the authorisation header business, that all works fine.
var token, headers;
$.ajax({ //get session token
url: "api/token",
headers: { Authorization: "Basic " + btoa("username:password") },
}).done(function (result) {
localStorage.setItem('access_token', token = result.access_token);
var headers = { Authorization: "Session " + token };
$.ajax({ //get an induction content object
url: "api/InductionContent/5",
headers: headers
}).done(function (ic) {
//at this point we have an object graph
var json = JSON.stringify(ic);
});
});
This is what JSON.stringify(ic) returns:
{
"InductionContentID": 5,
"InductionTemplateID": 1,
"InductionContent1": "<p>We recognise that it is ...(redacted)</p>",
"InductionContentOrder": 301,
"Caption": "Workplace Health and Safety",
"OwnerId": 0,
"ParentId": 0,
"TopicId": 5,
"InductionQAs": [
{
"InductionQAID": 1,
"InductionContentID": 5,
"Question": "Who is responsible for ...(redacted)",
"Answers": "A|B|C|*D",
"InductionQAOrder": 1
}
],
"User": null
}
All well and good. Now we round-trip it.
$.ajax({
type: "post",
url: "api/InductionContent",
headers: headers,
data: ic
});
This calls into the following web method:
// POST: api/InductionContent
[Authorize]
public void Post([FromBody]Models.InductionContent ic)
{
//redacted
}
The method is invoked and ic has a value. ic.User contains data, but inspection of ic.InductionQAs reveals that it has not been initialised.
Serialisation is configured as follows:
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.MessageHandlers
.Add(new AuthenticationHandler(CreateConfiguration()));
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}

MVC is smart but not quite smart enough. It needs a hint. The "round-trip" call should have looked like this:
$.ajax({
type: "post",
url: "api/InductionContent",
contentType: "text/json",
headers: headers,
data: JSON.stringify(ic)
}).done(function (result) {
//etc
});
There are two things to note here.
We have assumed manual control of payload encoding
We have annotated the post with the encoding used
One another thing...
Having got it all to work I decided the method should return the id of the freshly minted database row. Changing the method return type to int and appending a last line
return nc.InductionContentId;
should have been enough, but the done method of the ajax call totally failed to fire. Investigation with Fiddler revealed that the id value had indeed been returned with a content-type of text/json.
When you specify a content type for a jQuery ajax call, the same content type is used for the response. If the return value is an object, this will work fine. The object will be serialised as JSON and at the other end it will be parsed to provide the result object.
But for a simple value like an int, this comes unglued at the parsing stage. To resolve the problem, a return content type of plain text by using the dataType attribute.
$.ajax({
type: "post",
url: "api/InductionContent",
contentType: "text/json",
dataType: "text",
headers: headers,
data: JSON.stringify(ic)
}).done(function (result) {
var id = result;
//etc
});

Related

ASP.NET Core 2.2 Razor Pages - AJAX Call to page model not returning data

I am new to AJAX and cant even get the basics to function correctly.
I run an AJAX call from within a function in the javascript section from razor page but I cannot get the required string value to be returned.
The result I get in the alert box simply shows the HTML layout of the razor page as the message, as opposed to the actual string I want to return. I've spent hours trying to incorporate a X-CSRF-TOKEN in the header, but this makes no difference. I'm not sure the anti forgery token is required here and therefore the issue is before this step.
Razor Page:
$.ajax({
type: "GET",
url: "/Maps?handler=CanvasBackgroundImage",
contentType: 'application/json',
success: function (result) {
alert(result);
}
});
Page Model:
public JsonResult OnGetCanvasBackgroundImage()
{
var result = "test message";
return new JsonResult(result);
}
Below is the code that is now working for me. Thanks for everyone's input.
Razor Page (script section):
function getMapBackgroundImagePath() {
// Get the image path from AJAX Query to page model server side.
$.ajax({
type: "GET",
url: "/Maps/Create?handler=MapBackgroundImagePath",
contentType: "application/json",
data: { imageId: imageId, }, // Pass the imageId as a string variable to the server side method.
dataType: "json",
success: function (response) {
copyText = response;
// Run the function below with the value returned from the server.
renderCanvasWithBackgroundImage();
},
});
}
Page model:
public JsonResult OnGetMapBackgroundImagePath(string imageId)
{
// Get the image full path where the id = imageId string
int id = Convert.ToInt32(imageId);
var imagePath = _context.Image.Where(a => a.ID == id)
.Select(i => i.ImageSchedule);
// return the full image path back to js query in razor page script.
return new JsonResult(imagePath);
}

JSON encoded improperly when using KendoGrid POST payload

I am binding to a JSON data source, then rebinding after the user initiates a search based on filters on the page. The JSON payload is encoded improperly and nothing I've tried thus far seems to explain why.
If I could just add the correct JSON to the HTTP post, everything would work normally, and does with the $.ajax method listed first.
Using $.ajax call (works)
$.ajax(
{
url: '/api/DataProcessing',
type: "Post",
contentType: "application/json; charset=utf-8",
data: '' + JSON.stringify(searchObject),
dataType: 'json',
success: function (result) {
$(".kendoDataProcessing").data("kendoGrid").dataSource = new kendo.data.DataSource({ data: result });
$(".kendoDataProcessing").data("kendoGrid").dataSource.read();
$(".kendoDataProcessing").data("kendoGrid").refresh();
},
error: function (xhr, ajaxOptions, thrownError) {
alert('Status: ' + xhr.status + ', Error Thrown: ' + thrownError);
}
});
However, when I update the kendogrid data source in what I expect to send an equivalent payload, it encodes the JSON in an unexpected way (see below the code block for before and after HTTP requests captured in Fiddler. (encodes improperly)
$(".kendoDataProcessing").kendoGrid({
dataSource: {
transport: {
read: {
url: '/api/DataProcessing',
type: 'Post',
contentType: 'application/json; charset=utf-8',
data: '' + JSON.stringify(searchObject),
dataType: 'json',
}
},
pageSize: 25
},
height: 620,
sortable: true,
pageable: true,
filterable: true,
columns: [
{
field: "Client",
title: "Client Name",
width: 120
}, {
field: "Study",
title: "Study",
width: 100
}, {
field: "DataLogId",
title: "Batch Description",
width: 120
}, {
field: "Indicator",
title: "Indicator",
width: 100
}, {
field: "UserName",
title: "Username",
width: 110
}, {
field: "AssessmentPoint",
title: "Assessment Point",
width: 130
}, {
field: "DateStamp",
title: "Date Stamp",
width: 180
}]
});
**Expected JSON encoding (HTTP call created using $.ajax method) **
{"Client":"Choose a client...","Study":"Choose a study...","UserName":"Choose a user...","from":"","To":"","AssessmentPoint":"Choose an AP...","Indicator":"Choose an indicator...","DataLogId":""}
**Actual JSON encoding (HTTP call created using Kendogrid data source update and rebind **
0=%7B&1=%22&2=C&3=l&4=i&5=e&6=n&7=t&8=%22&9=%3A&10=%22&11=C&12=h&13=o&14=o&15=s&16=e&17=+&18=a&19=+&20=c&21=l&22=i&23=e&24=n&25=t&26=.&27=.&28=.&29=%22&30=%2C&31=%22&32=S&33=t&34=u&35=d&36=y&37=%22&38=%3A&39=%22&40=C&41=h&42=o&43=o&44=s&45=e&46=+&47=a&48=+&49=s&50=t&51=u&52=d&53=y&54=.&55=.&56=.&57=%22&58=%2C&59=%22&60=U&61=s&62=e&63=r&64=N&65=a&66=m&67 ... (continues)
It looks like it is making the json string into an array of sorts. So I tried with just a test string of "floof" and it encoded to "0=f&1=l&2=o&3=o&4=f"
Controller method called:
public HttpResponseMessage Post([FromBody]DataProcessingSearch dataProcessingSearch)
{
// dataProcessingSearch var is null (was passed oddly encoded)
}
Additional Details (search object)
var searchObject = new Object();
searchObject.Client = $('#ClientList').val();
searchObject.Study = $('#StudyList').val();
searchObject.Site = $('#SiteList').val();
searchObject.UserName = $('#UserList').val();
searchObject.from = $('#beginSearch').val();
searchObject.To = $('#endSearch').val();
searchObject.AssessmentPoint = $('#AssessmentPointList').val();
searchObject.Indicator = $('#IndicatorList').val();
searchObject.DataLogId = $('#DataLogIdText').val();
demo: http://so.devilmaycode.it/json-encoded-improperly-when-using-kendogrid-post-payload
function searchObject(){
return {
Client : $('#ClientList').val(),
Study : $('#StudyList').val(),
Site : $('#SiteList').val(),
UserName : $('#UserList').val(),
from : $('#beginSearch').val(),
To : $('#endSearch').val(),
AssessmentPoint : $('#AssessmentPointList').val(),
Indicator : $('#IndicatorList').val(),
DataLogId : $('#DataLogIdText').val()
}
}
// i have putted the dataSource outside just for best show the piece of code...
var dataSource = new kendo.data.DataSource({
transport: {
read : {
// optional you can pass via url
// the custom parameters using var query = $.param(searchObject())
// converting object or array into query sring
// url: "/api/DataProcessing" + "?" + query,
url: "/api/DataProcessing",
dataType: "json",
// no need to use stringify here... kendo will take care of it.
// also there is a built-in function kendo.stringify() to use where needed.
data: searchObject
},
//optional if you want to modify something before send custom data...
/*parameterMap: function (data, action) {
if(action === "read") {
// do something with the data example add another parameter
// return $.extend({ foo : bar }, data);
return data;
}
}*/
}
});
$(".kendoDataProcessing").kendoGrid({
dataSource: dataSource,
...
});
comments are there just for better explanation you can completely remove it if don't need it. the code is fully working as is anyway.
doc: http://docs.telerik.com/kendo-ui/api/wrappers/php/Kendo/Data/DataSource
What May be the wrong perception:-
1.The Json() method accepts C# objects and serializes them into JSON
strings. In our case we want to return an array of JSON objects; to
do that all you do is pass a list of objects into Json().
public JsonResult GetBooks()
{
return Json(_dataContext.Books);
}
Can you identify what is wrong with the above method? If you didn't already know, the above method will fail at runtime with a "circular reference" exception.
Note: try to return Json, HttpResponse may serialize the data in such a way that it is not acceptable by Kendo Grid. this has happened with me in my project.
Try this Approach:-
Now lets create instances of them in a JsonResult action method.
public JsonResult GetFooBar()
{
var foo = new Foo();
foo.Message = "I am Foo";
foo.Bar = new Bar();
foo.Bar.Message = "I am Bar";
return Json(foo);
}
This action method would return the following JSON:
{
"Message" : "I am Foo",
"Bar" : {
"Message" : "I am Bar"
}
}
In this example we got exactly what we expected to get. While serializing foo it also went into the Bar property and serialized that object as well. However, let's mix it up a bit and add a new property to Bar.
I remember working with a kendo grid in the past. Solution back then was returning jsonp. (needed to work crossdomain not sure if it does in your case)
Suggestion change you controller method to return sjonp by decorating you method with a JsonpFilterAttribute. Something like so:
[JsonpFilter]
public JsonResult DoTheThing(string data, string moreData)
{
return new JsonResult
{
Data = FetchSomeData(data, moreData)
};
}
Then in de Kendo grid try use http://demos.telerik.com/kendo-ui/datasource/remote-data-binding.
For the Jsonpfilter attribute first look at here or else here.

Accessing ServiceStack Authenticated Service using Ajax

I've been working through a simple API example, a modified version of the ServiceStack Hello World example with authentication. The goal of the proof of concept is to create an a RESTful API that contains services requiring authentication accessible entirely through Ajax from several different web projects.
I've read the wiki for, and implemented, Authentication and authorization and implementing CORS (many, results [sorry, not enough cred to point to the relevant link]). At this point, my Hello service can authenticate using a custom authentication mechanism which is over-riding CredentialsAuthProvider and a custom user session object. I've created, or borrowed, rather, a simple test application (an entirely separate project to simulate our needs) and can authenticate and then call into the Hello service, passing a name, and receive a 'Hello Fred' response through a single browser session. That is, I can call the /auth/credentials path in the url, passing the username and id, and receive a proper response. I can then update the url to /hello/fred and receive a valid response.
My breakdown in understanding is how to implement the authentication for all ajax calls. My initial login, below, works fine. No matter what I do, my attempt to call the authenticated service via ajax, I either receive a OPTIONS 404 error or Not Found error, or Origin http // localhost:12345 (pseudo-link) is not allowed by Access-Control-Allow-Origin, etc.
Do I need to go this route?
Sorry if this is confusing. I can provide greater details if required, but think this might be sufficient help the knowledgeable to help my lack of understanding.
function InvokeLogin() {
var Basic = new Object();
Basic.UserName = "MyUser";
Basic.password = "MyPass";
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify(Basic),
url: "http://localhost:58795/auth/credentials",
success: function (data, textStatus, jqXHR) {
alert('Authenticated! Now you can run Hello Service.');
},
error: function(xhr, textStatus, errorThrown) {
var data = $.parseJSON(xhr.responseText);
if (data === null)
alert(textStatus + " HttpCode:" + xhr.status);
else
alert("ERROR: " + data.ResponseStatus.Message + (data.ResponseStatus.StackTrace ? " \r\n Stack:" + data.ResponseStatus.StackTrace : ""));
}
});
}
EDIT:
Based on the responses and the link provided by Stefan, I've made a couple of changes:
My Config (Note: I'm using custom authentication and session object and that is all working correctly.)
public override void Configure(Funq.Container container)
{
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
}));
base.SetConfig(new EndpointHostConfig
{
GlobalResponseHeaders = {
{ "Access-Control-Allow-Origin", "*" },
{ "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS" },
{ "Access-Control-Allow-Headers", "Content-Type, Authorization" },
},
DefaultContentType = "application/json"
});
Plugins.Add(new CorsFeature());
this.RequestFilters.Add((httpReq, httpRes, requestDto) =>
{
//Handles Request and closes Responses after emitting global HTTP Headers
if (httpReq.HttpMethod == "OPTIONS")
httpRes.EndRequest(); // extension method
});
Routes
.Add<Hello>("/Hello", "GET, OPTIONS");
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
}
My Simple Hello Service
[EnableCors]
public class HelloService : IService
{
[Authenticate]
public object GET(Hello request)
{
Looks strange when the name is null so we replace with a generic name.
var name = request.Name ?? "John Doe";
return new HelloResponse { Result = "Hello, " + name };
}
}
After making the login call, above, my subsequent call the Hello service is now yielding a 401 error, which is progress, though not where I need to be. (The Jquery.support.cors= true is set in my script file.)
function helloService() {
$.ajax({
type: "GET",
contentType: "application/json",
dataType: "json",
url: "http://localhost:58795/hello",
success: function (data, textStatus, jqXHR) {
alert(data.Result);
},
error: function (xhr, textStatus, errorThrown) {
var data = $.parseJSON(xhr.responseText);
if (data === null)
alert(textStatus + " HttpCode:" + xhr.status);
else
alert("ERROR: " + data.ResponseStatus.Message +
(data.ResponseStatus.StackTrace ? " \r\n Stack:" + data.ResponseStatus.StackTrace : ""));
}
});
}
Again, this works in the RESTConsole if I first make the call to /auth/credentials properly and then follow that up with a call to /hello.
FINAL EDIT
Following Stefan's advise, below, including many other links, I was finally able to get this working. In addition to Stefan's code, I had to make one additional modification:
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization"));
On to the next challenge: Updating Jonas Eriksson's CustomAuthenticateAttibute code (which appears to be using an older version of ServiceStack as a couple of functions are no longer available.
THANKS AGAIN STEFAN!!
this code works for me, based on the Wiki documentation Custom authentication and authorization
Code is based also in the blog post from Community Resources
CORS BasicAuth on ServiceStack with custom authentication
For Basic Authentication, a custom provider
public class myAuthProvider : BasicAuthProvider
{
public myAuthProvider() : base() { }
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
if (userName == "admin" && password == "test")
return true;
else
return false;
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
//Fill the IAuthSession with data which you want to retrieve in the app
// the base AuthUserSession properties e.g
session.FirstName = "It's me";
//...
// derived CustomUserSession properties e.g
if(session is CustomUserSession)
((CustomUserSession) session).MyData = "It's me";
//...
//Important: You need to save the session!
authService.SaveSession(session, SessionExpiry);
}
}
public class CustomUserSession : AuthUserSession
{
public string MyData { get; set; }
}
In AppHost
using System.Web;
using ServiceStack; // v.3.9.60 httpExtensions methods, before in ServiceStack.WebHost.Endpoints.Extensions;
using ....
AppHost.Configure
public override void Configure(Container container)
{
SetConfig(new ServiceStack.WebHost.Endpoints.EndpointHostConfig
{
DefaultContentType = ContentType.Json
..
// remove GlobalResponseHeaders because CordFeature adds the CORS headers to Config.GlobalResponseHeaders
});
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization")); //Registers global CORS Headers
this.RequestFilters.Add((httpReq, httpRes, requestDto) =>
{
if (httpReq.HttpMethod == "OPTIONS")
httpRes.EndRequestWithNoContent(); // v 3.9.60 httpExtensions method before httpRes.EndServiceStackRequest();
});
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(() => new CustomUserSession(), // OR the AuthUserSession
new IAuthProvider[] {
new myAuthProvider(),
}) { HtmlRedirect = null }); // Redirect on fail
HtmlRedirect answer
Routes.Add<TestRequest>("/TestAPI/{Id}", "POST,GET, OPTIONS");
....
}
In Service
[Authenticate]
public class TestAPI : Service
{
...
}
in javascript
jQuery.support.cors = true;
function make_base_auth(user, password) {
var tok = user + ':' + password;
var hash = btoa(tok);
return "Basic " + hash;
}
Login first
function Authenticate() {
$.ajax({
type: 'Post',
contentType: 'application/json',
url: serverIP + 'Auth',
cache: false,
async: false,
data: {},
dataType: "json",
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", make_base_auth(username, password));
},
success: function (response, status, xhr) {
localStorage.sessionId = data.SessionId;
var UserName = response.userName;
},
error: function (xhr, err) {
alert(err);
}
});
}
and request
function DoTest() {
var TestRequest = new Object();
TestRequest.name = "Harry Potter";
TestRequest.Id = 33;
var username = "admin";
var password = "test";
$.ajax({
type: 'Post',
contentType: 'application/json',
cache: false,
async: false,
url: serverIP + '/TestAPI/'+ TestRequest.Id,
data: JSON.stringify(TestRequest),
dataType: "json",
beforeSend: function (xhr) {
xhr.setRequestHeader("Session-Id", localStorage.sessionId);
},
success: function (response, status, xhr) {
var s= response.message;
},
error: function (xhr, err) {
alert(xhr.statusText);
}
});
}
these questions here and here are helpful.
Also this answer for CredentialsAuthProvider, in case we can use cookies and sessions.

How can I post data as form data instead of a request payload?

In the code below, the AngularJS $http method calls the URL, and submits the xsrf object as a "Request Payload" (as described in the Chrome debugger network tab). The jQuery $.ajax method does the same call, but submits xsrf as "Form Data".
How can I make AngularJS submit xsrf as form data instead of a request payload?
var url = 'http://somewhere.com/';
var xsrf = {fkey: 'xsrf key'};
$http({
method: 'POST',
url: url,
data: xsrf
}).success(function () {});
$.ajax({
type: 'POST',
url: url,
data: xsrf,
dataType: 'json',
success: function() {}
});
The following line needs to be added to the $http object that is passed:
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
And the data passed should be converted to a URL-encoded string:
> $.param({fkey: "key"})
'fkey=key'
So you have something like:
$http({
method: 'POST',
url: url,
data: $.param({fkey: "key"}),
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
})
From: https://groups.google.com/forum/#!msg/angular/5nAedJ1LyO0/4Vj_72EZcDsJ
UPDATE
To use new services added with AngularJS V1.4, see
URL-encoding variables using only AngularJS services
If you do not want to use jQuery in the solution you could try this. Solution nabbed from here https://stackoverflow.com/a/1714899/1784301
$http({
method: 'POST',
url: url,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(obj) {
var str = [];
for(var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: xsrf
}).success(function () {});
I took a few of the other answers and made something a bit cleaner, put this .config() call on the end of your angular.module in your app.js:
.config(['$httpProvider', function ($httpProvider) {
// Intercept POST requests, convert to standard form encoding
$httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
$httpProvider.defaults.transformRequest.unshift(function (data, headersGetter) {
var key, result = [];
if (typeof data === "string")
return data;
for (key in data) {
if (data.hasOwnProperty(key))
result.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
}
return result.join("&");
});
}]);
As of AngularJS v1.4.0, there is a built-in $httpParamSerializer service that converts any object to a part of a HTTP request according to the rules that are listed on the docs page.
It can be used like this:
$http.post('http://example.com', $httpParamSerializer(formDataObj)).
success(function(data){/* response status 200-299 */}).
error(function(data){/* response status 400-999 */});
Remember that for a correct form post, the Content-Type header must be changed. To do this globally for all POST requests, this code (taken from Albireo's half-answer) can be used:
$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
To do this only for the current post, the headers property of the request-object needs to be modified:
var req = {
method: 'POST',
url: 'http://example.com',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: $httpParamSerializer(formDataObj)
};
$http(req);
You can define the behavior globally:
$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
So you don't have to redefine it every time:
$http.post("/handle/post", {
foo: "FOO",
bar: "BAR"
}).success(function (data, status, headers, config) {
// TODO
}).error(function (data, status, headers, config) {
// TODO
});
As a workaround you can simply make the code receiving the POST respond to application/json data. For PHP I added the code below, allowing me to POST to it in either form-encoded or JSON.
//handles JSON posted arguments and stuffs them into $_POST
//angular's $http makes JSON posts (not normal "form encoded")
$content_type_args = explode(';', $_SERVER['CONTENT_TYPE']); //parse content_type string
if ($content_type_args[0] == 'application/json')
$_POST = json_decode(file_get_contents('php://input'),true);
//now continue to reference $_POST vars as usual
These answers look like insane overkill, sometimes, simple is just better:
$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
"&password=" + encodeURIComponent(password) +
"&grant_type=password"
).success(function (data) {
//...
You can try with below solution
$http({
method: 'POST',
url: url-post,
data: data-post-object-json,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(obj) {
var str = [];
for (var key in obj) {
if (obj[key] instanceof Array) {
for(var idx in obj[key]){
var subObj = obj[key][idx];
for(var subKey in subObj){
str.push(encodeURIComponent(key) + "[" + idx + "][" + encodeURIComponent(subKey) + "]=" + encodeURIComponent(subObj[subKey]));
}
}
}
else {
str.push(encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]));
}
}
return str.join("&");
}
}).success(function(response) {
/* Do something */
});
Create an adapter service for post:
services.service('Http', function ($http) {
var self = this
this.post = function (url, data) {
return $http({
method: 'POST',
url: url,
data: $.param(data),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
}
})
Use it in your controllers or whatever:
ctrls.controller('PersonCtrl', function (Http /* our service */) {
var self = this
self.user = {name: "Ozgur", eMail: null}
self.register = function () {
Http.post('/user/register', self.user).then(function (r) {
//response
console.log(r)
})
}
})
There is a really nice tutorial that goes over this and other related stuff - Submitting AJAX Forms: The AngularJS Way.
Basically, you need to set the header of the POST request to indicate that you are sending form data as a URL encoded string, and set the data to be sent the same format
$http({
method : 'POST',
url : 'url',
data : $.param(xsrf), // pass in data as strings
headers : { 'Content-Type': 'application/x-www-form-urlencoded' } // set the headers so angular passing info as form data (not request payload)
});
Note that jQuery's param() helper function is used here for serialising the data into a string, but you can do this manually as well if not using jQuery.
var fd = new FormData();
fd.append('file', file);
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});
Please checkout!
https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
For Symfony2 users:
If you don't want to change anything in your javascript for this to work you can do these modifications in you symfony app:
Create a class that extends Symfony\Component\HttpFoundation\Request class:
<?php
namespace Acme\Test\MyRequest;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ParameterBag;
class MyRequest extends Request{
/**
* Override and extend the createFromGlobals function.
*
*
*
* #return Request A new request
*
* #api
*/
public static function createFromGlobals()
{
// Get what we would get from the parent
$request = parent::createFromGlobals();
// Add the handling for 'application/json' content type.
if(0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/json')){
// The json is in the content
$cont = $request->getContent();
$json = json_decode($cont);
// ParameterBag must be an Array.
if(is_object($json)) {
$json = (array) $json;
}
$request->request = new ParameterBag($json);
}
return $request;
}
}
Now use you class in app_dev.php (or any index file that you use)
// web/app_dev.php
$kernel = new AppKernel('dev', true);
// $kernel->loadClassCache();
$request = ForumBundleRequest::createFromGlobals();
// use your class instead
// $request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Just set Content-Type is not enough, url encode form data before send.
$http.post(url, jQuery.param(data))
I'm currently using the following solution I found in the AngularJS google group.
$http
.post('/echo/json/', 'json=' + encodeURIComponent(angular.toJson(data)), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).success(function(data) {
$scope.data = data;
});
Note that if you're using PHP, you'll need to use something like Symfony 2 HTTP component's Request::createFromGlobals() to read this, as $_POST won't automatically loaded with it.
AngularJS is doing it right as it doing the following content-type inside the http-request header:
Content-Type: application/json
If you are going with php like me, or even with Symfony2 you can simply extend your server compatibility for the json standard like described here: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html
The Symfony2 way (e.g. inside your DefaultController):
$request = $this->getRequest();
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
$data = json_decode($request->getContent(), true);
$request->request->replace(is_array($data) ? $data : array());
}
var_dump($request->request->all());
The advantage would be, that you dont need to use jQuery param and you could use AngularJS its native way of doing such requests.
Complete answer (since angular 1.4). You need to include de dependency $httpParamSerializer
var res = $resource(serverUrl + 'Token', { }, {
save: { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
});
res.save({ }, $httpParamSerializer({ param1: 'sdsd', param2: 'sdsd' }), function (response) {
}, function (error) {
});
In your app config -
$httpProvider.defaults.transformRequest = function (data) {
if (data === undefined)
return data;
var clonedData = $.extend(true, {}, data);
for (var property in clonedData)
if (property.substr(0, 1) == '$')
delete clonedData[property];
return $.param(clonedData);
};
With your resource request -
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
This isn't a direct answer, but rather a slightly different design direction:
Do not post the data as a form, but as a JSON object to be directly mapped to server-side object, or use REST style path variable
Now I know neither option might be suitable in your case since you're trying to pass a XSRF key. Mapping it into a path variable like this is a terrible design:
http://www.someexample.com/xsrf/{xsrfKey}
Because by nature you would want to pass xsrf key to other path too, /login, /book-appointment etc. and you don't want to mess your pretty URL
Interestingly adding it as an object field isn't appropriate either, because now on each of json object you pass to server you have to add the field
{
appointmentId : 23,
name : 'Joe Citizen',
xsrf : '...'
}
You certainly don't want to add another field on your server-side class which does not have a direct semantic association with the domain object.
In my opinion the best way to pass your xsrf key is via a HTTP header. Many xsrf protection server-side web framework library support this. For example in Java Spring, you can pass it using X-CSRF-TOKEN header.
Angular's excellent capability of binding JS object to UI object means we can get rid of the practice of posting form all together, and post JSON instead. JSON can be easily de-serialized into server-side object and support complex data structures such as map, arrays, nested objects, etc.
How do you post array in a form payload? Maybe like this:
shopLocation=downtown&daysOpen=Monday&daysOpen=Tuesday&daysOpen=Wednesday
or this:
shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday
Both are poor design..
This is what I am doing for my need, Where I need to send the login data to API as form data and the Javascript Object(userData) is getting converted automatically to URL encoded data
var deferred = $q.defer();
$http({
method: 'POST',
url: apiserver + '/authenticate',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
transformRequest: function (obj) {
var str = [];
for (var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: userData
}).success(function (response) {
//logics
deferred.resolve(response);
}).error(function (err, status) {
deferred.reject(err);
});
This how my Userdata is
var userData = {
grant_type: 'password',
username: loginData.userName,
password: loginData.password
}
The only thin you have to change is to use property "params" rather than "data" when you create your $http object:
$http({
method: 'POST',
url: serviceUrl + '/ClientUpdate',
params: { LangUserId: userId, clientJSON: clients[i] },
})
In the example above clients[i] is just JSON object (not serialized in any way). If you use "params" rather than "data" angular will serialize the object for you using $httpParamSerializer: https://docs.angularjs.org/api/ng/service/$httpParamSerializer
Use AngularJS $http service and use its post method or configure $http function.

Ajax json post to Controller across domains, "not allowed by" Access-Control-Allow-Headers

I create a simple MVC Controller action, that takes some json data - then return true or false.
[AllowCrossSiteJson]
public JsonResult AddPerson(Person person)
{
//do stuff with person object
return Json(true);
}
I call it from javascript:
function saveData(person) {
var json = $.toJSON(person); //converts person object to json
$.ajax({
url: "http://somedomain.com/Ajax/AddPerson",
type: 'POST',
dataType: 'json',
data: json,
contentType: 'application/json; charset=utf-8',
success: function (data) {
alert("ok");
}
});
}
Everything works as long as I am on the same domain, but as soon as I call it from another domain, I run into problems.
On the controller is an action filter "AllowCrossSiteJson" that sets the header "Access-Control-Allow-Origin" to "*", allowing any origin to access the controller action.
public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
base.OnActionExecuting(filterContext);
}
}
However - I then get this error in firebug, when calling across domains:
OPTIONS http://somedomain.com/Ajax/AddPerson?packageId=3 500 (Internal Server Error)
XMLHttpRequest cannot load http://somedomain.com/Ajax/AddPerson. Request header field Content-Type is not allowed by Access-Control-Allow-Headers.
What is wrong here?
I have been looking through possible solutions for hours, and it seems to be something to do with jquery using OPTIONS (not POST as I would expect).
If that is indeed the problem, how can I fix that?
To fix the Access-Control-Allow-Origin error, you need to include the following header in your response:
Access-Control-Allow-Headers: Content-Type
Basically, any "non-simple" header needs to be included as a comma-delimited list in the header above. Check out the CORS spec for more details:
http://www.w3.org/TR/cors/
"Content-Type" needs to be included because "application/json" does not match the values defined here:
http://www.w3.org/TR/cors/#terminology
I'd recommend you JSONP, it's the only really cross browser and reliable solution for cross domain AJAX. So you could start by writing a custom action result that will wrap the JSON response with a callback:
public class JsonpResult : ActionResult
{
private readonly object _obj;
public JsonpResult(object obj)
{
_obj = obj;
}
public override void ExecuteResult(ControllerContext context)
{
var serializer = new JavaScriptSerializer();
var callbackname = context.HttpContext.Request["callback"];
var jsonp = string.Format("{0}({1})", callbackname, serializer.Serialize(_obj));
var response = context.HttpContext.Response;
response.ContentType = "application/json";
response.Write(jsonp);
}
}
and then:
public ActionResult AddPerson(Person person)
{
return new JsonpResult(true);
}
and finally perform the cross domain AJAX call:
$.ajax({
url: 'http://somedomain.com/Ajax/AddPerson',
jsonp: 'callback',
dataType: 'jsonp',
data: { firstName: 'john', lastName: 'smith' },
success: function (result) {
alert(result);
}
});

Resources