Pass array of objects containing arrays to MVC action via javascript - javascript

Say I have a javascript object like:
function Parent(n, c) {
this.Name = n;
this.Children = c;
}
var p = new Parent("asdf", [1,2,3]);
And I want to pass an array of the parent object and its children to an MVC 4 controller via JSON.
How would I go about formatting the ajax request? I've seen quite a few other questions along these lines, although none that use an array as a member variable.
This is what I have so far:
var parents = [];
parents.push(new Parent("qwer", "child1"));
parents.push(new Parent("sdfg", 12345));
parents.push(new Parent("zxcv", [4,5,6]));
$.ajax({
url: MakeUrl("Ctlr/Action"),
type: "POST",
contentType: 'application/json;charset=utf-8',
data: JSON.stringify({
parents : parents
}),
success: function (data, state, xhr) {
$("#someDiv").html(data);
},
error: function (xhr, state, err) {
Utility.displayError(xhr.reponseText);
}
});
The result of stringify actually looks reasonable:
"{"parents":[{"Name":"qwer","Value":"child1"}, {"Name":"sdfg","Value":12345}, {"Name":"zxcv","Value":[4,5,6]}]}"
Here is the Controller action:
public ActionResult Action(IEnumerable<Parent> parents) {
...
}
And in case it's relevant, the server-side Parent object:
public class Parent {
public string Name { get; set; }
public object Children { get; set; }
}
Children is an object because it is sometimes another data type - I realize this may be a slight code-smell, but the ultimate function of this class is to pass arbitrary parameters to our reporting engine.
The simple data types seem to work just fine in this way (date, integer, string, etc), but the Children array just comes through as {object}, which as far as I can tell is not even a string, but some generic System object. Is there a way to do this in MVC without resorting to, say, pushing it into a string and parsing it out?

For now, I've settled for submitting a flat list via javascript, which is then built out on the server side.
The javascript:
var parents = [];
parents.push(new Parent("asdf", "qwer"));
parents.push(new Parent("zxcv", 123456));
[4,5,].forEach(function (e, i) {
params.push(new Parent("Children[" + i + "]", e));
});
Which looks like this after JSON.stringify:
[{"Name":"asdf","Value":"qwer"},{"Name":"zxcv","Value":123456},{"Name":"Children[0]","Value":4},{"Name":"Children[1]","Value":5},{"Name":"Children[2]","Value":6}]
And is then un-flattened in the Controller via the following function:
private IEnumerable<Parent> Unzip(IEnumerable<Parent> parameters) {
var unzipped = new Dictionary<string, Parent>();
var r = new Regex(#"(.*)\[.*\]");
foreach (var p in parameters)
{
var match = r.Match(p.Name.ToString());
if (match.Success)
{
var name = match.Groups[1].Value;
if (!unzipped.ContainsKey(name))
unzipped.Add(name, new Parent() { Name = name, Value = new List<object>() { } });
((List<object>)(unzipped[name].Value)).Add(p.Value);
}
else
unzipped.Add(p.Name, p);
}
return unzipped.Values;
}

Related

Post knockout.js viewmodel to .Net Core (2.0) MVC Controller

Overview:
Currently, I try to create an ASP.NET Core MVC website which gets data from the server on page load. After that, the page uses knockout.js to maintain a viewmodel to update the data sent from the server (in the first step). Finally, there is a button which sends the edited data back to the server (through an AJAX request). But the corresponding value in my server method is always empty.
Current Approach:
First, there is my model class.
public class Order
{
public string Name { get; set; }
public int TemplateId { get; set; }
public decimal Price { get; set; }
public int Contingent { get; set; }
public int MaximumOrder { get; set; }
public int UserOrderCount { get; set; }
public Order() { }
public Order(SnackOffer offer)
{
Name = offer.Template.Name;
TemplateId = offer.Template.SnackTemplateId;
Price = offer.Price;
}
}
In my view I use the model class as a List (List) and load the data like this:
var model = new viewModel();
#(Json.Serialize(Model.Offers)).forEach(function (item, index) {
model.offers.push({
name: item.name,
templateId: item.templateId,
contingent: ko.observable(item.contingent),
userOrderCount: ko.observable(item.userOrderCount),
price: item.price,
maximumOrder: item.maximumOrder
});
})
The viewModel is defined like this (shortend for readability):
function viewModel() {
var self = this;
self.offers = ko.observableArray();
}
Now after someone hits the mentioned update button I call this javascript function:
self.ConfirmOrder = function () {
var data = ko.toJSON(self.offers);
$('#overlay').css('display', 'block');
$.ajax({
type: 'post',
url: '/Snack/ConfirmOrder',
data: data,
contentType: 'application/json; charset=utf-8',
success: function (result) {
$('#overlay').css('display', 'none');
},
error: function (result) {
console.log(result);
$('#overlay').css('display', 'none');
}
});
}
Here you can see that I use the ko.toJSON method to convert the observableArray to a JSON String and use this string as the data argument for the ajax post.
The ConfirmOrder method in the MVC Controller looks like this currently:
[HttpPost]
public void ConfirmOrder(List<OrderSnackViewModel.Order> offers)
{
foreach (var item in offers)
{
_logger.LogInformation(item.Name);
}
}
There is no logic yet cause the offers list is created (so not null) but the count of list items is always 0.
In a concret example this is the JSON string I receive from the server while load the view:
[{"name":"Test","templateId":1,"price":1.94,"contingent":4,"maximumOrder":7,"userOrderCount":0},{"name":"Test 1","templateId":2,"price":1.50,"contingent":30,"maximumOrder":7,"userOrderCount":0}]
And this is the string i produce with the ajax call:
[{"name":"Test","templateId":1,"contingent":4,"userOrderCount":0,"price":1.94,"maximumOrder":7},{"name":"Test 1","templateId":2,"contingent":30,"userOrderCount":0,"price":1.5,"maximumOrder":7}]
What have I tried already:
I have tried to use FromBody in my MVC Controller
I have tried to prefix the JSON output with the name offer so my controller method can map the JSON string items to the object. (e.g. data = '{ "offers":' + data + '}'; )
I have tried to rename the Properties of my model to match exactly the JSON names. So I renamed Name to name and Contingent to contingent and so on.
I have tried to use an array instead of the List in the MVC controller action
Is there a master somewhere which can help me with this problem?
(If you need some more information please do not hesitate to ask)
Thanks to the input of #adiga I have found the solutions for (my) error.
[HttpPost]
public JsonResult ConfirmOrder([FromBody]List<Order> offers)
{
_logger.LogInformation("COUNT -> " + offers.Count());
foreach (var item in offers)
{
_logger.LogInformation(item.Name);
}
}
This is the working solution. I thought I already have tried it but maybe I have forgoten the content-type for the ajax post.
var data = ko.toJSON(self.offers);
$('#overlay').css('display', 'block');
$.ajax({
type: 'post',
url: '/Snack/ConfirmOrder',
data: data,
contentType: 'application/json; charset=utf-8',
success: function (result) {
//location.reload();
$('#overlay').css('display', 'none');
},
error: function (result) {
console.log(result);
$('#overlay').css('display', 'none');
}
});
Thanks again for all responses!

$http Call to Web API 2 Not Passing Parameter

This is my C# WebAPI2 controller, which gets hit:
[HttpGet, Route("bycaseidlist/{idArray}")]
public async Task<IHttpActionResult> GetByCaseIdList([FromUri] List<int> idArray)
This is the call:
var idArray = [4,4,2,4];
var url = baseUrl + 'api/cases/bycaseidlist/' + idArray ;
$http.get(url)
The problem is that the API doesn't get the array, it gets ...this:
In other words an array with one value: 0. Why is this happening? How do I fix it? It seems to be in-line with this answer, but it doesn't work. Should I pass it in the body? I feel like I am missing something obvious.
Get ActionMethods can take objects as arguments. However, the default behavior is to look at the body when the parameter is not a .net primitive. In order to force the action method to use a model binder to read the object data from the request, the parameter can be decorated with the [FromUri] or [ModelBinder] attributes. (Note there are other ways to do this that include doing parameter binding rules but that is probably overkill for what you are trying to accomplish here). Here is an implementation that solves the original problem that you were posing.
<script type="text/javascript">
var ajaxCall = function (myArry) {
var ajaxProperties = {};
ajaxProperties.url = "/api/Mul/Mutiply";
ajaxProperties.type = "Get";
ajaxProperties.data = {};
ajaxProperties.data.numbers = myArry;
ajaxProperties.contentType = "application/json";
console.log(ajaxProperties);
ajaxProperties.success = function (data) {
console.log(data);
}
ajaxProperties.error = function (jqXHR) {
console.log(jqXHR);
};
$.ajax(ajaxProperties);
};
var getData = function (e) {
var myArry = new Array();
myArry.push($('input[name=num1').val());
myArry.push($('input[name=num2').val());
ajaxCall(myArry);
return false;
};
</script>
Controller
[HttpGet]
public IHttpActionResult Multiply([FromUri] int[] numbers)
{
int result = 0;
if(numbers.Length > 0)
{
result = 1;
foreach (int i in numbers)
{
result = result * i;
}
}
return Ok(result);
}
}
I think my mistake was using Get. I might be remembering incorrectly (someone confirm if you know offhand), but Get might not be able to take objects as arguments. Anyway, I changed the method to POST and then changed the param to be sent in the request body, rather than the url. It now works. Here is the working code:
[HttpPost, Route("bycaseidlist")]
public async Task<IHttpActionResult> PostByCaseIdList([FromBody] int[] sqlCaseIdArray)
and the call itself:
function runDbCall(url, sqlCaseIdArray){
return $http({
method: 'POST',
url: url,
data: sqlCaseIdArray
});
}
runDbCall(url, sqlCaseIdArray)
I will come back to this when I figure out if the problem was Get not being able to take objects, but I thought it could in url, just not in body...need to clarify. If someone posts an answer just on that part, I will accept, since that's probably the root of the prob.

Sitecore SPEAK - set searchdatasource root programmatically

I have been playing with SPEAK dialogs lately. So far I like it, but have stumbled across an issue. I have passed with an itemID in the url parameters and I want to display the children of this item in a listcontrol.
My approach was to create a SearchDataSource and set the field "rootItemId" via javascript. This doesn't seem to work. Is there a way to access the SearchDataSource's rootItemId in the PageCode?
Another way I've been using lately is to use Anders Laub's JSON Datasource control here. http://laubplusco.net/creating-simple-sitecore-speak-json-datasource/.
From the JavaScript PageCode you can then do a Ajax call and append in JSON result items to populate your listcontrol, where the listcontrols is bound to the JSON datasources Json property.
$.ajax({
url: "/api/sitecore/RolePermissions/GetAllRoles",
type: "POST",
context: this,
success: function (data) {
var json = jQuery.parseJSON(data);
for (var i = 0; i < json.length; i++) {
var obj = json[i];
this.JsonDS.add(obj);
}
}
});
I managed to do this with query. In pageCode:
public class SelectTitle : PageCodeBase
{
//Fetches DataSource as rendering
public Rendering DataSource { get; set; }
public override void Initialize()
{
var articleid = WebUtil.GetQueryString("article");
if (!String.IsNullOrEmpty(articleid))
{
//Set the query.
this.DataSource.Parameters["query"] =
String.Format("fast:/some/path/*[##id = '{0}']/Elements/*[##templateid = '{1}']", articleid, '{guid}');
}
}
}

How to parse a JSON object ? (From server-side to client-side...javascript)

I have a jquery interacting with a server side Web Method. The Web method accepts a string 'memID' from the jquery and executes SQL queries based on it. Then I create a class:-
public class Member
{
// Objects of Member. //
public string id { get; set; }
public string HPCode { get; set; }
public string OPfromDate { get; set; }
public string OPthruDate { get; set; }
public Member(string ID, List<string> hpCode, List<string> opfromDate, List<string> opthruDate)
{
id = ID;
for (int j = 0; j < hpCode.Count; j++){ HPCode = HPCode + (hpCode)[j] + '*' };
for (int j = 0; j < opfromDate.Count; j++){OPfromDate = OPfromDate + (opfromDate)[j] + '*' };
for (int j = 0; j < opthruDate.Count; j++){OPthruDate = OPthruDate+ (opthruDate)[j] + '*' };
}
}
This class is used to return the results of my SQL query to the client side:-
return JsonConvert.SerializeObject(member);
I used breakpoints and checked on the client side, indeed it is getting the return values. However I am not sure about what is the best way to parse these values and store them in variables on my javascript side so i can use them for client-side functionalities.
// Ajax function that sends member ID from client side to server side without a post back.
function ajax()
{
$.ajax
({
url: 'Default.aspx/MyMethod',
type: 'POST',
dataType: "json",
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({ memID: mem_ID }),
success: onSuccess,
fail : onFail
});
}
// If client-to-server communication is successful.
function onSuccess(data)
{
confirm(data.d);
var returnedstring = (data.d);
var jsondata = $.parseJSON(data.d);
}
Now how do I parse the 'data' ?? is there an eval function? or parse function?
Does anybody have any web examples? before this I was only passing back 1 value to my client side, so it was easy to get, now i am confused with multiple values.
UPDATE:-
I tried doing this on my javascript side:-
var returnedstring = (data.d);
var member = data.d.id;
var Hp = data.d.HPCode;
however when I use breakpoints and hover over them with my mouse, i get member and HP as undefined, however the returnedstring has all the correct values.. ... any idea?
SOLUTION (I couldn't figure out the other way suggested in the answers, but this works for me) :-
function onSuccess(data)
{
// stores entire return in one string.
var returnedstring = (data.d);
// parses json returned data
var jsondata = $.parseJSON(data.d);
var member = jsondata.id;
var HpCode = jsondata.HPCode;
}
Because you're using dataType: "json", data is already parsed out of the JSON string. That's why you're able to access data.d in Javascript.
To access the members, you can do something like this:
console.log(data.d.HPCode);

Jquery $.ajax() call to pass associative array with other params to MVC controller failing

I have a generic dispatcher that takes in parameters and then make $.ajax()
As you can see, I am looping through the parameters to build the data.
function dispatch(controller, action, param1, param2,...) {
var args = '', pg = 1, id = '';
var l = arguments.length;
var data = {};
for (var i = 2; i < arguments.length; i += 2) {
data[arguments[i]] = arguments[i + 1];
}
$.ajax({
type: "POST",
url: "/" + (controller + "/" + action,
data: data,
dataType: "json",
success: function (res) {
alert('success');
},
error: function (xhr, status, error) {
alert('error!');
}
});
}
This is how I call:
dispatch("XYZ", "AddGuests", "id", 10, "name", "Bob");
Everything works as expected and I get all parameters back in my MVC 3 controllers.
Now I need to pass multidimentional associative array as parameter to my controller.
I want to use the single generic dispatcher I use for other calls.
But somehow I am getting null for the associative array parameter.
Basically I want to post a guests array to my controller's List parameter.
Code to prepare the associative array data:
var guest = {};
var guests = [];
for(var i=0;i<2;i++){
var name = GetName();
var email = GetEmail();
guest = { 'Email': email, 'Name': name };
guests.push(guest);
}
End of the loop would give me, say:
{
{'Email':"bob#zyz.com", 'Name':"Bob"},
{'Email':"tom#zyz.com", 'Name':"Tom"}
}
This is the new call to the dispatcher:
dispatch("XYZ", "AddGuests", "id", 10, "guests", JSON.stringify(guests));
This is my controller:
[HttpPost]
public ActionResult AddGuests(string id, List<SelectedGuest> guests){
//save guests
}
[Serializable]
public class SelectedGuest
{
public string Email { get; set; }
public string Name { get; set; }
}
When I look at httpfox I see correct values posted:
id 10
guests [{"Email":"bob#zyz.com","Name":"Bob"},{"Email":"tom#zyz.com","Name":"Tom"}]
If I do this, I get the guest passed correctly to my controller:
But this limits me to pass just the guests array not other parameters.
data = JSON.stringify(guests);
What I am doing wrong? Thanks for reading.
If using JSON.stringify(guests) works, why not combine it into another JSON object with your other parameters that don't need stringification?
data = { "id": 10, "Guests": JSON.stringify(guests) };
Alternatively, you could construct a javascript object that combines these and call stringify on that.

Categories