Cannot bind JSon with MVC 3 controller data using KnockoutJS - asp.net-mvc-3

I am new to javascript and MVC 3. I am developing a sample application to get familiar with KnockoutJs.
I am passing a c# object with some properties to a controller. Than this object is passed to the View serialized as JSon. Then I am using the data with Knockout in my view and want to return this data back to the server. But the binding with the the server data fails for on of my properties.
Here is my code:
Model:
public class FranchiseInfo
{
public string FullName { get; set; }
public string ShortName { get; set; }
public List<string> ServerIps = new List<string>();
}
Controller with sample data returning JSon to the View:
public JsonResult Data()
{
FranchiseInfo franchiseInfo = new FranchiseInfo();
franchiseInfo.FullName = "PokerWorld";
franchiseInfo.ShortName = "PW";
franchiseInfo.ServerIps.Add("192.111.1.3");
franchiseInfo.ServerIps.Add("192.112.1.4");
return Json(franchiseInfo, JsonRequestBehavior.AllowGet);
}
Javascript file using knockout:
$(function () {
function viewModel() {
var self = this;
self.FullName = ko.observable();
self.ShortName = ko.observable();
self.optionValues = ko.observableArray([]);
self.ServerIps = ko.observableArray([]);
$.getJSON("Home/Data", function (data) {
self.FullName(data.FullName);
self.ShortName(data.ShortName);
self.optionValues([data.FullName, data.ShortName]);
for (var i = 0; i < data.ServerIps.length; i++) {
self.ServerIps.push({ name: ko.observable(data.ServerIps[i]) });
}
});
self.addIp = function () {
self.ServerIps.push({ name: ko.observable("0.0.0") });
}
self.showIps = function () {
alert(self.ServerIps[name]);
}
self.save = function () {
$.ajax({
url: "Home/Save",
type: "post",
data: ko.toJSON({ FullName: self.FullName, ShortName: self.ShortName, ServerIps: self.ServerIp }),
contentType: "application/json",
success: function (result) { alert("result") }
});
}
};
ko.applyBindings(new viewModel);
View:
Full Name:
<span data-bind="text: FullName"></span>
<input data-bind="value: FullName" />
</div>
<div>
Short Name:
<span data-bind="text: ShortName"></span>
</div>
<select data-bind="options: optionValues"></select>
<div data-bind="foreach: ServerIps">
Name:
<input data-bind="value: name" />
<span data-bind="text: name" />
</div>
<div data-bind="text: ko.toJSON(ServerIps)"></div>
<button data-bind="click: addIp">Add IP</button>
<button data-bind="click: save">Save</button>
When Save button is clicked the data is sent to the server in Json format:
Here is the controller:
public JsonResult Save(FranchiseInfo franchiseInfo)
{
//some data here
//return Json result
}
Full name and Short name properties bind correctly with the c# model when I am sending them in Json format back to the server but the ServerIps property which is an array cannot bind. I think because it is in the format { name: ip} and the model property ServerIps is of type List. How can I fix this ? Any help with working example will be appreciated. Thanks.

I had the same problem in Java Spring.
We solved it by serializing the ViewModel as a request string.
We wrote the function ourselves (although you might want to check if the 'value' is an array and go a bit recursive):
function serializeViewModelToPost(dataString) {
var data = ko.toJS(dataString);
var returnValue = '';
$.each(data, function (key, value) {
returnValue += key + '=' + value + '&';
});
return returnValue;
}
Another option is to parse it serverside:
link
UPDATE:
self.save = function () {
$.ajax({
url: "Home/Save",
type: "post",
data: serializeViewModelToPost(this)),
success: function (result) { alert("result") }
});
You still need to edit the serialize function to check for arrays.

Related

Post Model to Controller in ajax

This is My ajax Code
$('#RolesSave').click(function () {
var sRow = JSON.parse(JSON.stringify($('#table').bootstrapTable('getSelections')));
var ID = JSON.stringify(sRow, ["ID"]);
var view = {
CompanyCode: $('#Company').val(),
RolesCode: $('#Roles').val(),
ID,
};
$.ajax({
method: "POST",
url: '#Url.Action("Save")',
data: $('#formData').serialize(),
success: function (data) {
alert(data + '!!!')
},
error: function () {
alert('ERROR')
}
})
})
This is My Controller
[HttpPost]
public IActionResult Save(RightsModel view)
{
return View();
}
and This My Model
public class RightsModel
{
public List<string> ID { get; set; }
public string Company { get; set; }
public string Roles { get; set; }
}
and this is my view
<form id="formData">
#Html.DropDownListFor(m => m.Company, Model.Company)
#Html.DropDownListFor(m => m.Roles, Model.Roles)
<button id="RolesSave" class="btn btn-primary btn-light" type="submit">存檔</button>
<table id="table"></table>
My problem is when I use the code $('#formData').serialize(). It can convert the data in View into Model normally.
But when I use JSON.serialize(view), It can't achieve the same goal.
Even if I add [FormBody] to the Action, I cannot get any relevant information.
Any suggestions?
JSON.serialize is not exists in js,we usually use JSON.stringify.And you need to make sure the model structure of view is the same with model RightsModel.And you need to add contentType:"application/json", to ajax since you want to pass json type data to action.Here is a demo:
js:
$('#RolesSave').click(function () {
var sRow = JSON.parse(JSON.stringify($('#table').bootstrapTable('getSelections')));
var ID = [];
sRow.forEach(function (item, index) {
ID.push(""+item.ID);
});
var view = {
Company: $('#Company').val(),
Roles: $('#Roles').val(),
ID: ID
};
$.ajax({
method: "POST",
url: '#Url.Action("Save")',
data: JSON.stringify(view),
contentType:"application/json",
success: function (data) {
alert(data + '!!!')
},
error: function () {
alert('ERROR')
}
})
})
action:
[HttpPost]
public IActionResult Save([FromBody]RightsModel view)
{
return View();
}
result:
I try yo use var ID = ["1", "2", "3"]; to test,and it can work.ID need to be type List<string>.

Razor not printing Values to screen after ASP 3.1 RazorPages AJAX Post Updates Model

Hello I am updating a model with an AJAX call on the event of a dropdown selection.
The model is updated and when I step through the below razor loop the values exist.
However nothing inside the #if statement prints to the screen, not even the H2.
The div is just empty... Thoughts?
#if (Model.FieldsRequiredOnStart != null)
{
foreach (var item in Model.FieldsRequiredOnStart)
{
for (int i = 0; i < #item.Inputs.Count(); i++)
{
<h2>Fields Required on Start</h2>
var x = #item.Inputs[i];
<span>#x.Name</span>
<input placeholder="#x.Name" maxlength="#x.MaxSize" type="#x.InputType"> <input />
}
}
}
function onSelect(e) {
let id = $("#wfDropdown").data("kendoDropDownList").value()
if (e.item) {
$('#wfDefId').val(id)
} else {
$('#wfDefId').val(id)
}
$.ajax({
type: 'Post',
url: '/CreateNewWorkflow?handler=RequiredInputs',
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
data: { Id: id },
success: function () {
}
});
}
EDIT ON SUCCESS:
I ended up using the Partial View solution. My issue was I was not sending a refreshed model when I had previously tried the partial way. The Second answer is also valid. Gotcha: If you make a partial be sure to remove the #Page reference at the top or you will get Model is null errors.
Also worth noting the C# Syntax I had to use was slightly different than what is provided in the answer to return the Partial view..
public ActionResult OnPostRequiredInputs(int id)
{
//DO STUFF
//Refresh Model to pass to partial
IEnumerable<Razor.PageModels.CreateWorkflowNames> namelist = da.GetWfDefVersionNameAndIds();
var freshModel = new CreateNewWorkflowModel(_cache, _mapper, _wlog, _workflowFactory, _configuration)
{
FieldsRequiredOnStart = entityDefinitionFieldsList,
CreateWorkflowModel = namelist
};
return Partial("/Pages/_CreateNewWorkflowRequiredFieldsPartial.cshtml", freshModel);
}
Assuming your CreateNewWorkflow controller action returns a model rehydrated with the new data, you should be able to set that new data in the onSuccess callback of your ajax request.
I'd do this to accomplish the result.
Create partial view for the razor we're gonna need to refresh.
//Filename: YourView.cshtml
<div id="partialWrapper"></div>
//File name: _YourPartialView.cshtml
#if (Model.FieldsRequiredOnStart != null)
{
foreach (var item in Model.FieldsRequiredOnStart)
{
for (int i = 0; i < #item.Inputs.Count(); i++)
{
<h2>Fields Required on Start</h2>
var x = #item.Inputs[i];
<span>#x.Name</span>
<input placeholder="#x.Name" maxlength="#x.MaxSize" type="#x.InputType"> <input />
}
}
}
Make sure your controller action returns a partial view.
public IActionResult<YourModelClass> CreateNewWorkflow(YourRequestClass request) {
//your logic
//...
var rehydratedModel = new YourModelClass(); //actually fill this with data
return PartialView(rehydratedModel);
}
Set the partial view result to your wrapper div in the onSuccess call back.
function onSelect(e) {
let id = $("#wfDropdown").data("kendoDropDownList").value()
if (e.item) {
$('#wfDefId').val(id)
} else {
$('#wfDefId').val(id)
}
$.ajax({
type: 'Post',
url: '/CreateNewWorkflow?handler=RequiredInputs',
beforeSend: function (xhr) {
xhr.setRequestHeader("XSRF-TOKEN",
$('input:hidden[name="__RequestVerificationToken"]').val());
},
data: { Id: id },
success: function (data) { //data represents your partial view
$('#partialWrapper').html(data) //set partial view
}
});
That is a pretty typical flow of how you refresh razor pages with ajax.
Razor not printing Values to screen after ASP 3.1 RazorPages AJAX Post Updates Model, The div is just empty
The issue is related to the Ajax success function, according to your code, we can see that you didn't do anything to update the page with the latest data.
Generally, after getting the latest data in the success function, we could use JQuery to find the page elements and bind the latest data or populate the new page element to replace the old data. You could refer to the following sample code:
<select id="ddl1" asp-for="CategoryId" asp-items="Model.Categories">
<option value="">Select Category</option>
</select>
<h4>SubCategories</h4>
#if (Model.SubCategories != null)
{
<table >
<tr><th>SubCategoryId</th><th>CategoryId</th><th>SubCategoryName</th></tr>
<tbody id="tbody">
#foreach (var item in Model.SubCategories)
{
<tr>
<td>#item.SubCategoryId</td>
<td>#item.CategoryId</td>
<td>#item.SubCategoryName</td>
</tr>
}
</tbody>
</table>
}
Code in the cshtml.cs file:
private ICategoryService _categoryService;
public DDLpageModel(ICategoryService categoryService)
{
_categoryService = categoryService;
}
[BindProperty(SupportsGet = true)]
public int CategoryId { get; set; }
public int SubCategoryId { get; set; }
public SelectList Categories { get; set; }
public List<SubCategory> SubCategories { get; set; }
public void OnGet()
{
Categories = new SelectList(_categoryService.GetCategories(), nameof(Category.CategoryId), nameof(Category.CategoryName));
SubCategories = _categoryService.GetSubCategories(1).ToList();
}
public JsonResult OnGetSubCategories()
{
return new JsonResult(_categoryService.GetSubCategories(CategoryId));
}
Then, in the Ajax success function, find the element and set the value or dynamic add page elements with the latest data and replace the old one.
#section scripts{
<script>
$(function () {
$("#ddl1").on("change", function () {
var categoryId = $(this).val();
//method 1: using JQuery Ajax get the latest data and update the main page content
$.ajax({
url: `?handler=SubCategories&categoryId=${categoryId}`,
contentType: 'application/json; charset=utf-8',
type: 'get',
dataType: 'json',
success: function (data) {
$("#tbody").html("");
//loop through the data and append new data to the tbody
$.each(data, function (i, item) {
$("#tbody").append("<tr><td>" + item.subCategoryId + "</td><td>" + item.categoryId + "</td><td>" + item.subCategoryName + "</td></tr>");
});
}
});
});
});
</script>
}
Besides, you could also create a Partial page (for example: _SubCategories.cshtml):
#model List<SubCategory>
<table class="table table-striped">
<tr><th>SubCategoryId</th><th>CategoryId</th><th>SubCategoryName</th></tr>
<tbody id="tbody">
#foreach (var item in Model)
{
<tr>
<td>#item.SubCategoryId</td>
<td>#item.CategoryId</td>
<td>#item.SubCategoryName</td>
</tr>
}
</tbody>
</table>
In the main page .cshtml.cs file, add the following handler:
public PartialViewResult OnGetSubcategoryPartial()
{
var subcategories = _categoryService.GetSubCategories(CategoryId).ToList();
return Partial("_SubCategories", subcategories);
}
Then, using JQuery Ajax to call the above handler and load the partial page:
<h2>Using Partial Page</h2>
<select id="ddl2" asp-for="CategoryId" asp-items="Model.Categories">
<option value="">Select Category</option>
</select>
<div id="output">
</div>
#section scripts{
<script>
$(function () {
$("#ddl2").on("change", function () {
var categoryId = $(this).val();
$.ajax({
url: `?handler=SubcategoryPartial&categoryId=${categoryId}`,
contentType: 'application/html; charset=utf-8',
type: 'get',
dataType: 'html',
success: function (result) {
$("#output").html("");
$("#output").append(result);
}
});
});
});
</script>
}
The screenshot like this:

Ajax call return always a null object

I know there are a lot of similar posts about this topic but I'm still not able to solve my problem.
I'm trying to call an ActioResult method with a button from my controller through ajax.
The problem is I always get back a null object
I'm quiet sure the problem is that I'm not able to bind the ajax call with the controller in the "data: " field
The AJAX call:
$(document).ready(function () {
$(".milkmilk").click(function () {
$.ajax({
type: "POST",
url: '#Url.Action("GoatMilk", "User")',
datatype: "html",
data: { name: 'name' },
success: function (data) {
$(this).closest('[data-rel]').html(data);
},
error: function (data) {
alert("error!");
}
});
});
});
The controller:
public ActionResult GoatMilk(string name)
{
var rep = new GoatRepository();
var goat = rep.GetAnimal(name);
if(goat != null)
{
var model = FarmFactory.CreateAnimalModel<GoatViewModel>(goat) as GoatViewModel;
model.Milk = rep.MilkProduction(goat);
_MyEchoFarmDB.Update(goat);
var rek = new FarmRepository();
var deposit = rek.GetDeposit(name);
deposit.Milk = goat.Milk;
db.Update(deposit);
return Json(model.Milk, JsonRequestBehavior.AllowGet);
}
return View();
}
The Html code
#foreach (var item in Model)
{
<tbody>
<tr>
<td>
<button class="milkmilk" data-rel="item.Name">MILK</button>
</td>
</tr>
</tbody>
}
The code never enters inside the if(goat != null), so I always get back an error (notice that without the Ajax call the method works just fine)
Thank you!
It may not be the cleanest way to solve this, but seems to run smoothly
HTML:
<td>
<button type="button" class="milk" data-rel="#item.Name">MILK</button>
<p class="milkGoat">#item.Milk</p>
</td>
Controller:
public JsonResult GoatMilk(string name)
{
return Json(model.Milk, JsonRequestBehavior.AllowGet);
}
Ajax:
<script type="text/javascript">
$(document).ready(function () {
$(".milk").click(function () {
var name = $(this).data('rel');
var me = $(this)
$.ajax({
type: "POST",
url: '#Url.Action("GoatMilk", "User")',
datatype: "html",
data: { name: name },
success: function (milk) {
me.next(".milkGoat").html(milk);
},
error: function (milk) {
alert("error!");
}
});
});
});
</script>
instead of ActionResult try JsonResult method to return
public JsonResult GoatMilk()
{
return Json();
}

knockoutjs data bind hidden field value

I'm having a hidden field in a knockout template that its value gets updated with jquery. The problem is when trying to pass this value to the server with ajax, I get null value in the controller. But the html source code shows that the value of the hidden field is updated. If I replaced the hidden field with a textbox, it would work fine only when I enter text manually.
jQuery
function getFileDetail(fileID, fileName) {
$('#hdnFileName' + fileID).val(fileName);
$('#lblFileName' + fileID).text(fileName);
}
Here is the html knockout template:
<script type="text/html" id="fileTemplate">
<div data-role="fieldcontain">
<label data-bind="text: 'File Upload ' + ID, attr: { id: 'lblFileName' + ID }"></label><input type="button" value="Remove" data-bind="click: removeFile" />
</div>
<input type="hidden" name="hdnFileName" data-bind="attr: { id: 'hdnFileName' + ID, value: fileName }" />
</script>
ViewModel
function FileViewModel() {
var self = this;
self.ID = ko.observable();
self.fileName = ko.observable();
self.removeFile = function (file) { };
self.Files = ko.observableArray([{ ID: 1, fileName: "", removeFile: function (file) { self.Files.remove(file); }}]);
self.addNewFile = function () {
var newFile = new FileViewModel();
newFile.ID = self.Files().length + 1;
newFile.fileName = "";
newFile.removeFile = function (file) { self.Files.remove(file); };
self.Files.push(newFile);
//$("input[name='hdnFileName'").trigger("change");
}
}
function ViewModel() {
var self = this;
self.fileViewModel = new FileViewModel();
self.submitForm = function () {
$.ajax({
type: "POST",
url: "<%= Url.Action("MeetingPresenter")%>",
data: "{Files:" + ko.utils.stringifyJson(self.fileViewModel.Files) + "}",
contentType: "application/json",
success: function (data) {},
});
};
}
Your model property ID is an observable, so you need to 'unwrap' to get the value from it when you are concatenating, like this:
<input type="hidden" name="hdnFileName" data-bind="attr: { id: 'hdnFileName' + ID(), value: fileName }" />
and this:
<label data-bind="text: 'File Upload ' + ID(), attr: { id: 'lblFileName' + ID() }"></label>
If you are using knockout.js you don't neede to modify the DOM, you can just update the ViewModel and the DOM will be updated according
function getFileDetail(fileID, fileName) {
viewModel.fileViewModel.update(fileID, fileName);
}
Add the update function in FileViewModel
function FileViewModel() {
// rest of the code
self.update = function(fileID, fileName) {
var file = ko.utils.arrayFirst(self.Files(), function(file) {
return file.ID == fileID;
});
file.fileName(fileName); // this will change and the UI will be updated according
};
}
Note: Please notice that you have a default item in Files that will not be changed with update function because properties are not observable
self.Files = ko.observableArray([{ ID: 1, fileName: "", removeFile: function (file) { self.Files.remove(file); }}]);
You can solve this by making them observable (i.e. ID: observable(1)) or you can create a new FileViewModel().
Note: The viewModel must be accesible in the function (i.e. global instance), otherwise will be undefined.
It looks to me that setting a field's value via the DOM does not interact with knockout. If you are setting its value using .value, the observable will not be updated. You should be updating the observable.
I wrote a little Fiddle to demonstrate. Every 2 seconds, it sets the input's value via the DOM, but the bound observable only changes when you type something.
http://jsfiddle.net/qcv01h2e/
var viewModel = (function () {
return {
fv: ko.observable().extend({notify:'always'})
};
}());
ko.applyBindings(viewModel);
setInterval(function () {
console.debug("Set it");
var f = document.getElementById('field');
f.value = "Hi";
console.debug("fv is", viewModel.fv());
}, 2000);
I came across a similar issue where I need to set a value without user input.
Before doing the click update function I do the required model update. If you have mode operations better to introduce a function in the model.
<input data-bind="click: function(){ isEnabled(true); update() }" />
What I actually did was,
<input data-bind="click: function(){ isEnabled(!isEnabled()); update() }" />
Keep in mind that asynchronous nature of javascript.

How to use ajax call to pass json data to controller and render a partial view as result?

I need to pass model object to controller, and from there to call service to generate data for the partial view. I am able to pass the json object to the main view, and I am able to generate the partial view. However, I am having difficulties to render the partial view in the main view after the call. If I don't pass object to controller, I am able to render the partial view.
My Main goal is: to pass json object and render partial view with the same ajax call.
Would appreciate help on this.
I apologize for the lengthy code here, but not sure how I could do it some other way.
The following code works, where I do not pass Json object via ajax call, and create department object in the controller:
main view code:
#model PartialViewDemo.Models.School
....
<body>
....
<div>
#Html.Partial("_MyPartialView", Model.Department )
</div>
....
<div id="divTest"></div>
<input type="button" value="Click" id="btnClick"/>
</body>
<script src="~/Content/jquery-1.7.1.min.js"></script>
<script type="text/javascript">
$(function() {
$('#btnClick').click(function(data) {
var dept = {
DepartmentName: "test Dept",
DepartmentRule: "test rule",
Comment:" test comment"
};
$.ajax({
url: '/home/ShowPartailView/',
success: function (result) {
$('#divTest').html(result);
},
failure: function (errMsg) {
alert(errMsg);
}
});
});
});
</script>
controller code:
public ActionResult Index()
{
var model = new School();
model.Department = GetDepartmentList(3);
return View(model);
}
public List<Department> GetDepartmentList(int counter)
{
var model = new List<Department>();
for (var i = 1; i <= counter; i++)
{
var data = new Department();
data.DepartmentName = "Dept " + i;
data.DepartmentRule = "Rule " + i;
data.Comment = "Comment " + i;
model.Add(data);
}
return model;
}
public PartialViewResult ShowPartailView()
{
Department dept = new Department()
{
DepartmentName = "test Dept",
DepartmentRule = "test rule",
Comment = "We Rock!"
};
PartialViewResult result = PartialView("_MySecondPartialView", dept);
return result;
}
Partial view code:
#model PartialViewDemo.Models.Department
<h2>_MyView from partial view using PartialView</h2>
#if (Model != null)
{
<div>
<table>
<thead>
....
</thead>
<tbody>
<tr>
<td>#Model.DepartmentName</td>
<td>#Model.DepartmentRule</td>
<td>#Model.Comment</td>
</tr>
</tbody>
</table>
</div>
}
Model:
public class Department
{
public string DepartmentName { get; set; }
public string DepartmentRule { get; set; }
public string Comment { get; set; }
}
public class School
{
public List<Department> Department { get; set; }
}
However, when I pass in Json object to ajax call with all other code stay the same, except the following changes, the partial view won't show with click event.
$.ajax({
url: '/home/ShowPartailView/',
data: JSON.stringify(dept),
dataType: 'json',
type: 'POST',
contentType: 'application/json; charset=utf-8',
success: function (result) {
$('#divTest').html(result);
},
failure: function (errMsg) {
alert(errMsg);
}
});
with controller code:
public PartialViewResult ShowPartailView(Department dept)
{
PartialViewResult result = PartialView("_MySecondPartialView", dept);
return result;
}
In the second example where you pass the object, you have specified the
dataType: 'json',
ajax option but your controller method is returning a partial view so it needs to be
dataType: 'html',
Side note: You can omit the contentType option and just use data: dept,
If you have return partial view then try this to render partial view in Div or any other element.
$(document).ready(function () {
$.ajax({
url: '/YourContollerName/YourActionName',
type: "POST"
})
.success(function (result) {
$('.loadOnDivClass').empty();
$('.loadOnDivClass').html(result);
})
.error(function (status) {
alert(status);
})
});
If Contrller Action return like this
return PartialView("_PartialList",modelObj);

Resources