How to ModelBind JSON to an object and nested List? - c#

I'm looking to bind a JSON object to a List nested in an object.
Background
I have a Category class that contains a list of ConfigurationFunds:
public class Category
{
public int Id { get; set; }
public string CountryId { get; set; }
public string Name { get; set; }
public List<ConfigurationFund> Funds { get; set; }
public Category()
{
Funds = new List<ConfigurationFund>();
}
}
public class ConfigurationFund
{
public int Id { get; set; }
public string CountryId { get; set; }
public string Name { get; set; }
public ConfigurationFund()
{
}
}
The user can select a number of Funds per Category, and then I want to POST a JSON string back to my Controller and have the ModelBinder bind the JSON to the object model.
This is my Action method:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Category category)
{
// Categoy.Id & Categoy.CountryID is populated, but not Funds is null
return Json(true); //
}
So far, I have this jQuery:
$('#Save').click(function (e) {
e.preventDefault();
var data = {};
data["category.Id"] = $('#CategorySelector').find(":selected").val();
data["category.countryId"] = $('#CategorySelector').find(":selected").attr("countryId");
var funds = {};
$('#ConfiguredFunds option').each(function (i) {
funds["funds[" + i + "].Id"] = $(this).val();
funds["funds[" + i + "].countryId"] = $(this).attr("countryId");
});
data["category.funds"] = funds;
$.post($(this).attr("action"), data, function (result) {
// do stuff with response
}, "json");
});
But this isn't working. The properties of Category are populated, but the List<ConfigurationFund>() isn't being populated.
Question
How do I need to modify this to get it to work?
Supplementary Info
Just to note, I've also tried to post the Category & ConfiguredFunds separately, and it works, with something similar to the following:
$('#Save').click(function (e) {
e.preventDefault();
var data = {};
data["category.Id"] = $('#CategorySelector').find(":selected").val();
data["category.countryId"] = $('#CategorySelector').find(":selected").attr("countryId");
$('#ConfiguredFunds option').each(function (i) {
data["configuredFunds[" + i + "].Id"] = $(this).val();
data["configuredFunds[" + i + "].countryId"] = $(this).attr("countryId");
});
$.post($(this).attr("action"), data, function (result) {
// do stuff with response
}, "json");
});
In the following Action method, the ConfiguredFunds are populated, and the Category is also populated. However, the Category's List isn't populated. I need the Category & its List to be populated.
public ActionResult Edit(List<ConfigurationFund> configuredFunds, Category category)
{
return Json(true);
}

I've figured it out. I just needed to change
data["category.Id"] = $('#CategorySelector').find(":selected").val();
data["category.countryId"] = $('#CategorySelector').find(":selected").attr("countryId");
var funds = {};
$('#ConfiguredFunds option').each(function (i) {
funds["funds[" + i + "].Id"] = $(this).val();
funds["funds[" + i + "].countryId"] = $(this).attr("countryId");
});
data["category.funds"] = funds;
$.post($(this).attr("action"), data, function (result) {
// do stuff with response
}, "json");
to:
data["category.Id"] = $('#CategorySelector').find(":selected").val();
data["category.countryId"] = $('#CategorySelector').find(":selected").attr("countryId");
$('#ConfiguredFunds option').each(function (i) {
data["category.funds[" + i + "].Id"] = $(this).val();
data["category.funds[" + i + "].countryId"] = $(this).attr("countryId");
});
$.post($(this).attr("action"), data, function (result) {
// do stuff with response
}, "json");
Basically, I just specified that the funds belong to a Category with data["category.funds"]

Related

Not posting data to an action with MVC(HTTP 415)

I'm looking to send the following fields to a MVC action
public class AddProductViewModel
{
public string ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public double Price { get; set; }
public bool Enabled { get; set; }
public List<CategorySelectModel> Categories { get; set; } = new List<CategorySelectModel>();
public List<IFormFile> Photos { get; set; } = new List<IFormFile>();
}
CategorySelectModel:
public string CategoryId { get; set; }
public string CategoryName { get; set; }
and I haved used what i believe represents the actual json data...but I dont get why it isnt posting the data to the action
here is the js code where I have serialized the data and send to the controller
function SubmitProducts(element)
{
element.preventDefault();
let ulContainer = document.getElementById("dropDownAndSel").getElementsByTagName('ul')[0];
var Categories = [];
let x = document.getElementById("dropDownCategories");
for (let i = 0; i < x.children.length; i++) {
if (x.options[i].selected) {
let CategoryName = x.options[i].innerHTML;
let CategoryId = x.children[i].value;
let dropdownItems = { CategoryId, CategoryName };
if (!Categories.includes(dropdownItems)) {
Categories.push(dropdownItems);
}
}
}
let pId = document.getElementById('pId').value;
let ProductId = pId;
let ProductName = document.getElementById('ProductName').value;
if (!ProductName || ProductName.length ==0) {
alert('ProdutName cannot be null or empty');
return;
}
let priceStr = document.getElementById('productPriceId').value;
if (!priceStr || priceStr.length == 0) {
alert('Price cant be empty')
return;
}
let Price = parseFloat(priceStr);
let QuantityStr = document.getElementById('qtyNum').value;
if (!QuantityStr || QuantityStr.length==0) {
alert('Quantity cant be empty');
return;
}
let Quantity = parseInt(QuantityStr);
var formData = new FormData();
let filesContainer = document.getElementById('photoProduct_0');
for (let i = 0; i < filesContainer.files.length; i++) {
formData.append('model.Photos', filesContainer.files[i], filesContainer.files[i].name);
}
formData.set('model.ProductId', ProductId);
formData.set('model.ProductName', ProductName);
formData.set('model.Price', Price);
formData.set('model.Quantity', Quantity);
formData.set('model.Categories', JSON.stringify(Categories));
$.ajax({
url: '/' + 'Product' + '/' + 'AddProduct',
type: 'POST',
contentType: false,
processData: false,
data: formData,
success: console.log('success'),
});
}
Here is the action signature:
[HttpPost]
public async Task<IActionResult> AddProduct([FromBody] AddProductViewModel model)
Code above post FormData data. Therefore, the AddProductViewModel parameter should be bound using form-data in the request body:
[HttpPost]
public async Task<IActionResult> AddProduct([FromForm] AddProductViewModel model)
Reference to the following post: ASP.NET Core form POST results in a HTTP 415 Unsupported Media Type response

List of Objects sent to the controller from jQuery are always null

So this is my class that of my model:
[Table("Question", Schema = "trs")]
public class Question
{
[Key]
public int QuestionId { get; set; }
[ForeignKey("TranType")]
[Required]
public int TranTypeId { get; set; }
[ForeignKey("Company")]
[Required]
public int CompanyId { get; set; }
[Required]
[StringLength(300)]
public string Text { get; set; }
[Required]
public bool IsActive { get; set; }
[ForeignKey("QuestionType")]
public int QTypeId { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedUserId { get; set; }
public DateTime UpdateDate { get; set; }
public string UpdateUserId { get; set; }
public Company Company { get; set; }
public QuestionType QuestionType { get; set; }
public TranType TranType { get; set; }
public ICollection<Answer> Answer { get; set; }
public ICollection<Grading> Grading { get; set; }
}
This is my controller action, for now it is doing nothing because I need to get the values first which are the problem:
[Authorize(Roles = "Admin")]
public class QuestionController : Controller
{
readonly IQuestionRepository questionRepository;
readonly ICompanyRepository companyRepository;
public QuestionController(IQuestionRepository qRepository, ICompanyRepository cpnRepository)
{
questionRepository = qRepository;
companyRepository = cpnRepository;
}
[HttpPost]
public ActionResult Save([FromBody] List<Question> qView)
{
return View(qView);
}
}
Now I have tried also:
[HttpPost]
public ActionResult Save(List<Question> qView)
{
return View(qView);
}
In both cases I have problems; in the first option (with [FromBody]) qView is null; and in option 2 (without [FromBody]) qView is not null but the List is empty (Count == 0).
Here is the Code to generate the JSON data:
function Send()
{
var qCounter = parseInt($('#quesCounter').val());
var listQuestion = [];
var qView = {};
qView.Questions = listQuestion;
for (i = 1; i <= qCounter; i++)
{
var question = {};
var listAnswer = [];
question.Answers = listAnswer;
var anCounter = parseInt($('#qtCounter' + i).val());
var qText = $('#qtText' + i).val();
var qType = $('#qType' + i).val();
question["Text"] = qText;
question["QTypeId"] = qType;
for (j = 1; j <= anCounter; j++)
{
var answer = {};
var aText = $('#anText' + i.toString() + j.toString()).val();
var aCorrect = "";
if ($('#anCorrect' + i.toString() + j.toString()).prop('checked')) {
aCorrect = "yes";
}
else {
aCorrect = "no";
}
answer["Text"] = aText;
answer["IsCorrect"] = aCorrect;
question.Answers.push(answer);
}
qView.Questions.push(question);
}
$.ajax({
type: "POST",
url: "Save", // the method we are calling
contentType: "application/json",
data: JSON.stringify(qView),
//data: JSON.stringify({ 'qView': qView }),
dataType: "json",
success: function (result) {
alert('Yay! It worked!');
// Or if you are returning something
alert('I returned... ' + result.WhateverIsReturning);
},
error: function (result) {
alert('Oh no :(');
}
});
}
I have no clue what is wrong.
Please any idea where is my issue?
Hi guys so the issue was the way I was calling the the ajax method in the wrong way and some of the properties in the mapping on the client side; I fixed by running the method this way:
function Send()
{
var qCounter = parseInt($('#quesCounter').val());
var listQuestion = [];
for (i = 1; i <= qCounter; i++)
{
var question = {};
var listAnswer = [];
question.Answer = listAnswer; //Second problem was here because I was calling here the array Answers when the List<Answer> property on the server side was called Answer.
var anCounter = parseInt($('#qtCounter' + i).val());
var qText = $('#qtText' + i).val();
var qType = $('#qType' + i).val();
question["Text"] = qText;
question["QTypeId"] = parseInt(qType);// first problem was here because the property on the Server side was integer and I was sending string.
for (j = 1; j <= anCounter; j++)
{
var answer = {};
var aText = $('#anText' + i.toString() + j.toString()).val();
var aCorrect = "";
if ($('#anCorrect' + i.toString() + j.toString()).prop('checked')) {
aCorrect = 1; //third problem was here because true/yes was not recognize so I had to change to 1/0;.
}
else {
aCorrect = 0; //same as above (third problem).
}
answer["Text"] = aText;
answer["IsCorrect"] = aCorrect;
question.Answer.push(answer);
}
listQuestion.push(question);
}
$.ajax({
type: "POST",
url: "Save", // the method we are calling
contentType: "application/json",
data: JSON.stringify(listQuestion),
success: function (result) {
alert('worked');
},
error: function (result) {
alert('Something failed');
}
});
}
You're setting the contentType to application/json and then you stringify your data before sent? You've just contradicted yourself.
You're basically saying: Server, I am sending you a JSON object. But then since you stringify the object, the Server will only see a string. That's why the server wouldn't match the data to your model.
My 2 cents
As long as the JavaScript object / JSON you construct on client-side matches the model you have in the parameter of your controller, you don't need to specify contentType and [FromBody] - to keep things simply. MVC is smart enough to do the binding for you. You don't even need to use lowercase in your C# model for the properties. You can even use true/false for boolean, as long as the matching property in C# model is defined as bool.
Don't use your domain/data model on your view/page. Create a separate model (called view model) and put only what you need to display/use on the view/page there. You don't want to expose what you have in the database to the whole world.
Use camelCase for variables and functions please in JavaScript. The way you declared your function Send(), qView.Questions = and question.Answers.push(answer); just made me crazy :p.
Code Example
$.ajax({
type: 'POST',
// Use HTML helper to generate the link instead of hardcode.
// HTML helper will take care the host, port, and more for you.
url: '#Url.Action("save", "question", new { area = "" })',
// You usually don't need to specify the contentType.
// The default 'application/x-www-form-urlencoded' works well for
// JSON objects.
//contentType: "application/json",
// You don't need to stringify the data
data: { qView: qView },
//data: JSON.stringify({ 'qView': qView }),
dataType: "json",
success: function (result) {
alert('Yay! It worked!');
// Or if you are returning something
alert('I returned... ' + result.WhateverIsReturning);
},
error: function (result) {
alert('Oh no :(');
}
})
// I would use .done() here instead of success and error() but
// this is just personal taste.
;

How to send a model which consist generic list from ajax call in mvc C#

I am trying to call the controller from ajax and sending model data which consist some list of data inside it. I am getting the count correct for them but inside the list properties are coming null.
Controller:
public JsonResult InsertorUpdate(IncidentEdit incident)
{
try
{
SrvincidentDtls.TicketNo = incident.TicketNo;
SrvincidentDtls.Priority = incident.Priority;
SrvincidentDtls.Status = incident.Status;
SrvincidentDtls.Title = incident.Title;
SrvincidentDtls.IsActive = incident.IsActive;
List<Comments> lstComm = new List<Comments>();
if(incident.CommentList!=null )
{
foreach (var comm in incident.CommentList)
{
Comments Comm = new Comments();
Comm.Comment = comm.Comments;
Comm.AddedBy = comm.AddedBy;
Comm.AddedBy_ID = comm.AddedBy_ID;
Comm.CreatedDate = comm.CreatedDate;
lstComm.Add(Comm);
}
}
SrvincidentDtls.Comments = lstComm.ToArray();
SrvincidentDtls.Description = incident.Description;
var result=proxyService.InsertorUpdateIncidentDetails(SrvincidentDtls);
return Json(new { success = true, jvalue = result }, JsonRequestBehavior.AllowGet);
}
catch(Exception ex)
{
throw ex;
}
}
Model:
public class IncidentEdit
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public string Status { get; set; }
public string Priority { get; set; }
public List<IncidentComments> CommentList { get; set; }
}
Generic Class:
public class IncidentComments
{
public string Comments { get; set; }
public string AddedBy { get; set; }
public string AddedBy_ID { get; set; }
public DateTime CreatedDate { get; set; }
}
Ajax call:
function InsertOrUpdate(){
incidentDetails = {
Title: $("#txtTitle").val(),
Description: $("#txtDescription").val(),
Priority: $('#selPriority option:selected').val(),
Status: $('#selStatus option:selected').val(),
IsActive: 1,
CreatedDate :$('#spncrtdDt').text(),
CommentList:PopulateCommentList()
};
$.ajax({
type: "Get", //HTTP POST Method
url: insertORupdatUrl, // Controller/View
data: {incident:incidentDetails},
contentType: "application/json; charset=utf-8",
success: function (data) {
}
});
}
js function:
function PopulateCommentList() {
var CommentList = [];
var dtTable = $('#dvCommentTbl').DataTable();
for (var i = 0; i < 10; i++) {
if (dtTable.row(i).data() != "" && dtTable.row(i).data() != null && dtTable.row(i).data() != undefined) {
CommentList.push({
Comments: dtTable.row(i).data()[1],
AddedBy: dtTable.row(i).data()[2],
AddedBy_ID: dtTable.row(i).data()[0],
CreatedDate: dtTable.row(i).data()[3]
});
}
}
return CommentList;
}
the count for comment list is coming fine but the data like Added_By,comments,Created Dtae all coming null.
Plz help.
You can take a variable like
var commlist=PopulateCommentList();
then populate it in your model json:
incidentDetails = {
Title: $("#txtTitle").val(),
Description: $("#txtDescription").val(),
Priority: $('#selPriority option:selected').val(),
Status: $('#selStatus option:selected').val(),
IsActive: 1,
CreatedDate :$('#spncrtdDt').text(),
CommentList:commlist
};
it will work as it work for me.

Issue while binding the POSTed complex form data from Angular JS to ASP.Net Web API 2

I'm working on some demo examples on how to pass the form data from an Angular Service to a Web API 2 controller POST action. But my object is always null on the controller side. Here is how my code looks like
AngularJS Call to Web API
$http({
method: 'POST',
url: MyApp.rootPath + 'api/CustomerSpaSilo/SearchCustomers?nameFilter=' + nameFilter,
data: $.param({ '': pagingVM }),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
The pagingVM is an angular $scope object that contains paging details - SortBy, CurrentPage, ItemsPerPage, SortDesc.
My Web API POST method
[HttpPost]
[Route("SearchCustomers")]
public HttpResponseMessage Post(string nameFilter, [FromBody] PagingViewModel pagingVM)
{
if (nameFilter == null)
nameFilter = "";
//Code Implementation
}
The PagingViewModel class
public class PagingViewModel
{
public string SortBy { get; set; }
public bool SortDesc { get; set; }
public int CurrentPage { get; set; }
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int TotalPages
{
get {
if (this.ItemsPerPage > 0)
return (int)Math.Ceiling((decimal)TotalItems / this.ItemsPerPage);
else
return 1;
}
}
}
The pagingVM parameter is always coming as default object. A quick look in the Chrome's network activity shows that all the values are being passed in the request body as the form data. If I change the parameter type to FormDataCollection, then I can read the values and build my object but I'm looking to Web API bind the incoming request values for me. Am I missing something here?
controller
[RoutePrefix("api/CustomerSpaSilo")]
public class CustomerSpaSiloController : ApiController
{
[HttpPost]
[Route("SearchCustomers")]
public IHttpActionResult Post(string nameFilter, [FromBody] PagingViewModel pagingVM)
{
if (nameFilter == null)
nameFilter = "";
//Code Implementation
return Ok("Result=" + pagingVM.ToString());
}
}
model
public class PagingViewModel
{
public string SortBy { get; set; }
public bool SortDesc { get; set; }
public int CurrentPage { get; set; }
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int TotalPages
{
get
{
if (this.ItemsPerPage > 0)
return (int) Math.Ceiling((decimal) TotalItems/this.ItemsPerPage);
else
return 1;
}
}
public override string ToString()
{
return string.Format("PagingViewModel(SortBy='{0}',SortDesc='{1}', CurrentPage='{2}', TotalItems='{3}', ItemsPerPage='{4}')",
SortBy, SortDesc, CurrentPage, TotalItems, ItemsPerPage);
}
}
postman screenshot
js client
<!DOCTYPE html>
<html ng-app="app">
<head>
<title>Home</title>
</head>
<body ng-controller="HomeController">
<button ng-click="post()">post</button>
<p>{{status}}</p>
<pre>{{result | json}}</pre>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<script>
(function() {
'use strict';
console.trace('js loaded');
angular
.module('app', [])
.factory('api', apiFactory)
.controller('HomeController', HomeController);
function apiFactory($http) {
var svc = {
searchCustomers: searchCustomers
};
return svc;
function searchCustomers(nameField, paging) {
return $http
.post('/api/CustomerSpaSilo/SearchCustomers?nameFilter=' + nameField,
paging,
{
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("&");
}
}
);
}
}
function HomeController($scope, api) {
console.trace('HomeController');
$scope.status = null;
$scope.result = null;
$scope.post = function () {
var paging = {
SortBy: 'abc',
SortDesc: false,
CurrentPage: 3,
TotalItems: 52,
ItemsPerPage: 10
};
api.searchCustomers('ddd', paging)
.then(function(response) {
$scope.status = 'OK';
$scope.result = response.data;
}, function(reason) {
$scope.status = 'Error';
$scope.result = reason;
});
};
}
})();
</script>
</body>
</html>
chrome screenshot
you should use your $http service like as -
$http.post(MyApp.rootPath + 'api/CustomerSpaSilo/SearchCustomers?nameFilter=' + nameFilter, pagingVM ).then(function (result) {
//success
}, function (data) {
//faild
});

looping through an object passed in via JQUERY as an array and using c# webmethod to get data out

all,
I'm getting to my webmethod in my code behind but I'm having problems deserializing my json data.
I have no good reference but here is what I"m trying to do. My the code in my webmethod is not allowing me to get the data out passed from my ajax call. thanks for any help.
$("[id$=rdbSaveAjax1]").click(function () {
var mappedJobRole = new Array();
$(".jobRole").each(function (index) {
var jobRoleIndex = index;
var jobRoleID = $(this).attr('id');
var jobRoleName = $(this).text();
// add all the roleids and rolenames to the job role array.
var roleInfo = {
"roleIndex": jobRoleIndex,
"roleID": jobRoleID,
"roleName": jobRoleName
};
queryStr = { "roleInfo": roleInfo };
mappedJobRole.push(queryStr);
});
$.ajax({
type: "POST",
url: "Apage.aspx/Save_Mapped_Role",
data: "{'savedRole': " + JSON.stringify(mappedJobRole) + "}",
contentType: "application/json; charset=utf-8",
dataType: "json",
async: false,
success: function (data) {
alert("successfully posted data");
},
error: function (data) {
alert("failed posted data");
}
});
});
In my code behind I can't seem to get the data out.
My class:
public class MappedRole
{
public int Index { get; set; }
public string RoleID { get; set; }
public string RoleName { get; set; }
}
My webmethod:
[WebMethod]
public static bool Save_Mapped_Role(object savedRole)
{
bool success = false;
JavaScriptSerializer js = new JavaScriptSerializer();
IList<MappedRole> role = new JavaScriptSerializer().Deserialize<IList<MappedRole>>savedRole);
int Index = role[0].Index;
string RoleID = role[0].RoleID;
string RoleName = role[0].RoleName;
return success;
}
This line seems to be missing an opening parenthese
IList<MappedRole> role = new JavaScriptSerializer().Deserialize<IList<MappedRole>>savedRole);
Should read
IList<MappedRole> role = new JavaScriptSerializer().Deserialize<IList<MappedRole>>(savedRole);
Furthermore, in order to use the Deserialize method, you need to create classes based on the JSON variables you are passing. For example based on the JSON object you created, you should have the two classes below.
SavedRole Class
public class SavedRole
{
public roleInfo[] { get; set; }
}
roleInfo Class
public class roleInfo
{
public int roleIndex { get; set; }
public string roleID { get; set; }
public string roleName { get; set; }
}
Now the Deserialze method will do its magic and populate the objects for you. Then you'll be able to loop through the object and do what you need with the data.
[WebMethod]
public static bool Save_Mapped_Role(object savedRole)
{
bool success = false;
var serializer = new JavaScriptSerializer();
SavedRole role = serializer.Deserialize<SavedRole>(savedRole);
//Loop through the data like so
int roleIndex = 0;
string roleID = null;
string roleName = null;
foreach (var item in role.roleInfo) {
roleIndex = item.roleIndex;
roleID = item.roleID;
roleName = item.roleName;
//Do more logic with captured data
}
return success;
}
Hope that helps
this post explain how you can convert a JSON to C#: Parse JSON in C#
If you don't want to use that, you need to do some changes in your project:
First, to get the your RoleInfo, you need to transform it in a Dictionary like:
(((object[])savedRole)[0] as Dictionary<string, object>)["roleInfo"]
After that, you can manipule your object to create your List:
var list = ((object[])savedRole);
IList<MappedRole> role = new List<MappedRole>();
foreach (var item in list)
{
var dic = ((item as Dictionary<string, object>)["roleInfo"] as Dictionary<string, object>);
MappedRole map = new MappedRole()
{
roleIndex = Convert.ToInt32(dic["roleIndex"]),
roleID = dic["roleID"].ToString(),
roleName = dic["roleName"].ToString()
};
role.Add(map);
}

Categories