MVC How to receive an object array that comes from JSON? - javascript

I am trying to send an object array to my controller but having some difficulties.
It is sending the array and when delivered to the controller, the object count of the array also seems OK.
But if you will look inside the objects all the attributes of the objects are null
How it can be possible?
JavaScript:
function callme(results) {
for (var i = 0; i < results.length; i++) {
var endRes = {
Id: results[i].id,
Icon: results[i].icon
};
jsonObj.push(endRes);
}
sendPackage(jsonObj);
}
function sendPackage(jsonObj) {
$.ajax({
type: "POST",
url: '../../Home/RegisterList',
data: { List: jsonObj },
cache: false,
dataType: "json",
error: function (x, e, data) {
alert(data);
}
});
}
Controller:
[HttpPost]
public JsonResult RegisterList(ICollection<DetailsModel> List)
{
foreach (var i in List) ....... // other process will be here
............................... // other process will be here
return Json(new { message = "OK" });
}
Model:
public class DetailsModel
{
public string Id { get; set; }
public string Icon { get; set; }
}

Ok I've solved the problem last night by using Newton's JSON.NET (you can get it from NuGet). I've stringified the array and recieved it as a string with the controller. Finally I used json.net to convert(deserialize) this string into a collection.
To stringify: use same code but change the data section of the json request with:
data: { List : JSON.stringify(jsonObj) }
Finally recieve it by:
using Newtonsoft.Json;
public JsonResult RegisterList(string List)
{
ICollection<DetailsModel> jsonModel = JsonConvert.DeserializeObject<ICollection<DetailsModel>>(List);
}
And voila; you have a collection named jsonModel!

Unfortunately model binding of lists is not that nice and obvious in MVC. See:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Passing the list this way works:
data: {"List[0].Id":"1", "List[0].Icon":"test"}

Related

Posting JSON object via AJAX to ASP.NET Core Web API

I have tried different methods that have been posted on this site, but nothing seems to work.
I want to create a clothing site (a personal project). The products on the site have their own class, that is built like this:
public class Product
{
public string ProductName { get; set; }
public string ProductPrice { get; set; }
public int Quantity { get; set; }
}
The shopping cart is another class that will contain a list of Product objects and this one is built like this:
public class ShoppingCart
{
[Key]
public int Id { get; set; }
List<Product> ProductList { get; set; }
public string ClientName { get; set; }
public string ClientAddress { get; set; }
public string ClientMail { get; set; }
}
I created an API Controller class and thought that would solve the problem. It looks like this:
[Route("api/Shopping")]
[ApiController]
public class ShoppingCartController : ControllerBase
{
[HttpPost]
public ShoppingCart Save([FromBody] ShoppingCart s)
{
return s;
}
}
In my JavaScript code I create my JSON object and try to post it like this:
var orderB = document.getElementById("orderB");
orderB.addEventListener("click", function () {
var inputName = document.getElementById("inputName").value;
var inputAddress = document.getElementById("inputAddress").value;
var inputMail = document.getElementById("inputMail").value;
var auxArray = [];
for (var i = 0; i < productsAux.length; i++) {
auxArray[i] = { "productName": productsAux[i].titlu, "productPrice": productsAux[i].pret, "quantity": localStorage.getItem(productsAux[i].titlu)};
}
var shoppingCart = {
productList: auxArray,
clientName: inputName,
clientAddress: inputAddress,
clientMail: inputMail
};
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "api/shopping/save",
contentType: "application/json charset=utf-8",
}).done(function (res) {
alert(res);
});
After I push the order button on my page I expect to see the alert pop-up with the callback result which I suppose is the ShoppingCart object that is created using the JSON that I send.
For those coming on later, I would suggest checking that your types are correct on both ends. For instance, if your JS is posting a byte array and C# tries to convert it to an int, the whole object (not just that prop) will be null.
This has caught me many a time.
I opened the Network tab and I got this: I got a 404 (kind of
expected that) , the name of the method 'save' , a type of 'xhr' and a
size of 45B.
The 404 error obviously means the url/routing is wrong. Here to solve it ,you have two ways to achieve.
First way:
You can change url to "api/shopping" in ajax as follow:
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "api/shopping",
contentType: "application/json charset=utf-8",
}).done(function (res) {
alert(res);
})
Second way:
You can change the path name
of Save action by Attribute routing with Http verb attributes as follow:
[Route("api/Shopping")]
[ApiController]
public class ShoppingCartController : ControllerBase
{
[HttpPost("Save")]
public ShoppingCart Save([FromBody] ShoppingCart s)
{
return s;
}
}
Update
According to your comment, in addition to the above updates, you also need to modify the routing settings as follows:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Debug result:

how to assign values to nested object through json object array

This is my model class:
public class SearchForFlight
{
public SearchForFlight()
{
Segments = new otherType();
}
public int AdultCount { get; set; }
public JourneyType JourneyType { get; set; }
public string Sources { get; set; }
public otherType Segments { get; set; }
}
public class otherType
{
public string Origin { get; set; }
public string Destination { get; set; }
public FlightCabinClass FlightCabinClass { get; set;}
public DateTime PreferredDepartureTime { get; set;
public DateTime PreferredArrivalTime { get; set; }
}
Now, My requirement is to post objects along with nested object to an external api.
The required form is something like this:
{
AdultCount: $("#AdultCount").val(),
JourneyType: $("#JourneyType :selected").text(),
PreferredAirlines: null,
Segments: [
{
Origin: $("#Origin").val(),
Destination: $("#Destination").val(),
FlightCabinClass: $("#FlightCabinClass").val(),
PreferredDepartureTime:$("#PreferredDepartureTime").val(),
PreferredArrivalTime: $("#PreferredArrivalTime").val(),
}
]
}
So, i have created another class OtherType and put all those nested objects into it.
I got the idea from this so question
How to send nested json object to mvc controller using ajax
Now, this is my Script tag with all the code inside to post simple objects along with nested objects.But nested objects value comes out to be null.
How should i model this code here.
<script>
$(document).ready(function () {
$("#btnPost").click(function () {
var sof = {
AdultCount: $("#AdultCount").val(),
JourneyType: $("#JourneyType :selected").text(),
PreferredAirlines: null,
Segments: [
{
Origin: $("#Origin").val(),
Destination: $("#Destination").val(),
FlightCabinClass: $("#FlightCabinClass").val(),
PreferredDepartureTime: $("#PreferredDepartureTime").val(),
PreferredArrivalTime: $("#PreferredArrivalTime").val(),
}
],
};
$.ajax(
{
url: "/api/Flight/SearchFlight",
type: "Post",
data: sof,
success: function (data) {
alert(data);
}
});
});
});
</script>
Posted Properties values for Origin, Destination comes out to be null.
The textbox rendered on view page are something like this:
#Html.TextBoxFor(model => model.Segments.Origin)
Any hint please.
Remove the array [] for Segments. Use contentType and stringify in your $.ajax func. Use the generated id for the Origin. It might not be "Origin". So,pls change it accordingly.
<script>
$(document).ready(function () {
$("#btnPost").click(function () {
var sof = {
AdultCount: $("#AdultCount").val(),
JourneyType: $("#JourneyType :selected").text(),
PreferredAirlines: null,
Segments: {
Origin: $("#Origin").val(),
Destination: $("#Destination").val(),
FlightCabinClass: $("#FlightCabinClass").val(),
PreferredDepartureTime: $("#PreferredDepartureTime").val(),
PreferredArrivalTime: $("#PreferredArrivalTime").val(),
},
};
$.ajax(
{
url: "/api/Flight/SearchFlight",
type: "Post",
contentType: 'application/json',
data: JSON.stringify(sof),
success: function (data) {
alert(data);
}
});
});
});
</script>

JavaScript serialization of form not correct when added as a property of another object

I was able to serialize my form and pass the object to my MVC controller. Here is a cut down version of the code.
public void Test(ComplexType model)
{
// do stuff with model
}
with the JavaScript:
$.ajax({
type: 'POST',
url: '/CmaTestRun/Test/',
data: $('#theForm').serializeArray()
}).......
However, I then realized that I needed to pass additional data along with the serialized form so I made the following changes.
I created a class to that would hold the original ComplexType and an integer value and would be passed to the controller:
public class TestObject
{
public int TestId { get; set; }
public ComplexType TestModel { get; set;}
}
And my controller action:
public void Test(TestObject model)
{
}
Finally, in my JavaScript, I made the following changes:
var TestObject = {
TestId:99,
ComplexType: $('#theForm').serializeArray()
}
$.ajax({
type: 'POST',
url: '/CmaTestRun/Test/',
data: TestObject
})
When I run and step into the controller, the TestObject is passed and TestId is 99. However, although my ComplexType has the correct structure, its properties are all null.
How should I change the code so that everything is populated correctly?
EDIT - Serialized Form
The properties are a bit different from the original post. The collapsed objects follow the same structure as the expanded ones.
Try this, first serialize the form and then push extra data.
var params = $('#theForm').serializeArray();
params.push({name: 'testId', value: 99});
$.ajax({
type: 'POST',
url: '/CmaTestRun/Test/',
data: params
})
and you can also use jquery $.param() param
Demo
$(document).ready(function(){
var TestObject = {
TestId:99,
ComplexType: $('#theForm').serializeArray()
}
var shallowEncoded = $.param( TestObject, false );
var shallowDecoded = decodeURIComponent( shallowEncoded );
console.log(shallowEncoded);
console.log(shallowDecoded);
})
and here is controller: Nothing to change. Modelbinder will take care of it.
[HttpPost]
public ActionResult Test(TestObject)
{
}
and if you want to keep them seperate in controller:
[HttpPost]
public ActionResult Test(ComplexType model, int testId)
{
// do stuff with model
}
and keep the model like it was before. modelbinder populates the model from http form collection from posted data.

Posting a model and additional parameters from javascript to an MVC3 controller

I have some JS which is passing (or trying to pass) a model and a string to an MVC controller.
The JS code is:
$.ajax({
url: self.ajaxValidationUrl,
type: "POST",
data: { model: $("form").serialize(), stepList: thisStepList },
async: false,
success: function(errors) {
console.log("Errors...");
if (errors.length > 0) {
anyServerError = true;
}
for (var i = 0; i < errors.length; i++) {
console.log(errors[i].ErrorMessage);
self.errorList += "<li>" + errors[i].ErrorMessage + "</li>";
}
}
});
The Controller looks like this:
[HttpPost]
public ActionResult ValidateReport(MyTemplate model, string stepList)
{
var errors = model.Validate();
return Json(errors);
}
The model parameter is blank and isn't resolving to the MyTemplate object. The post parameters are coming in ok.
I have a JSONmodel binder I got from somewhere, the place has escaped me but look at this.
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
return serializer.Deserialize(stringified, bindingContext.ModelType);
}
}
}
That allows you to do this in your controller.
[HttpPost]
public ActionResult GiftsCOG([FromJson] List<GiftModel> gifts, [FromJson] string guid)
{
}
This allows you to pass JSON from javascript.

JSON doesn't work with typed class in Webservice

I'm having a class like the following:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
[DataContract()]
public class TestCol : List<Test> { }
[DataContract()]
public class MainTest
{
public TestCol Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
And a webservice with the following webmethod like this:
[WebMethod]
public String Test(MainTest input)
{
String rtrn = String.Empty;
foreach (Test test in input.Components)
rtrn += test.Name;
return rtrn;
}
Which is called by AJAX with the following method:
var Test = {};
Test.Name = "Test";
var MainTest = {};
MainTest.Components = [];
MainTest.Components.push(Test);
$.ajax({
type: "POST",
url: "WebService/WSTest.asmx/Test",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({
"input": MainTest
}),
success: function(data, textStatus) {
console.log("success");
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
window.console && console.log && console.log(XMLHttpRequest.responseText + " || " + textStatus + " || " + errorThrown);
}
});
When executing the AJAX call, it will return errors. I found out that the error is with the typed class TestCol, which has no properties.
Now do I have found 2 solutions that require changes in the C# classes:
Remove the TestCol class and change the Components property to List<Test> datatype:
[DataContract()]
public class MainTest
{
public List<Test> Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
Or add an extra property to the TestCol class and change the webmethod:
[DataContract()]
public class TestCol : List<Test>
{
public List<Test> Components { get; set; }
}
[DataContract()]
public class MainTest
{
public TestCol Components { get; set; }
}
[DataContract()]
public class Test
{
public Test() { }
public String Name { get; set; }
}
&
[WebMethod]
public String Test(MainTest input)
{
String rtrn = String.Empty;
foreach (Test test in input.Components.Components)
rtrn += test.Name;
return rtrn;
}
Both solutions require changes in the C# classes, which I prefer not to, as other code is depended on it. Does anyone know a solution for this problem?
Edit: I've uploaded a test solution, containing above code: http://jeroenvanwarmerdam.nl/content/temp/JSONtoClassWebservice.zip
So this solution changes the List to Object instead of Test. I hoped to change as little code as possible (i dislike having to do casts in foreach loops). The below code does so with two function additions and the previously mentioned inheritance change.
public class TestCol : List<object>
{
public new IEnumerator<Test> GetEnumerator()
{
return this.ConvertAll<Test>(
dict => ConvertDictionaryTo<Test>(
(Dictionary<string, object>) dict
)
).GetEnumerator();
}
private T ConvertDictionaryTo<T>(IDictionary<string, object> dictionary) where T : new()
{
Type type = typeof(T);
T ret = new T();
foreach (var keyValue in dictionary)
{
type.GetProperty(keyValue.Key).SetValue(ret, keyValue.Value, null);
}
return ret;
}
}
Convert function courtesy TurBas
Mapping object to dictionary and vice versa
JavaScriptSerializer serialization: IEnumerable -> JavaScript Array
When the JavaScriptSerializer is used, it automatically converts an IEnumerable (without IDictionary) type -- that covers List<> or anything derived from it -- into an array.
Deserialization: JavaScript Array -> IEnumerable -> Collection Object
Now, upon deserialization from JSON, the JavaScriptSerializer must take the array, create an IEnumerable, then create an object for the field by passing that IEnumerable into its constructor.
Constructing Collection object via Constructor
Now, for List<> you have a constructor overload that takes an IEnumerable. So if you put List<Test> as the type of your component it creates it fine.
Constructors not inherited
However, TestCol does NOT have such a constructor! The reason why it worked with List<Test> and not with TestCol (which derives from List<Test>) is that the only thing that is not inherited between classes are constructors!
Therefore, the JavaScriptSerializer does not have any way to construct a TestCol from an IEnumerable. So it fails silently.
Deserialize Array by Creating List, then Casting to Type
Now the JavaScriptSerializer may then attempt to create a List<Test> from this IEnumerable<Test>, and then try to cast it into a TestCol.
Possible Solution
Solution: Try putting in:
public TestCol () {} // Need this when you have another constructor
public TestCol (IEnumerable<Test> list) : base(list) {} // Constructor that takes an IEnumerable
public TestCol (IList<Test> list) : base(list) {} // Constructor that takes an IList
as your TestCol's constructors.
And if it still doesn't work, implement an explicit type cast from List<Test> to TestCol.
public static explicit operator TestCol(IList<Test> list) { return new TestCol(list); }
hmmm this didn't work in the web method?
foreach (Test test in input.Components.TestCol)
Re comment below, does this work then?
foreach (Test test in (List<Test>)input.Components.TestCol)
It should work because a class can be enumerated...
If you're expecting JSON, you'll need to return JSON.
Check with the System.Web.Script.Serialization.JavaScriptSerializer
http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx
If you use ASMX services the JavaScriptSerializer will be responsible for the data conversion and not the DataContractJsonSerializer. So all DataContract attributes which you use will not work.
You are write that classes like public class TestCol : List<Test> { } are bad for the JavaScriptSerializer, but classes having List<Test> as the property (public class MainTest { public List<Test> Components { get; set; }}) have no problem.
So I suggest to simplify you code to the following. The classes used as the parameters can be defines as
public class Test {
public String Name { get; set; }
}
public class MainTest {
public List<Test> Components { get; set; }
}
The WebMethod Test will be
[WebMethod]
public String Test(MainTest input)
{
StringBuilder rtrn = new StringBuilder();
foreach (Test test in input.Components) {
rtrn.AppendLine (test.Name);
}
return rtrn.ToString ();
}
and the ajax call can be
var tests = {
Components: [
{Name:"Test1"},
{Name:"Test2"},
{Name:"Test3"}
]
};
$.ajax({
type: "POST",
url: "WebService1.asmx/Test",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: JSON.stringify({
"input": tests
}),
success: function (data, textStatus) {
alert("success:\n" + data.d);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert(XMLHttpRequest.responseText+" || "+textStatus+" || "+errorThrown);
}
});
How you can see all will be very simple and it's work. For more details how you can send complex data I recommend you to read another answer and this.
You seem to be using ASMX (not WCF) because you have omitted [DataMember] attributes on all your public properties and still get serialized. WCF is "opt-in", so you shouldn't be seeing any serialization of any properly.
As a result, all [DataContract] attributes are useless.
ASMX defaults to the JavaScriptSerializer if you are using ScriptManger and outputing JSON. The JavaScriptSerializer is "opt-out" (which means that all public properties are automatically serialized unless marked with [ScriptIgnoreAttribute]).
The JavaScriptSerializer supports serializing List<>'s. You should not be having problems serializing your TestCol property because JavaScriptSerializer automatically supports serializing all types that implement IEnumerable (but not IDictionary) -- which includes List<> -- into JSON arrays.
Your error seems to be that the JavaScriptSerializer does not properly handle classes that inherit from List<> (or from a class implementing IEnumerable). In your first work-around, you eliminated the class that inherited from List<>. In your second work-around, you skipped all functionalities of the base class, but re-implemented the List<> in a property.
Your JSON post data currently looks like:
{ Components: [
{ Name:"foo" },
{ Name:"bar" },
:
] }
However, you have one extra level or redirection in the serializer (inheriting from List<Test> -> TestCol). It is possible that the serializer is looking for:
{ Components: {
Items: [
{ Name:"foo" },
{ Name:"bar" },
:
] }
}
because you are essentially serializing the "Items" property of List<>. So, your JSON post data is just feeding Test objects to the wrong place, and your TestCol Components property ends up empty.
I'd suggest that you add a web service method to output a test MainTest object to see what it serializes into. You'll probably find that it puts in an additional level.

Categories

Resources